#!/usr/bin/env python
# --------------------------------------------------------------
# The functions for the lattice modifications to replace the base
# RF gap nodes with The Axis Fields nodes that can overlap with
# quadrupoles.
# The initial base RF gap nodes have zero length, and the length
# of new nodes is defined by the RF fields on the RF gap axis.
# We also have to adjust these lengths to avoid overlapping RF fields.
# This correction will be on the level of microns, and it will
# not change the strength of the fields.
# The RF fields can overlap the zero length elements like markers or
# dipole correctors. The whole RF gap field will be chopped into pieces.
# The RF fields also can overlap the fields one or several quads, and
# these quads' fields will be included into the RF gap node to use
# during the bunch tracking.
# --------------------------------------------------------------
import math
import sys
import os
import time
# import from orbit Python utilities
from orbit.utils import orbitFinalize
# from orbit.py_linac.lattice import LinacApertureNode
from orbit.py_linac.lattice import Quad, Drift
from orbit.py_linac.lattice import BaseRF_Gap, AxisFieldRF_Gap
from orbit.py_linac.lattice import OverlappingQuadsNode
from orbit.py_linac.lattice import AxisField_and_Quad_RF_Gap
from orbit.py_linac.overlapping_fields import EngeFunction
from orbit.core.orbit_utils import Function
from orbit.py_linac.lattice_modifications.rf_models_modifications_lib import Make_AxisFieldRF_Gaps_and_Find_Neihbor_Nodes
def GetEngeFunction(quad):
"""
This is an example of a EngeFunctionFactory function.
It returns the EngeFunction function for the instance of Quad node.
The quad should have the aperture parameter.
It is used in the quad+rf lattice transformation by default.
User can prepare his/her own EngeFunctionFactory function.
"""
length_param = quad.getLength()
if quad.hasParam("aperture"):
acceptance_diameter_param = quad.getParam("aperture")
cutoff_level = 0.001
func = EngeFunction(length_param, acceptance_diameter_param, cutoff_level)
return func
else:
msg = "Inside the Replace_BaseRF_Gap_and_Quads_to_Overlapping_Nodes Python function. "
msg += os.linesep
msg += "Cannot create the EngeFunction for the quad!"
msg += os.linesep
msg = msg + "quad name = " + quad.getName()
msg = msg + os.linesep
msg = msg + "It does not have the aperture parameter!"
msg = msg + os.linesep
orbitFinalize(msg)
return None
[docs]def Replace_BaseRF_Gap_and_Quads_to_Overlapping_Nodes(
accLattice, z_step, dir_location="", accSeq_Names=[], cavs_Names=[], EngeFunctionFactory=GetEngeFunction
):
"""
Function will replace BaseRF_Gap nodes by AxisField_and_Quad_RF_Gap.
It is assumed that AxisField_and_Quad_RF_Gap nodes do not overlap any
others nodes (except Drifts). The location of the axis field
data files are specified by dir_location input variable.
The replacement will be performed only for specified sequences.
If the cavities list is empty, all of them will be replaced!
If you want to replace the nodes in a particular cavity please specify it!
The dir_location is the location of the directory with the axis field
files.
We assume that the RF gap field overlaps only with the fields of
two neighboring quads.
z_step defines the longitudinal step during the tracking through the
elements with overlapping fields.
The magnetic field longitudinal dependency in quads will be described
by Enge Functions that will be produced by the GetEngeFunction function
by default. The user can supply his/her own factory for these functions.
"""
# -----------------------------------------------------------------------------
"""
node_pos_dict = accLattice.getNodePositionsDict()
#for node_ind in range(len(accLattice.getNodes())):
for node_ind in range(199,298):
node = accLattice.getNodes()[node_ind]
#if(not isinstance(node,Quad)): continue
(pos_start,pos_end) = node_pos_dict[node]
print "debug ind=",node_ind," node=",node.getName()," (pos_start,pos_end)=",(pos_start,pos_end)
"""
# -----------------------------------------------------------------------------
# ---- rf_length_tolerance RF fields should not overlap more than this value
rf_length_tolerance = 0.0001
drift_length_tolerance = 0.00000001
node_pos_dict = accLattice.getNodePositionsDict()
for accSeq_Name in accSeq_Names:
accSeq = accLattice.getSequence(accSeq_Name)
# print "debug ================== START seq=",accSeq.getName()
if accSeq == None:
msg = "The Replace_BaseRF_Gap_and_Quads_to_Overlapping_Nodes Python function. "
msg += os.linesep
msg += "Cannot find the acc. sequence with this name in the lattice!"
msg += os.linesep
msg = msg + "accSeq name = " + accSeq_Name
msg = msg + os.linesep
msg = msg + "lattice name = " + accLattice.getName()
msg = msg + os.linesep
orbitFinalize(msg)
# --------------------------------------------------------------
nodes = accSeq.getNodes()
# --- just for case: if the nodes are not in the right order
nodes = sorted(nodes, key=lambda x: x.getPosition(), reverse=False)
# ---- create Enge Functions' dictionary by using the usual quad nodes as keys
enge_func_quad_dict = {}
quads = accLattice.getQuads(accSeq)
for quad in quads:
enge_func_quad_dict[quad] = EngeFunctionFactory(quad)
# --------------------------------------------
cavs = accSeq.getRF_Cavities()
if len(cavs_Names) > 0:
cavs_tmp = []
for cav in cavs:
if cav.getName() in cavs_Names:
cavs_tmp.append(cav)
cavs = cavs_tmp
# ---- let's check that all rf gaps are BaseRF_Gap instances
# ---- and create the dictionaries to account for new rf gaps later
# ---- rf_gap_to_cavs_dict[rf_gap] = cav
# ---- new_rf_gaps_arr_in_cav_dict[cav] = [new_AxisField_and_Quad_RF_Gap_0,..]
rf_gap_to_cavs_dict = {}
new_rf_gaps_arr_in_cav_dict = {}
for cav in cavs:
rf_gaps = cav.getRF_GapNodes()
new_rf_gaps_arr_in_cav_dict[cav] = []
for rf_gap in rf_gaps:
rf_gap_to_cavs_dict[rf_gap] = cav
if not isinstance(rf_gap, BaseRF_Gap):
msg = "The Replace_BaseRF_Gap_and_Quads_to_Overlapping_Nodes function. "
msg += "You are trying to replace the RF gap which is not BaseRF_Gap instance!"
msg += os.linesep
msg = msg + "RF Gap =" + rf_gap.getName()
msg = msg + os.linesep
msg = msg + "Type of gap node=" + rf_gap.getType()
msg = msg + os.linesep
orbitFinalize(msg)
# ---- af_rf_gap_dict[rf_gap] = AxisFieldRF_Gap(rf_gap)
# ---- rf_gap_ind_up_down_arr[[rf_gap,gap_ind,drift_down_ind,drift_up_ind],...]
(af_rf_gap_dict, rf_gap_ind_up_down_arr) = Make_AxisFieldRF_Gaps_and_Find_Neihbor_Nodes(
rf_length_tolerance, accLattice, accSeq, dir_location, cavs
)
if len(rf_gap_ind_up_down_arr) == 0:
msg = "The Replace_BaseRF_Gap_and_Quads_to_Overlapping_Nodes function. "
msg += "This Acc. Sequence does not have BaseRF_Gaps!"
msg += os.linesep
msg += "It is better to use another method of the lattice modification!"
msg += os.linesep
msg = msg + "Acc Sequence =" + accSeq.getName()
msg = msg + os.linesep
orbitFinalize(msg)
# -----------------------------------------------------
# ---- Now we are going to build a new lattice with AxisField_and_Quad_RF_Gap
# ---- and OverlappingQuadsNode classes. Even drifts will be replaced with
# ---- these nodes to simplify the lattice structure. The original lattice
# ---- should have only drifts, quads, and BaseRF_Gaps as elements with
# ---- non-zero length. If the user wants to include other sources of the EM
# ---- fields, he/she has to create or modify the classes responsible for
# ---- handling the overlapping EM fields.
# ---- We will generate new nodes in an arbitrary order, but at the end
# ---- we will sort them according to their position.
# -----------------------------------------------------
new_nodes = []
# -------------------------------------------------------------------------
# ---- 1st STEP - nodes from the beginning to the first RF gap
[rf_gap, gap_ind, drift_down_ind, drift_up_ind] = rf_gap_ind_up_down_arr[0]
node_ind_start = 0
node_ind_end = drift_down_ind
(node_pos_start, pos_tmp) = node_pos_dict[nodes[node_ind_start]]
accSeq_z_start = node_pos_start
(rf_gap_pos_start, rf_gap_pos_end) = node_pos_dict[rf_gap]
(z_gap_min, z_gap_max) = af_rf_gap_dict[rf_gap].getZ_Min_Max()
(rf_gap_pos_start, rf_gap_pos_end) = (rf_gap_pos_start + z_gap_min, rf_gap_pos_end + z_gap_max)
# print "debug rf gap=",rf_gap.getName()," (rf_gap_pos_start, rf_gap_pos_end)=",(rf_gap_pos_start, rf_gap_pos_end)
# print "debug (node_ind_start,node_ind_end) = ",(node_ind_start,node_ind_end)
# for ind in range(node_ind_start,rf_gap_ind_up_down_arr[0][1]+1):
# print "debug ind =",ind," node=",nodes[ind].getName()," (node_pos_start,node_pos_end)=",node_pos_dict[nodes[ind]]
node_pos_start = node_pos_start - accSeq_z_start
node_pos_end = rf_gap_pos_start - accSeq_z_start
# ---- if the length of the node is equal to 0. we will add only the zero length elements
(local_quads, zero_length_nodes) = Get_quads_zeroLengthNodes_in_range(accSeq, node_ind_start, node_ind_end)
if abs(node_pos_start - node_pos_end) > drift_length_tolerance:
# print "debug before n_quads=",len(local_quads)," n_markers=",len(zero_length_nodes)
pos_start = node_pos_start
pos_end = node_pos_end
ovrlp_count = 0
for ind in range(len(zero_length_nodes) + 1):
node = OverlappingQuadsNode()
name = rf_gap.getName() + ":Before:" + str(ovrlp_count + 1) + ":" + node.getType()
node.setName(name)
if ind == len(zero_length_nodes):
pos_end = node_pos_end
else:
pos_end = zero_length_nodes[ind].getPosition()
length = pos_end - pos_start
if abs(length) < drift_length_tolerance:
if ind < len(zero_length_nodes):
new_nodes.append(zero_length_nodes[ind])
pos_start = pos_end
continue
pos = (pos_end + pos_start) / 2
node.setLength(length)
node.setPosition(pos)
for quad in local_quads:
node.addQuad(quad, enge_func_quad_dict[quad], quad.getPosition() - pos_start)
node.setZ_Step(z_step)
nParts = int(length / z_step) + 1
node.setnParts(nParts)
new_nodes.append(node)
ovrlp_count += 1
if ind < len(zero_length_nodes):
new_nodes.append(zero_length_nodes[ind])
pos_start = pos_end
else:
new_nodes += zero_length_nodes
# --------------------------------------------------------------------------
# ---- 2st STEP - nodes from the last RF gap to the end of the Acc. Sequence
[rf_gap, gap_ind, drift_down_ind, drift_up_ind] = rf_gap_ind_up_down_arr[len(rf_gap_ind_up_down_arr) - 1]
node_ind_start = drift_up_ind
node_ind_end = len(nodes) - 1
(rf_gap_pos_start, rf_gap_pos_end) = node_pos_dict[rf_gap]
(z_gap_min, z_gap_max) = af_rf_gap_dict[rf_gap].getZ_Min_Max()
(rf_gap_pos_start, rf_gap_pos_end) = (rf_gap_pos_start + z_gap_min, rf_gap_pos_end + z_gap_max)
node_pos_start = rf_gap_pos_end
(node_pos_tmp, node_pos_end) = node_pos_dict[nodes[node_ind_end]]
(local_quads, zero_length_nodes) = Get_quads_zeroLengthNodes_in_range(accSeq, node_ind_start, node_ind_end)
if abs(node_pos_start - node_pos_end) > drift_length_tolerance:
# print "debug after n_quads=",len(local_quads)," n_markers=",len(zero_length_nodes)
pos_start = node_pos_start - accSeq_z_start
pos_end = node_pos_end - accSeq_z_start
ovrlp_count = 0
for ind in range(len(zero_length_nodes) + 1):
node = OverlappingQuadsNode()
name = rf_gap.getName() + ":After:" + str(ovrlp_count + 1) + ":" + node.getType()
node.setName(name)
if ind == len(zero_length_nodes):
pos_end = node_pos_end - accSeq_z_start
else:
pos_end = zero_length_nodes[ind].getPosition()
length = pos_end - pos_start
if abs(length) < drift_length_tolerance:
if ind < len(zero_length_nodes):
new_nodes.append(zero_length_nodes[ind])
pos_start = pos_end
continue
pos = (pos_end + pos_start) / 2
node.setLength(length)
node.setPosition(pos)
for quad in local_quads:
node.addQuad(quad, enge_func_quad_dict[quad], quad.getPosition() - pos_start)
node.setZ_Step(z_step)
nParts = int(length / z_step) + 1
node.setnParts(nParts)
new_nodes.append(node)
ovrlp_count += 1
if ind < len(zero_length_nodes):
new_nodes.append(zero_length_nodes[ind])
pos_start = pos_end
else:
new_nodes += zero_length_nodes
# --------------------------------------------------------------------------
# ---- 3st STEP - nodes of AxisField_and_Quad_RF_Gap type - new RF gaps
# ---- rf_gap_to_cavs_dict[rf_gap] = cav
# ---- new_rf_gaps_arr_in_cav_dict[cav] = [new_AxisField_and_Quad_RF_Gap_0,..]
n_rf_gaps = len(rf_gap_ind_up_down_arr)
for local_gap_ind in range(n_rf_gaps):
[rf_gap, gap_ind, drift_down_ind, drift_up_ind] = rf_gap_ind_up_down_arr[local_gap_ind]
cav = rf_gap_to_cavs_dict[rf_gap]
new_rf_gaps_arr = new_rf_gaps_arr_in_cav_dict[cav]
axisFieldRF_Gap = af_rf_gap_dict[rf_gap]
(local_quads, zero_length_nodes) = Get_quads_zeroLengthNodes_in_range(accSeq, drift_down_ind, drift_up_ind)
local_quads = Find_Neihbor_Quads(accLattice, accSeq, rf_gap, gap_ind, af_rf_gap_dict, enge_func_quad_dict)
(rf_gap_pos_start, rf_gap_pos_end) = node_pos_dict[rf_gap]
rf_gap_pos_zero = rf_gap_pos_start - accSeq_z_start
(z_gap_min, z_gap_max) = axisFieldRF_Gap.getZ_Min_Max()
(rf_gap_pos_start, rf_gap_pos_end) = (rf_gap_pos_start + z_gap_min, rf_gap_pos_end + z_gap_max)
pos_start = rf_gap_pos_start - accSeq_z_start
pos_end = rf_gap_pos_end - accSeq_z_start
# --------------------------------------------------------
ovrlp_count = 0
for ind in range(len(zero_length_nodes) + 1):
axisField_and_Quad_RF_Gap = AxisField_and_Quad_RF_Gap(axisFieldRF_Gap)
name = rf_gap.getName() + ":" + axisField_and_Quad_RF_Gap.getType() + ":" + str(ovrlp_count + 1)
new_rf_gaps_arr.append(axisField_and_Quad_RF_Gap)
axisField_and_Quad_RF_Gap.setName(name)
if ind == len(zero_length_nodes):
pos_end = rf_gap_pos_end - accSeq_z_start
else:
pos_end = zero_length_nodes[ind].getPosition()
length = pos_end - pos_start
if abs(length) < drift_length_tolerance:
if ind < len(zero_length_nodes):
new_nodes.append(zero_length_nodes[ind])
pos_start = pos_end
continue
pos = (pos_end + pos_start) / 2
z_min = pos_start - rf_gap_pos_zero
z_max = pos_end - rf_gap_pos_zero
axisField_and_Quad_RF_Gap.setZ_Min_Max(z_min, z_max)
axisField_and_Quad_RF_Gap.setPosition(pos)
for quad in local_quads:
axisField_and_Quad_RF_Gap.addQuad(quad, enge_func_quad_dict[quad], quad.getPosition() - rf_gap_pos_zero)
axisField_and_Quad_RF_Gap.setZ_Step(z_step)
new_nodes.append(axisField_and_Quad_RF_Gap)
ovrlp_count += 1
if ind < len(zero_length_nodes):
new_nodes.append(zero_length_nodes[ind])
pos_start = pos_end
# --------------------------------------------------------------------------
# ---- 4st STEP - nodes between two RF gaps
for local_gap_ind in range(n_rf_gaps - 1):
[rf_gap0, gap0_ind, drift_down0_ind, drift_up0_ind] = rf_gap_ind_up_down_arr[local_gap_ind]
[rf_gap1, gap1_ind, drift_down1_ind, drift_up1_ind] = rf_gap_ind_up_down_arr[local_gap_ind + 1]
(rf_gap0_pos_start, rf_gap0_pos_end) = node_pos_dict[rf_gap0]
(z_gap0_min, z_gap0_max) = af_rf_gap_dict[rf_gap0].getZ_Min_Max()
(rf_gap0_pos_start, rf_gap0_pos_end) = (rf_gap0_pos_start + z_gap0_min, rf_gap0_pos_end + z_gap0_max)
(rf_gap1_pos_start, rf_gap1_pos_end) = node_pos_dict[rf_gap1]
(z_gap1_min, z_gap1_max) = af_rf_gap_dict[rf_gap1].getZ_Min_Max()
(rf_gap1_pos_start, rf_gap1_pos_end) = (rf_gap1_pos_start + z_gap1_min, rf_gap1_pos_end + z_gap1_max)
(local_quads, zero_length_nodes) = Get_quads_zeroLengthNodes_in_range(accSeq, drift_up0_ind, drift_down1_ind)
node_pos_start = rf_gap0_pos_end - accSeq_z_start
node_pos_end = rf_gap1_pos_start - accSeq_z_start
if abs(node_pos_start - node_pos_end) > drift_length_tolerance:
pos_start = node_pos_start
pos_end = node_pos_end
ovrlp_count = 0
for ind in range(len(zero_length_nodes) + 1):
node = OverlappingQuadsNode()
name = rf_gap0.getName() + ":After:" + str(ovrlp_count + 1) + ":" + node.getType()
node.setName(name)
if ind == len(zero_length_nodes):
pos_end = node_pos_end
else:
pos_end = zero_length_nodes[ind].getPosition()
length = pos_end - pos_start
if abs(length) < drift_length_tolerance:
if ind < len(zero_length_nodes):
new_nodes.append(zero_length_nodes[ind])
pos_start = pos_end
continue
pos = (pos_end + pos_start) / 2
node.setLength(length)
node.setPosition(pos)
for quad in local_quads:
node.addQuad(quad, enge_func_quad_dict[quad], quad.getPosition() - pos_start)
node.setZ_Step(z_step)
nParts = int(length / z_step) + 1
node.setnParts(nParts)
new_nodes.append(node)
ovrlp_count += 1
if ind < len(zero_length_nodes):
new_nodes.append(zero_length_nodes[ind])
pos_start = pos_end
else:
new_nodes += zero_length_nodes
# --------------------------------------------------------------------------
new_nodes = sorted(new_nodes, key=lambda x: x.getPosition(), reverse=False)
# --------------------------------------------------------------------------
# ---- rf_gap_to_cavs_dict[rf_gap] = cav
# ---- new_rf_gaps_arr_in_cav_dict[cav] = [new_AxisField_and_Quad_RF_Gap_0,..]
# ---- the new istances of the AxisField_and_Quad_RF_Gap has setAsFirstRFGap(False) by default
for cav in cavs:
new_rf_gaps_arr = new_rf_gaps_arr_in_cav_dict[cav]
cav.removeAllGapNodes()
for axisField_and_Quad_RF_Gap in new_rf_gaps_arr:
cav.addRF_GapNode(axisField_and_Quad_RF_Gap)
# ------------------------------------------
# ---- let's replace all nodes in the AccSeq by the new set
accSeq.removeAllNodes()
for node in new_nodes:
accSeq.addNode(node)
# ---- new set of nodes for the lattice
new_latt_nodes = []
for accSeq in accLattice.getSequences():
new_latt_nodes += accSeq.getNodes()
accLattice.setNodes(new_latt_nodes)
accLattice.initialize()
# ------- debug START printing of new nodes and their positions in the lattice
"""
node_pos_dict = accLattice.getNodePositionsDict()
for accSeq_Name in accSeq_Names:
accSeq = accLattice.getSequence(accSeq_Name)
nodes = accSeq.getNodes()
accSeq_z_start = node_pos_dict[nodes[0]][0]
#--------------------------------------------------------------------------
for node in nodes:
pos = node.getPosition()
(pos_start,pos_end) = node_pos_dict[node]
delta = pos - ((pos_start+pos_end)/2 - accSeq_z_start)
if(abs(delta) > drift_length_tolerance):
print "debug new node=",node.getName()," pos=",node.getPosition()," (pos_start,pos_end)=",node_pos_dict[node]," delta=",delta
"""
# ------- debug STOP printing of new nodes and their positions in the lattice
def Get_quads_zeroLengthNodes_in_range(accSeq, node_ind_start, node_ind_end):
"""
Returns all quads and zero-length nodes in this index range.
It also checks that all elements inside this range has zero length or they
are drifts of quads.
"""
nodes = accSeq.getNodes()
zero_length_nodes = []
child_nodes = []
quads = []
for node_ind in range(node_ind_start, node_ind_end + 1):
node = nodes[node_ind]
children_arr = node.getBodyChildren()
if len(children_arr) > 0:
# print "debug ========= parent node=",node.getName()," pos = ",node.getPosition()
for child in children_arr:
if child.getLength() == 0.0:
child_nodes.append(child)
# print " debug child=",child.getName()," pos=",child.getPosition()
if not isinstance(node, BaseRF_Gap):
length = node.getLength()
if length == 0.0:
zero_length_nodes.append(node)
else:
if isinstance(node, Quad):
quads.append(node)
else:
if not isinstance(node, Drift):
msg = "The Replace_BaseRF_Gap_and_Quads_to_Overlapping_Nodes function. "
msg += "This Acc. Sequence has an element that "
msg += os.linesep
msg += "1. has non-zero length"
msg += os.linesep
msg += "2. not a quad"
msg += os.linesep
msg += "3. not a drift"
msg += os.linesep
msg += "This function does not know how to handle this elelement!"
msg += os.linesep
msg = msg + "Acc Sequence =" + accSeq.getName()
msg = msg + os.linesep
msg = msg + "Acc element =" + node.getName()
msg = msg + os.linesep
msg = msg + "Acc element type =" + node.getType()
msg = msg + os.linesep
orbitFinalize(msg)
zero_length_nodes += child_nodes
zero_length_nodes = sorted(zero_length_nodes, key=lambda x: x.getPosition(), reverse=False)
return (quads, zero_length_nodes)
def Find_Neihbor_Quads(accLattice, accSeq, rf_gap, gap_ind, af_rf_gap_dict, enge_func_quad_dict):
"""
This function will find the quads with the fields overlapping RF gap fields.
It returns the quads that should be taken into account.
gap_ind is an index of rf_gap in the original accLattice.getNodes().
"""
nodes = accSeq.getNodes()
node_pos_dict = accLattice.getNodePositionsDict()
(z_min, z_max) = af_rf_gap_dict[rf_gap].getZ_Min_Max()
(gap_pos_start, gap_pos_end) = node_pos_dict[rf_gap]
(gap_pos_start, gap_pos_end) = (gap_pos_start + z_min, gap_pos_end + z_max)
quads = []
# ---- let's go down
node_ind = gap_ind
while node_ind >= 0:
node = nodes[node_ind]
if isinstance(node, Quad):
(node_pos_start, node_pos_end) = node_pos_dict[node]
(z_min_node, z_max_node) = enge_func_quad_dict[node].getLimitsZ()
(node_pos_start, node_pos_end) = (
(node_pos_start + node_pos_end) / 2 + z_min_node,
(node_pos_start + node_pos_end) / 2 + z_max_node,
)
delta = node_pos_end - gap_pos_start
if delta > 0.0:
quads.append(node)
else:
break
node_ind -= 1
# ---- let's go up
node_ind = gap_ind
while node_ind <= len(nodes) - 1:
node = nodes[node_ind]
if isinstance(node, Quad):
(node_pos_start, node_pos_end) = node_pos_dict[node]
(z_min_node, z_max_node) = enge_func_quad_dict[node].getLimitsZ()
(node_pos_start, node_pos_end) = (
(node_pos_start + node_pos_end) / 2 + z_min_node,
(node_pos_start + node_pos_end) / 2 + z_max_node,
)
delta = node_pos_start - gap_pos_end
if delta < 0.0:
quads.append(node)
else:
break
node_ind += 1
quads = sorted(quads, key=lambda x: x.getPosition(), reverse=False)
return quads