diff --git a/manual/introduction.tex b/manual/introduction.tex index a7f7f67..cab8ad4 100644 --- a/manual/introduction.tex +++ b/manual/introduction.tex @@ -114,7 +114,7 @@ \section{Lattice Elements} One class are the elements that particles are tracked through. These ``tracking'' elements are contained in the ``tracking branches'' (\sref{s:branch.def}) of the lattice. Other elements, called ``\vn{lord}'' elements, are used to -represent relationships between elements. ``\vn{Super_lord}'' elements (\sref{c:super}) are +represent relationships between elements. ``\vn{Super-lord}'' elements (\sref{c:super}) are used when elements overlap spatially. ``\vn{Multipass_lord}'' elements (\ref{c:multipass}) are used when a beam goes through the same elements multiple times like in a recirculating Linac or when different beam go through the same elements like in the interaction region of a @@ -279,13 +279,13 @@ \section{Differences From Bmad} % \item With \bmad superposition of two non-drift elements, if there existed the appropriate -combined type, will result in a \vn{super_slave} of the appropriate combined type. For example, -a \vn{solenoid} superimposed over a \vn{quadrupole} would give a \vn{sol_quad} \vn{super_slave} with -\vn{solenoid} and \vn{quadrupole} \vn{super_lords}. The problem here is that calculation of the -\vn{super_slave} parameters may not be possible. For example if the \vn{super_lord} -elements are misaligned, in general it is not possible to compute a corresponding \vn{super_slave} -misalignment. To avoid this, \accellat creates a \vn{UnionEle} \vn{super_slave} element -(which in \bmad is known as a ``jumbo'' \vn{super_slave}). It is up to the tracking routines to +combined type, will result in a super-slave of the appropriate combined type. For example, +a \vn{solenoid} superimposed over a \vn{quadrupole} would give a \vn{sol_quad} super slave with +\vn{solenoid} and \vn{quadrupole} super lords. The problem here is that calculation of the +super slave parameters may not be possible. For example if the super lord +elements are misaligned, in general it is not possible to compute a corresponding super slave +misalignment. To avoid this, \accellat creates a \vn{UnionEle} super slave element +(which in \bmad is known as a ``jumbo'' super slave). It is up to the tracking routines to figure out how to track though a \vn{UnionEle} % \item diff --git a/manual/superimpose.tex b/manual/superimpose.tex index 150266e..324f8a2 100644 --- a/manual/superimpose.tex +++ b/manual/superimpose.tex @@ -129,7 +129,7 @@ \section{Superposition on a Drift} element is just a solenoid. Next comes \vn{M}, \vn{S{\#}1}, and finally \vn{D\#2} is the rest of the drift outside \vn{S}. -In the above example, \vn{Q} and \vn{S} will be \vn{super_lord} elements (\vn{s:lord.slave}) and +In the above example, \vn{Q} and \vn{S} will be super lord elements (\vn{s:lord.slave}) and four elements in the tracking part of the lattice will be \vn{super_slave} elements. This is illustrated in \fig{f:super.ex}B. @@ -339,7 +339,7 @@ \section{Superposition on a Drift} my_oct: octupole, ..., superimpose, ref = a_drft##2 ! This is an error \end{example} -When the parameters of a super_slave are computed from the parameters of its super_lords, some types +When the parameters of a super_slave are computed from the parameters of its super lords, some types of parameters may be ``missing''. For example, it is, in general, not possible to set appropriate aperture parameters (\sref{s:limit}) of a super_slave if the lords of the slave have differing aperture settings. When doing calculations, \bmad will use the corresponding parameters stored in @@ -434,7 +434,7 @@ \section{Jumbo Super_Slaves} \end{example} \index{lord_pad1}\index{lord_pad2} \vn{Q} and part of \vn{S} have been combined into a jumbo \vn{super_slave} named \vn{Q{\B}S}. Since -the \vn{super_lord} elements of a jumbo \vn{super_slave} may not completely span the slave two +the super lord elements of a jumbo \vn{super_slave} may not completely span the slave two parameters of each lord will be set to show the position of the lord within the slave. These two parameters are \begin{example} @@ -442,7 +442,7 @@ \section{Jumbo Super_Slaves} lord_pad2 ! offset at downstream end \end{example} \vn{lord_pad1} is the distance between the upstream edge of the jumbo \vn{super_slave} and a -\vn{super_lord}. \vn{lord_pad2} is the distance between the downstream edge of a \vn{super_lord} and +super lord. \vn{lord_pad2} is the distance between the downstream edge of a super lord and the downstream edge of the jumbo \vn{super_slave}. With the present example, the lords have the following padding: \begin{example} diff --git a/src/accessor.jl b/src/accessor.jl index 9d98272..a8dda61 100644 --- a/src/accessor.jl +++ b/src/accessor.jl @@ -23,6 +23,7 @@ function Base.getproperty(lat::Lattice, sym::Symbol) if sym == :name; return getfield(lat, :name); end if sym == :branch; return getfield(lat, :branch); end if sym == :pdict; return getfield(lat, :pdict); end + if sym == :private; return getfield(lat, :private); end return getfield(lat, :pdict)[sym] end @@ -160,7 +161,7 @@ function Base.setproperty!(ele::Ele, sym::Symbol, value, check_settable = true) else branch = lat_branch(ele) - if isnothing(branch) || isnothing(branch.lat) || branch.lat.record_changes + if isnothing(branch) || isnothing(branch.lat) || branch.lat.auditing_enabled pdict[:changed][sym] = get_elegroup_param(ele, pdict[Symbol(parent)], pinfo) end @@ -183,7 +184,7 @@ function ele_parameter_has_changed!(ele) branch = lat_branch(ele) if isnothing(branch) || isnothing(branch.lat); return; end - if branch.lat.record_changes + if branch.lat.auditing_enabled if branch.type == TrackingBranch branch.ix_ele_min_changed = min(branch.ix_ele_min_changed, ele.ix_ele) branch.ix_ele_max_changed = max(branch.ix_ele_max_changed, ele.ix_ele) @@ -357,9 +358,9 @@ function set_elegroup_param!(ele::Ele, group::BMultipoleGroup, pinfo::ParamInfo, if isnothing(mul.integrated) mul.integrated = (mtype[end] == 'L') elseif (mtype[end] == 'L') != mul.integrated - error(f"Cannot set non-integrated multipole value for integrated multipole and " * - f"vice versa for {pinfo.user_sym} in {ele_name(ele)}.\n" * - f"Use toggle_integrated! to change the integrated status.") + error("Cannot set non-integrated multipole value for integrated multipole and " * + "vice versa for {pinfo.user_sym} in $(ele_name(ele)).\n" * + "Use toggle_integrated! to change the integrated status.") end return setfield!(mul, pinfo.struct_sym, value) @@ -373,9 +374,9 @@ function set_elegroup_param!(ele::Ele, group::EMultipoleGroup, pinfo::ParamInfo, if isnothing(mul.Eintegrated) mul.Eintegrated = (mtype[end] == 'L') elseif (mtype[end] == 'L') != mul.Eintegrated - error(f"Cannot set non-integrated multipole value for integrated multipole and " * - f"vice versa for {pinfo.user_sym} in {ele_name(ele)}.\n" * - f"Use toggle_integrated! to change the integrated status.") + error("Cannot set non-integrated multipole value for integrated multipole and " * + "vice versa for {pinfo.user_sym} in $(ele_name(ele)).\n" * + "Use toggle_integrated! to change the integrated status.") end return setfield!(mul, pinfo.struct_sym, value) diff --git a/src/bookkeeper.jl b/src/bookkeeper.jl index a6054ae..8658496 100644 --- a/src/bookkeeper.jl +++ b/src/bookkeeper.jl @@ -1,24 +1,3 @@ -#--------------------------------------------------------------------------------------------------- -# ChangedLedger - -""" - Internal: mutable struct ChangedLedger - -When bookkeeping a branch, element-by-element, starting from the beginning of the branch, -the ledger keeps track of what has changed so that the change can propagate to the -following elements. - -Ledger parameters, when toggled to true, will never be reset for the remainder of the branch bookkeeping. -The exception is the `this_ele_length` parameter which is reset for each element. -""" ChangedLedger - -@kwdef mutable struct ChangedLedger - this_ele_length::Bool = false - s_position::Bool = false - ref_group::Bool = false - floor_position::Bool = false -end - #--------------------------------------------------------------------------------------------------- # bookkeeper!(Lattice) @@ -32,24 +11,31 @@ bookkeeping code will propagate that change through the reset of the lattice. function bookkeeper!(lat::Lattice) if !lat.parameters_have_changed; return; end lat.parameters_have_changed = false + push_bookkeeping_state!(lat, autobookkeeping = false, auditing_enabled = false) + + try + # Tracking branch bookkeeping + for (ix, branch) in enumerate(lat.branch) + if branch.type != TrackingBranch; continue; end + branch.pdict[:ix_branch] = ix + bookkeeper_tracking_branch!(branch) + end - # Tracking branch bookkeeping - - for (ix, branch) in enumerate(lat.branch) - if branch.type != TrackingBranch; continue; end - branch.pdict[:ix_branch] = ix - bookkeeper_tracking_branch!(branch) - end - - # Check for unbookkeeped parameters - for branch in lat.branch - for ele in branch.ele - for param in keys(ele.pdict[:changed]) - println("WARNING! Unbookkeeped parameter: $(repr(param)) in element $(ele_name(ele)). Please report this!") + # Check for unbookkeeped parameters + for branch in lat.branch + for ele in branch.ele + for param in keys(ele.pdict[:changed]) + println("WARNING! Unbookkeeped parameter: $(repr(param)) in element $(ele_name(ele)). Please report this!") + end end end + + catch this_err + pop_bookkeeping_state!(lat) + rethrow(this_err) end + pop_bookkeeping_state!(lat) return end @@ -67,7 +53,7 @@ Or dependent parameters cannot be directly changed. function check_if_settable(ele::Ele, sym::Symbol, pinfo::Union{ParamInfo, Nothing}) branch = lat_branch(ele) lat = lattice(ele) - if !isnothing(lat) && !lat.record_changes; return; end + if !isnothing(lat) && !lat.auditing_enabled; return; end if !isnothing(lat) if get(ele, :slave_status, 0) == Slave.MULTIPASS || get(ele, :slave_status, 0) == Slave.SUPER @@ -109,7 +95,7 @@ function bookkeeper_tracking_branch!(branch::Branch) elseif ele.slave_status == Slave.SUPER bookkeeper_superslave!(ele, changed, previous_ele) elseif ele.slave_status == Slave.MULTIPASS - bookkeeper_multipassslave!(ele, changed, previous_ele) + bookkeeper_multipass_slave!(ele, changed, previous_ele) else bookkeeper_ele!(ele, changed, previous_ele) end @@ -196,48 +182,24 @@ Internal bookkeeping for a non-`UnionEle` super slave. function bookkeeper_superslave!(slave::Ele, changed::ChangedLedger, previous_ele::Ele) # A non-UnionEle super slave has only one lord lord = slave.super_lords[1] - L_rel = slave.L / lord.L ix_slave = slave_index(slave) + L_rel = slave.L / lord.L - # Bookkeeping of the superlord is only done if the slave is the first superslave of the lord. - # Here the ReferenceGroup of the slave needs to be bookkeeped first. + # Bookkeeping of the super lord is only done if the slave is the first superslave of the lord. if ix_slave == 1 - elegroup_bookkeeper!(slave, ReferenceGroup, changed, previous_ele) bookkeeper_ele!(lord, changed, previous_ele) end # Transfer info from lord to slave - for param in copy(keys(lord.pdict[:changed])) - if typeof(param) != Symbol; continue; end - pinfo = ele_param_info(param) - if isnothing(pinfo); continue; end - group = pinfo.parent_group - if group ∉ keys(ELE_PARAM_GROUP_INFO); continue; end # Ignore custom stuff - + for group in PARAM_GROUPS_LIST[typeof(lord)] if group == LengthGroup; continue; end # Do not modify length of slave + if group == DownstreamReferenceGroup; continue; end + if group == OrientationGroup; continue; end - if group == ReferenceGroup - slave.dE_ref = lord.dE_ref * L_rel - slave.extra_dtime_ref = lord.extra_dtime_ref * L_rel - end - - if group == RFGroup - slave.voltage = lord.voltage * L_rel - end - - if group == BMultipoleGroup - for (ix, mlord) in enumerate(lord.param[:BMultipoleGroup].pole) - if !mlord.integrated; continue; end - mslave = slave.param[:BMultipole].pole[ix] - mslave.Kn = mlord.Kn * L_rel - mslave.Bn = mlord.Bn * L_rel - mslave.Ks = mlord.Ks * L_rel - mslave.Bs = mlord.Bs * L_rel - end - end + if has_changed(lord, group); slave.pdict[Symbol(group)] = copy(group); end if group == EMultipoleGroup - for (ix, elord) in enumerate(lord.param[:EMultipoleGroup].pole) + for (ix, elord) in enumerate(lord.pdict[:EMultipoleGroup].pole) if !elord.integrated; continue; end eslave = slave.param[:EMultipole].pole[ix] eslave.En = elord.En * L_rel @@ -245,51 +207,58 @@ function bookkeeper_superslave!(slave::Ele, changed::ChangedLedger, previous_ele end end - if group == BendGroup - sbend = slave.pdict[:BendGroup] - sbend.angle = lord.angle * L_rel - if ix_slave < length(lord.slaves) - sbend.e2 = 0 - sbend.e2_rect = 0.5 * sbend.angle - elseif ix_slave > 1 - sbend.e1 = 0 - sbend.e1_rect = 0.5 * sbend.angle - end - end - if group == TrackingGroup if lord.num_steps > 0 slave.num_steps = nint(lord.num_steps * L_rel) end end - if group == DownstreamReferenceGroup; continue; end - if group == OrientationGroup; continue; end + # alignment bookkeeping + if has_changed(lord, AlignmentGroup) + dL = 0.5 * slave.L + slave.s - lord.s + + if haskey(lord.pdict, :BendGroup) + # Need transformation from lord alignment point to slave alignment point + # Translate from lord alignment point to beginning of lord point + floor = OrientationGroup(r = [0, 0, -0.5*lord.l_chord]) + # Rotate from z parallel to lord chord to z tangent to bend curve. + if lord.ref_tilt != 0 + q = [] + end + # On the bend curve: From beginning of lord point to beginning of slave point + # From z tangent to bend curve to z parallel to slave chord. + # Translate from beginning of slave point to slave alignment point. + # Apply total transformation of AlignmentGroup. + + else + slave.r_floor = lord.r_floor + dL * rot(lord.q_floor, [0.0, 0.0, dL]) + end + end + slave.pdict[Symbol(group)] = lord.pdict[Symbol(group)] slave.pdict[:changed][group] = "changed" end # Now bookkeep the slave - changed2 = ChangedLedger() - bookkeeper_ele!(slave, changed2, previous_ele) # In case slave parameters have changed. + bookkeeper_ele!(slave, changed, previous_ele) # In case slave parameters have changed. # If last slave of lord, clear lord.changed dict. - if lord.slaves[end] == slave; lord.pdict[:changed] = Dict{Symbol,Any}(); end + if ix_slave == length(lord.slaves); lord.pdict[:changed] = Dict{Symbol,Any}(); end return end #--------------------------------------------------------------------------------------------------- -# bookkeeper_multipassslave!(ele, changed, previous_ele) +# bookkeeper_multipass_slave!(ele, changed, previous_ele) """ - Internal: bookkeeper_multipassslave!(slave::Ele, changed::ChangedLedger, previous_ele::Ele) + Internal: bookkeeper_multipass_slave!(slave::Ele, changed::ChangedLedger, previous_ele::Ele) Internal bookkeeping for multipass slave. -""" bookkeeper_multipassslave! +""" bookkeeper_multipass_slave! -function bookkeeper_multipassslave!(slave::Ele, changed::ChangedLedger, previous_ele::Ele) +function bookkeeper_multipass_slave!(slave::Ele, changed::ChangedLedger, previous_ele::Ele) lord = slave.multipass_lord cdict = lord.changed @@ -399,30 +368,48 @@ function elegroup_bookkeeper!(ele::Ele, group::Type{BMultipoleGroup}, changed::C ff = ele.pc_ref / (C_LIGHT * charge(ele.species_ref)) - for param in keys(cdict) - if typeof(param) == DataType; continue; end - (mtype, order, group) = multipole_type(param) - if isnothing(group) || group != BMultipoleGroup || mtype == "tilt"; continue; end - mul = multipole!(bmg, order) - - if mtype[1:2] == "Kn"; mul.Bn = mul.Kn * ff - elseif mtype[1:2] == "Ks"; mul.Bs = mul.Ks * ff - elseif mtype[1:2] == "Bn"; mul.Kn = mul.Bn / ff - elseif mtype[1:2] == "Bs"; mul.Ks = mul.Bs / ff + if ele.slave_status == Slave.SUPER + lord = ele.super_lords[1] + L_rel = ele.L / lord.L + for (ix, lpole) in enumerate(lord.param[:BMultipoleGroup].pole) + epole = ele.param[:BMultipole].pole[ix] + if lpole.integrated + epole.Kn = lpole.Kn * L_rel + epole.Bn = lpole.Bn * L_rel + epole.Ks = lpole.Ks * L_rel + epole.Bs = lpole.Bs * L_rel + else + ele.param[:BMultipole].pole[ix] = copy(lpole) + end end - end - - # Update multipoles if the reference energy has changed. - if changed.ref_group - if ele.field_master - for mul in bmg.pole - mul.Kn = mul.Bn / ff - mul.Ks = mul.Bs / ff + + # Not a slave case + else + for param in keys(cdict) + if typeof(param) == DataType; continue; end + (mtype, order, group) = multipole_type(param) + if isnothing(group) || group != BMultipoleGroup || mtype == "tilt"; continue; end + mul = multipole!(bmg, order) + + if mtype[1:2] == "Kn"; mul.Bn = mul.Kn * ff + elseif mtype[1:2] == "Ks"; mul.Bs = mul.Ks * ff + elseif mtype[1:2] == "Bn"; mul.Kn = mul.Bn / ff + elseif mtype[1:2] == "Bs"; mul.Ks = mul.Bs / ff end - else - for mul in bmg.pole - mul.Bn = mul.Kn * ff - mul.Bs = mul.Ks * ff + end + + # Update multipoles if the reference energy has changed. + if changed.ref_group + if ele.field_master + for mul in bmg.pole + mul.Kn = mul.Bn / ff + mul.Ks = mul.Bs / ff + end + else + for mul in bmg.pole + mul.Bn = mul.Kn * ff + mul.Bs = mul.Ks * ff + end end end end @@ -441,65 +428,85 @@ function elegroup_bookkeeper!(ele::Ele, group::Type{BendGroup}, changed::Changed if !has_changed(ele, BendGroup) && !changed.this_ele_length && !changed.ref_group; return; end - sym1 = param_conflict_check(ele, :L, :L_chord) - param_conflict_check(ele, :e1, :e1_rect) - param_conflict_check(ele, :e2, :e2_rect) - - if haskey(cdict, :angle) && haskey(cdict, :g) && length(sym1) == 1 - error("Conflict: $(sym1[1]), g, and angle cannot simultaneously be specified for a Bend element $(ele.name)"); end - - if haskey(cdict, :angle) && haskey(cdict, :g) - L = bg.g * bg.angle - elseif haskey(cdict, :angle) && haskey(cdict, :L_chord) - if bg.L_chord == 0 && bg.angle != 0; - error(f"Bend cannot have finite angle and zero length: {ele_name(ele)}"); end - bg.angle == 0 ? bg.g = 0.0 : bg.g = 2.0 * sin(bg.angle/2) / bg.L_chord - L = bg.angle * bg.g - elseif haskey(cdict, :angle) - L = ele.L - if L == 0 && bg.angle != 0; error(f"Bend cannot have finite angle and zero length: {ele_name(ele)}"); end - bg.angle == 0 ? bg.g = 0 : bg.g = bg.angle / L + if ele.slave_status == Slave.SUPER + lord = ele.super_lords[1] + L_rel = ele.L / lord.L + ix_slave = slave_index(slave) + ele.BendGroup = copy(lord.BendGroup) + bg = ele.BendGroup + bg.angle = lord.angle * L_rel + if ix_slave < length(lord.slaves) + bg.e2 = 0 + bg.e2_rect = 0.5 * bg.angle + elseif ix_slave > 1 + bg.e1 = 0 + bg.e1_rect = 0.5 * bg.angle + end + ele.g == 0 ? ele.L_chord = ele.L : ele.L_chord = 2 * sin(ele.angle/2) / ele.g + + # Not a slave else - L = ele.L - bg.angle = L * bg.g - end + sym1 = param_conflict_check(ele, :L, :L_chord) + param_conflict_check(ele, :e1, :e1_rect) + param_conflict_check(ele, :e2, :e2_rect) + + if haskey(cdict, :angle) && haskey(cdict, :g) && length(sym1) == 1 + error("Conflict: $(sym1[1]), g, and angle cannot simultaneously be specified for a Bend element $(ele.name)"); end + + if haskey(cdict, :angle) && haskey(cdict, :g) + L = bg.g * bg.angle + elseif haskey(cdict, :angle) && haskey(cdict, :L_chord) + if bg.L_chord == 0 && bg.angle != 0; + error(f"Bend cannot have finite angle and zero length: {ele_name(ele)}"); end + bg.angle == 0 ? bg.g = 0.0 : bg.g = 2.0 * sin(bg.angle/2) / bg.L_chord + L = bg.angle * bg.g + elseif haskey(cdict, :angle) + L = ele.L + if L == 0 && bg.angle != 0; error(f"Bend cannot have finite angle and zero length: {ele_name(ele)}"); end + bg.angle == 0 ? bg.g = 0 : bg.g = bg.angle / L + else + L = ele.L + bg.angle = L * bg.g + end - bg.bend_field_ref = bg.g * ele.pc_ref / (C_LIGHT * charge(ele.species_ref)) + bg.bend_field_ref = bg.g * ele.pc_ref / (C_LIGHT * charge(ele.species_ref)) - if haskey(cdict, :L_chord) - bg.angle = 2 * asin(bg.L_chord * bg.g / 2) - bg.g == 0 ? bg.L = bg.L_chord : bg.L = bg.angle / bg.g - else - bg.angle = L * bg.g - bg.g == 0 ? bg.L_chord = L : bg.L_chord = 2 * sin(bg.angle/2) / bg.g - end + if haskey(cdict, :L_chord) + bg.angle = 2 * asin(bg.L_chord * bg.g / 2) + bg.g == 0 ? bg.L = bg.L_chord : bg.L = bg.angle / bg.g + else + bg.angle = L * bg.g + bg.g == 0 ? bg.L_chord = L : bg.L_chord = 2 * sin(bg.angle/2) / bg.g + end - if ele.L != L - ele.L = L - elegroup_bookkeeper!(ele, LengthGroup, changed, previous_ele) - end + if ele.L != L + ele.L = L + elegroup_bookkeeper!(ele, LengthGroup, changed, previous_ele) + end - if haskey(cdict, :e1) - bg.e1_rect = bg.e1 - 0.5 * bg.angle - elseif haskey(cdict, :e1_rect) - bg.e1 = bg.e1_rect + 0.5 * bg.angle - elseif bg.bend_type == BendType.SECTOR - bg.e1_rect = bg.e1 + 0.5 * bg.angle - else - bg.e1 = bg.e1_rect - 0.5 * bg.angle - end + if haskey(cdict, :e1) + bg.e1_rect = bg.e1 - 0.5 * bg.angle + elseif haskey(cdict, :e1_rect) + bg.e1 = bg.e1_rect + 0.5 * bg.angle + elseif bg.bend_type == BendType.SECTOR + bg.e1_rect = bg.e1 + 0.5 * bg.angle + else + bg.e1 = bg.e1_rect - 0.5 * bg.angle + end - if haskey(cdict, :e2) - bg.e2_rect = bg.e2 - 0.5 * bg.angle - elseif haskey(cdict, :e2_rect) - bg.e2 = bg.e2_rect + 0.5 * bg.angle - elseif bg.bend_type == BendType.SECTOR - bg.e2_rect = bg.e2 + 0.5 * bg.angle - else - bg.e2 = bg.e2_rect - 0.5 * bg.angle + if haskey(cdict, :e2) + bg.e2_rect = bg.e2 - 0.5 * bg.angle + elseif haskey(cdict, :e2_rect) + bg.e2 = bg.e2_rect + 0.5 * bg.angle + elseif bg.bend_type == BendType.SECTOR + bg.e2_rect = bg.e2 + 0.5 * bg.angle + else + bg.e2 = bg.e2_rect - 0.5 * bg.angle + end + + clear_changed!(ele, BendGroup) end - clear_changed!(ele, BendGroup) return end @@ -535,6 +542,41 @@ function elegroup_bookkeeper!(ele::Ele, group::Type{LengthGroup}, changed::Chang return end +#--------------------------------------------------------------------------------------------------- +# elegroup_bookkeeper!(ele::Ele, group::Type{RFGroup}, ...) + +""" + elegroup_bookkeeper!(ele::Ele, group::Type{RFGroup}, changed::ChangedLedger, previous_ele::Ele) + +`RFGroup` bookkeeping. +""" + +function elegroup_bookkeeper!(ele::Ele, group::Type{RFGroup}, changed::ChangedLedger, previous_ele::Ele) + rg = ele.RFGroup + cdict = ele.changed + + if !has_changed(ele, RFGroup) && !has_changed(ele, ReferenceGroup) && !changed.this_ele_length + return + end + + if ele.slave_status == Slave.SUPER + lord = ele.super_lords[1] + L_rel = ele.L / lord.L + rg.voltage = lord.voltage * L_rel + end + + if ele.field_master + rg.voltage = rg.gradient * ele.L + elseif ele.L == 0 + rg.gradient = NaN + else + rg.gradient = rg.voltage / ele.L + end + + clear_changed!(ele, RFGroup) + return +end + #--------------------------------------------------------------------------------------------------- # elegroup_bookkeeper!(ele::Ele, group::Type{ReferenceGroup}, ...) @@ -551,6 +593,15 @@ function elegroup_bookkeeper!(ele::Ele, group::Type{ReferenceGroup}, changed::Ch if has_changed(ele, ReferenceGroup); changed.ref_group = true; end + if ele.slave_status == Slave.SUPER + lord = ele.super_lords[1] + L_rel = ele.L / lord.L + ele.dE_ref = lord.dE_ref * L_rel + ele.extra_dtime_ref = lord.extra_dtime_ref * L_rel + end + + # + if is_null(previous_ele) # implies BeginningEle if !changed.ref_group; return; end if rg.species_ref == Species(); error(f"Species not set for first element in branch: {ele_name(ele)}"); end @@ -705,6 +756,17 @@ Has any parameter in `group` changed since the last bookkeeping? """ function has_changed(ele::Ele, group::Type{T}) where T <: EleParameterGroup + if ele.slave_status == Slave.SUPER + lord = ele.super_lords[1] # UnionEle slave handled elsewhere. + for param in keys(lord.changed) + if param == AllGroup; return true; end + if param == group; return true; end + info = lord_param_info(param, lord, throw_error = false) + if isnothing(info); continue; end + if info.parent_group == group; return true; end + end + end + for param in keys(ele.changed) if param == AllGroup; return true; end if param == group; return true; end @@ -720,19 +782,20 @@ end # clear_changed! """ - clear_changed!(ele::Ele, group::Type{T}) where T <: EleParameterGroup + clear_changed!(ele::Ele, group::Type{T}; clear_lord::Bool = false) where T <: EleParameterGroup clear_changed!(ele::Ele) Clear record of any parameter in `ele` as having been changed that is associated with `group`. If group is not present, clear all records. -Exception: A superlord or multipasslord is not touched since these lords must retain changed +Exception: A super lord or multipasslord is not touched since these lords must retain changed information until bookkeeping has finished for all slaves. The appropriate lord/slave bookkeeping code will handle this. """ clear_changed! -function clear_changed!(ele::Ele, group::Type{T}) where T <: EleParameterGroup - if ele.lord_status == Lord.SUPER || ele.lord_status == Lord.MULTIPASS; return; end +function clear_changed!(ele::Ele, group::Type{T}; clear_lord::Bool = false) where T <: EleParameterGroup + if !clear_lord && (ele.lord_status == Lord.SUPER || + ele.lord_status == Lord.MULTIPASS); return; end for param in keys(ele.changed) if param == group @@ -861,4 +924,38 @@ function fork_bookkeeper(fork::Ele) end return -end \ No newline at end of file +end + +#--------------------------------------------------------------------------------------------------- +# push_bookkeeping_state + +""" + push_bookkeeping_state!(lat::Lattice; auditing_enabled::Union{Bool,Nothing} = nothing, + autobookkeeping::Union{Bool,Nothing} = nothing) + +Push the current state of `auditing_enabled` and `autobookkeeping` onto a saved state stack and set +these parameters to the corresponding arguments if the arguments are not `nothing`. +""" push_bookkeeping_state! + +function push_bookkeeping_state!(lat::Lattice; auditing_enabled::Union{Bool,Nothing} = nothing, + autobookkeeping::Union{Bool,Nothing} = nothing) + push!(lat.private[:bookkeeping_state], lat.pdict) + if !isnothing(auditing_enabled); lat.pdict[:auditing_enabled] = auditing_enabled; end + if !isnothing(autobookkeeping); lat.pdict[:autobookkeeping] = autobookkeeping; end +end + +#--------------------------------------------------------------------------------------------------- +# pop_bookkeeping_state! + +""" + pop_bookkeeping_state!(lat::Lattice) + +Restore the state of `auditing_enabled` and `autobookkeeping` from the saved state stack. +""" pop_bookkeeping_state! + +function pop_bookkeeping_state!(lat::Lattice) + lat.pdict[:auditing_enabled] = lat.private[:bookkeeping_state][end][:auditing_enabled] + lat.pdict[:autobookkeeping] = lat.private[:bookkeeping_state][end][:autobookkeeping] + pop!(lat.private[:bookkeeping_state]) +end + diff --git a/src/find.jl b/src/find.jl index 984bf13..0989c73 100644 --- a/src/find.jl +++ b/src/find.jl @@ -12,7 +12,7 @@ in the branch containing `reference`. Exceptions: - If the `reference` is a `multipass_lord`, return the `multipass_lord` whose slaves are all `offset` away from the corresponding slaves of `reference`. If no such `mulitpass_lord` exists, throw an error. -- If the `reference` is a `super_lord` element the index of the returned element is `N + offset` where, +- If the `reference` is a super lord element the index of the returned element is `N + offset` where, if `offset` is positive, `N` is the index of the last (that is, downstream) `super_slave` element and, if `index` is negative, `N` is the index of the first (that is, upstream) `super_slave` element. @@ -317,10 +317,10 @@ Notice that here strings to be matched to use single quotes. With the range construct, `atom1` and `atom2` must both evaluate to a single element in the same branch. -- With a range, if `atom1` is a `super_lord` element, For evaluating the range, the first slave of - the `super_lord` will be used for the boundary element of the range. - If `atom2` is a `super_lord` element, - the last slave of the `super_lord` will be used for the boundary element of the range. +- With a range, if `atom1` is a super lord element, For evaluating the range, the first slave of + the super lord will be used for the boundary element of the range. + If `atom2` is a super lord element, + the last slave of the super lord will be used for the boundary element of the range. - To exclude the boundary elements from the returned list, use the appropriate `offset`. - In a range construct the `ele_type` is used to remove elements from the returned list but do not affect matching to the elements at the ends of the range. That is, the elements diff --git a/src/lat_construction.jl b/src/lat_construction.jl index c2292e6..8bfc991 100644 --- a/src/lat_construction.jl +++ b/src/lat_construction.jl @@ -313,11 +313,14 @@ Returns a `Lattice` containing branches for the expanded beamlines and branches Lattice(root_line::BeamLine; name = "lat") = Lattice([root_line], name = name) function Lattice(root_lines::Vector{BeamLine}; name::AbstractString = "lat") - lat = Lattice(name, Branch[], Dict{Symbol,Any}(:LatticeGlobal => LatticeGlobal(), - :record_changes => false, + lat = Lattice(name = name, pdict = Dict{Symbol,Any}( + :LatticeGlobal => LatticeGlobal(), + :auditing_enabled => false, :autobookkeeping => false, - :parameters_have_changed => true - )) + :parameters_have_changed => true), + private = Dict{Symbol,Any}(:bookkeeping_state => Vector{Dict}()) + + ) for root in root_lines new_tracking_branch!(lat, root) @@ -333,7 +336,7 @@ function Lattice(root_lines::Vector{BeamLine}; name::AbstractString = "lat") bookkeeper!(lat) lat_sanity_check(lat) - lat.record_changes = true + lat.auditing_enabled = true lat.autobookkeeping = true return lat end diff --git a/src/manipulation.jl b/src/manipulation.jl index 631cbfd..1aafe05 100644 --- a/src/manipulation.jl +++ b/src/manipulation.jl @@ -299,6 +299,8 @@ function split!(branch::Branch, s_split::Real; select::Select.T = Select.UPSTREA insert!(branch.ele, slave1.ix_ele+1, slave2) # Just after slave1 slave1.L = s_split - slave1.s slave2.L = slave1.s_downstream - s_split + slave1.pdict[:changed][AllGroup] = true + slave2.pdict[:changed][AllGroup] = true # Now update the slave lists for the super lords to include the new slave. # Notice that the lord list of the slaves does not have to be modified. @@ -317,29 +319,30 @@ function split!(branch::Branch, s_split::Real; select::Select.T = Select.UPSTREA return (slave2, true) end - # Split case 3: Element to be split is not a super_slave. Here create a super_lord. - # Important for multipass control that original slave1 is put in super_lord branch + # Split case 3: Element to be split is not a super_slave. Here create a super lord. + # Important for multipass control that original slave1 is put in the super lord branch # and the copies are in the tracking branch. lord = slave1 - slave = copy(slave1) - pop!(slave.pdict, :multipass_lord, nothing) - slave.pdict[:super_lords] = Vector{Ele}([lord]) - slave.slave_status = Slave.SUPER - clear_changed!(slave) - - branch.ele[slave.ix_ele] = slave - slave2 = copy(slave) - insert!(branch.ele, slave.ix_ele+1, slave2) - slave.L = s_split - slave1.s - slave2.L = slave1.s_downstream - s_split - clear_changed!(slave2) - + slave1 = copy(slave1) + pop!(slave1.pdict, :multipass_lord, nothing) + slave1.pdict[:super_lords] = Vector{Ele}([lord]) + slave1.slave_status = Slave.SUPER + + branch.ele[slave1.ix_ele] = slave1 + slave2 = copy(slave1) + insert!(branch.ele, slave1.ix_ele+1, slave2) + slave1.L = s_split - lord.s + slave2.L = lord.s_downstream - s_split + slave1.pdict[:changed][AllGroup] = true + slave2.pdict[:changed][AllGroup] = true + sbranch = branch.lat.branch["super"] push!(sbranch.ele, lord) - lord.pdict[:slaves] = Vector{Ele}([slave, slave2]) + lord.pdict[:slaves] = Vector{Ele}([slave1, slave2]) lord.lord_status = Lord.SUPER + lord.pdict[:changed][AllGroup] = true index_and_s_bookkeeper!(branch) index_and_s_bookkeeper!(sbranch) @@ -385,11 +388,11 @@ end """ Internal: set_super_slave_names!(lord::Ele) -> nothing -`lord` is a super_lord and all of the slaves of this lord will have their name set. +`lord` is a super lord and all of the slaves of this lord will have their name set. """ function set_super_slave_names!(lord::Ele) - if lord.lord_status != Lord.SUPER; error("Argument is not a super_lord: $(ele_name(lord))"); end + if lord.lord_status != Lord.SUPER; error("Argument is not a super lord: $(ele_name(lord))"); end name_dict = Dict{String,Int}() for slave in lord.slaves diff --git a/src/parameters.jl b/src/parameters.jl index a29d7c4..9a16fef 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -82,7 +82,7 @@ ELE_PARAM_INFO_DICT = Dict( :ix_ele => ParamInfo(Nothing, Int, "Index of element in containing branch.ele[] array."), :branch => ParamInfo(Nothing, Branch, "Pointer to branch element is in."), :multipass_lord => ParamInfo(Nothing, Ele, "Element's multipass_lord. Will not be present if no lord exists."), - :super_lords => ParamInfo(Nothing, Vector{Ele}, "Array of element's super_lords. Will not be present if no lords exist."), + :super_lords => ParamInfo(Nothing, Vector{Ele}, "Array of element's super lords. Will not be present if no lords exist."), :slaves => ParamInfo(Nothing, Vector{Ele}, "Array of slaves of element. Will not be present if no slaves exist."), :girder => ParamInfo(Nothing, Ele, "Supporting Girder element. Will not be present if no supporting girder."), @@ -495,7 +495,7 @@ function ele_param_info(who::Union{Symbol,DataType}; throw_error = true) if haskey(ELE_PARAM_INFO_DICT, who); (return ELE_PARAM_INFO_DICT[who]); end # Is a multipole? Otherwise unrecognized. info = multipole_param_info(who) - if isnothing(info) && throw_error; error(f"Unrecognized element parameter: {who}"); end + if isnothing(info) && throw_error; error("Unrecognized element parameter: $who"); end return info end @@ -672,7 +672,7 @@ ELE_PARAM_GROUP_INFO = Dict( OriginEleGroup => EleParameterGroupInfo("Defines coordinate origin for Girder, FloorShift and Fiducial elements.", false), PatchGroup => EleParameterGroupInfo("Patch parameters.", false), ReferenceGroup => EleParameterGroupInfo("Reference energy and species.", true), - RFGroup => EleParameterGroupInfo("`RFCavity` and `LCavity` RF parameters.", false), + RFGroup => EleParameterGroupInfo("`RFCavity` and `LCavity` RF parameters.", true), RFAutoGroup => EleParameterGroupInfo("Contains `auto_amp`, and `auto_phase` related parameters.", false), SolenoidGroup => EleParameterGroupInfo("`Solenoid` parameters.", false), TrackingGroup => EleParameterGroupInfo("Default tracking settings.", false), diff --git a/src/query.jl b/src/query.jl index 2f60fa8..aad6421 100644 --- a/src/query.jl +++ b/src/query.jl @@ -197,6 +197,21 @@ function girder(ele::Ele) return ele.pdict[:girder] end +#--------------------------------------------------------------------------------------------------- +# multipass_lord + +""" + multipass_lord(ele::Ele) -> Union{Vector{Ele}, Nothing} + +Returns the multipass lord of element `ele`. +If no super lords exist for `ele`, `nothing` is returned. +""" multipass_lord + +function multipass_lord(ele::Ele) + if !haskey(ele.pdict, :multipass_lord) return nothing; end + return ele.pdict[:multipass_lord] +end + #--------------------------------------------------------------------------------------------------- # min_ele_length diff --git a/src/struct.jl b/src/struct.jl index fdc6eca..f2983f3 100644 --- a/src/struct.jl +++ b/src/struct.jl @@ -1160,24 +1160,46 @@ Lattice structure. ## Fields -• `name::String`. \\ -• `branch::Vector{Branch}`. \\ -• `pdict::Dict{Symbol,Any}`. \\ +• `name::String` Name of lattice. \\ +• `branch::Vector{Branch}` Array of branches. \\ +• `pdict::Dict{Symbol,Any}` Lattice parameter dictionaries. \\ +• `private::Dict{Symbol, Any}` Private storage space. \\ ## Standard pdict keys -• `:record_changes` - Bool: Record parameter changes? \\ -• `:autobookkeeping` - Bool: Automatic bookkeeping enabled? \\ -• `parameters_have_changed` - Bool: Have any parameters changed since the last bookkeeping? \\ +• `:auditing_enabled` - Bool: parameter changes monitored? \\ +• `:autobookkeeping` - Bool: Automatic bookkeeping enabled? \\ +• `:parameters_have_changed` - Bool: Have any parameters changed since the last bookkeeping? \\ -The `:record_changes` is usually `true` but can be set `false` by bookkeeping routines that -want to make parameter changes without leaving a record. Also if `:record_changes` is `false`, -changes to parameters that normally should not be changed are allowed. This enables bookkeeping -code to modify, for example, dependent parameters. +The `:auditing_enabled` is usually `true` but can be set `false` by bookkeeping routines that +want to make parameter changes without ele.pdict[:changed] being added to. +Also if `:auditing_enabled` is `false`, changes to parameters that normally should not be changed are allowed. +This enables bookkeeping code to modify, for example, dependent parameters. """ -mutable struct Lattice <: AbstractLattice - name::String - branch::Vector{Branch} - pdict::Dict{Symbol,Any} +@kwdef mutable struct Lattice <: AbstractLattice + name::String = "" + branch::Vector{Branch} = Vector{Branch}() + pdict::Dict{Symbol,Any} = Dict{Symbol,Any}() + private::Dict{Symbol,Any} = Dict{Symbol,Any}() end +#--------------------------------------------------------------------------------------------------- +# ChangedLedger + +""" + Internal: mutable struct ChangedLedger + +When bookkeeping a branch, element-by-element, starting from the beginning of the branch, +the ledger keeps track of what has changed so that the change can propagate to the +following elements. + +Ledger parameters, when toggled to true, will never be reset for the remainder of the branch bookkeeping. +The exception is the `this_ele_length` parameter which is reset for each element. +""" ChangedLedger + +@kwdef mutable struct ChangedLedger + this_ele_length::Bool = false + s_position::Bool = false + ref_group::Bool = false + floor_position::Bool = false +end diff --git a/src/superimpose.jl b/src/superimpose.jl index 46ccd9a..b2fa7f0 100644 --- a/src/superimpose.jl +++ b/src/superimpose.jl @@ -44,7 +44,7 @@ downstream edge after the branch start edge. If `wrap` = false, extend the latti - Zero length elements in the superposition region will be left alone. - Superimposing an element on top of a non-`Drift` element produces a `UnionEle` `super_slave` -with appropriate `super_lord` elements. +with appropriate super lord elements. - The superposition location is determined with respect to the `local` coordinates of the `ref`. Thus, a positive `offset` will displace the superposition location downstream if `ref` has @@ -74,304 +74,222 @@ function superimpose!(super_ele::Ele, ref::T; ele_origin::BodyLoc.T = BodyLoc.CE super_list = [] lat_list = [] - for this_ref in collect(ref_ele) - if typeof(this_ref) == Branch - ref_ele = this_ref.ele[1] - else - ref_ele = this_ref - end - - # Get insertion branch - if !haskey(ref_ele.pdict, :branch) - error("Reference element: $(ref_ele.name) does is not part of a lattice.") - end - branch = ref_ele.branch - if branch.type <: LordBranch - branch = ref_ele.slaves[1].branch - ref_ix_ele = ref_ele.slaves[1].ix_ele - else - ref_ix_ele = ref_ele.ix_ele - end - - lat = branch.lat - if lat ∉ lat_list - push!(lat_list, lat) - lat.pdict[:old_autobookkeeping] = lat.autobookkeeping - lat.autobookkeeping = false - lat.record_changes = false - end - - L_super = super_ele.L - offset = offset * ref_ele.orientation - # Convert from body entrance/exit to up/dowstream - machine_ref_origin = machine_location(ref_origin, ref_ele.orientation) - machine_ele_origin = machine_location(ele_origin, ref_ele.orientation) - - # Insertion of zero length element with zero offset at edge of an element. - if L_super == 0 && offset == 0 - if machine_ref_origin == Loc.UPSTREAM_END || (machine_ref_origin == Loc.CENTER && ref_ele.L == 0) - ix_insert = max(ref_ix_ele, 2) - push!(super_list, insert!(branch, ix_insert, super_ele)) - continue - elseif machine_ref_origin == Loc.DOWNSTREAM_END - ix_insert = min(ref_ix_ele+1, length(ref_ele.branch.ele)) - push!(super_list, insert!(branch, ix_insert, super_ele)) - continue + try + for this_ref in collect(ref_ele) + if typeof(this_ref) == Branch + ref_ele = this_ref.ele[1] + else + ref_ele = this_ref end - end - # Super_ele end locations: s1 and s2. - if branch.type <: LordBranch - if machine_ref_origin == Loc.UPSTREAM_END; s1 = ref_ele.slaves[1].s - elseif machine_ref_origin == Loc.CENTER; s1 = 0.5 * (ref_ele.slaves[1].s + ref_ele.slaves[end].s_downstream) - else; s1 = ref_ele.slaves[end].s_downstream - end - else # Not a lord branch - if machine_ref_origin == Loc.UPSTREAM_END; s1 = ref_ele.s - elseif machine_ref_origin == Loc.CENTER; s1 = 0.5 * (ref_ele.s + ref_ele.s_downstream) - else; s1 = ref_ele.s_downstream + # Get insertion branch + if !haskey(ref_ele.pdict, :branch) + error("Reference element: $(ref_ele.name) does is not part of a lattice.") end - end - - if machine_ele_origin == Loc.UPSTREAM_END; s1 = s1 + offset - elseif machine_ele_origin == Loc.CENTER; s1 = s1 + offset - 0.5 * L_super - else; s1 = s1 + offset - L_super - end - - s2 = s1 + L_super - - # If super_ele has zero length just insert it. - if L_super == 0 - if machine_ref_origin == Loc.DOWNSTREAM_END - ele_at, _ = split!(branch, s1, select = Select.DOWNSTREAM, ele_near = ref_ele) + branch = ref_ele.branch + if branch.type <: LordBranch + branch = ref_ele.slaves[1].branch + ref_ix_ele = ref_ele.slaves[1].ix_ele else - ele_at, _ = split!(branch, s1, select = Select.UPSTREAM, ele_near = ref_ele) + ref_ix_ele = ref_ele.ix_ele end - push!(super_list, insert!(branch, ele_at.ix_ele, super_ele)) - continue - end - - # Below is for a super_ele that has a nonzero length - branch_len = branch.ele[end].s_downstream - branch.ele[1].s - if s1 < branch.ele[1].s - if wrap - s1 = s1 + branch_len - else - @ele drift = Drift(L = branch.ele[1].s - s1) - insert!(branch, 2, drift) + lat = branch.lat + if lat ∉ lat_list + push!(lat_list, lat) + push_bookkeeping_state!(lat, autobookkeeping = false, auditing_enabled = false) end - end - if s2 > branch.ele[end].s_downstream - if wrap - s2 = s2 - branch_len - else - @ele drift = Drift(L = s2 - branch.ele[end].s_downstream) - insert!(branch.ele, length(branch.ele), drift) + L_super = super_ele.L + offset = offset * ref_ele.orientation + # Convert from body entrance/exit to up/dowstream + machine_ref_origin = machine_location(ref_origin, ref_ele.orientation) + machine_ele_origin = machine_location(ele_origin, ref_ele.orientation) + + # Insertion of zero length element with zero offset at edge of an element. + if L_super == 0 && offset == 0 + if machine_ref_origin == Loc.UPSTREAM_END || (machine_ref_origin == Loc.CENTER && ref_ele.L == 0) + ix_insert = max(ref_ix_ele, 2) + push!(super_list, insert!(branch, ix_insert, super_ele)) + continue + elseif machine_ref_origin == Loc.DOWNSTREAM_END + ix_insert = min(ref_ix_ele+1, length(ref_ele.branch.ele)) + push!(super_list, insert!(branch, ix_insert, super_ele)) + continue + end end - end - # Split points are chosen to avoid creating elements with non-zero length below the minimum. - # And super_lord length will be adjusted accordingly. - ele1 = ele_at_s(branch, s1, select = Select.UPSTREAM, ele_near = ref_ele) - ele2 = ele_at_s(branch, s2, select = Select.DOWNSTREAM, ele_near = ref_ele) - - min_len = min_ele_length(branch.lat) - - if abs(ele1.s - s1) < min_len - s1 = ele1.s - super_ele.L = super_ele.L + s1 - ele1.s - elseif abs(ele1.s_downstream - s1) < min_len - s1 = ele1.s_downstream - super_ele.L = super_ele.L + s1 - ele1.s_downstream - end - - if abs(ele2.s - s2) < min_len - s2 = ele2.s - super_ele.L = super_ele.L + ele2.s - s2 - elseif abs(ele2.s_downstream - s2) < min_len - s2 = ele2.s_downstream - super_ele.L = super_ele.L + ele2.s_downstream - s2 - end - - # `select` is set to minimize number of elements in the superposition region. - # The superposition region is from beginning of ele1 to the beginning of ele2. - - ele2, _ = split!(branch, s2, select = Select.UPSTREAM) # Notice that s2 split must be done first! - ele1, _ = split!(branch, s1, select = Select.DOWNSTREAM) - - # If there are just drifts here then no superimpose needed. - # Note: In this case there cannot be wrap around. + # Super_ele end locations: s1 and s2. + if branch.type <: LordBranch + if machine_ref_origin == Loc.UPSTREAM_END; s1 = ref_ele.slaves[1].s + elseif machine_ref_origin == Loc.CENTER; s1 = 0.5 * (ref_ele.slaves[1].s + ref_ele.slaves[end].s_downstream) + else; s1 = ref_ele.slaves[end].s_downstream + end + else # Not a lord branch + if machine_ref_origin == Loc.UPSTREAM_END; s1 = ref_ele.s + elseif machine_ref_origin == Loc.CENTER; s1 = 0.5 * (ref_ele.s + ref_ele.s_downstream) + else; s1 = ref_ele.s_downstream + end + end - all_drift = true - n_ele = 0 - for ele in Region(ele1, ele2, false) - n_ele += 1 - if typeof(ele) != Drift; all_drift = false; end - end + if machine_ele_origin == Loc.UPSTREAM_END; s1 = s1 + offset + elseif machine_ele_origin == Loc.CENTER; s1 = s1 + offset - 0.5 * L_super + else; s1 = s1 + offset - L_super + end - # + s2 = s1 + L_super - if all_drift - ix_super = ele1.ix_ele - super_ele = set!(branch, ix_super, super_ele) - push!(super_list, super_ele) - if n_ele > 1; deleatat!(branch.ele, ix_super+1:ix_super+n_ele-1); end - if typeof(branch.ele[ix_super-1]) == Drift; set_drift_slice_names(branch.ele[ix_super-1]); end - if typeof(branch.ele[ix_super+1]) == Drift; set_drift_slice_names(branch.ele[ix_super+1]); end - continue - end - - # Here if a super_lord element needs to be constructed. - lord_list = [] - sbranch = branch.lat.branch["super"] - - lord1 = push!(sbranch, super_ele) - lord1.lord_status = Lord.SUPER - lord1.pdict[:slaves] = Ele[] - push!(lord_list, lord1) - push!(super_list, lord1) - - for ele in Region(ele1, ele2, false) - if ele.L == 0; continue; end - ix_ele = ele.ix_ele - - if typeof(ele) == Drift - ele2 = set!(branch, ix_ele, super_ele) - ele2.slave_status = Slave.SUPER - ele2.L = ele.L - ele2.pdict[:super_lords] = Vector{Ele}([lord1]) - push!(lord1.slaves, ele2) - set_drift_slice_names(ele) - - elseif ele.slave_status != Slave.SUPER - lord2 = push!(sbranch, ele) - lord2.lord_status = Lord.SUPER - push!(lord_list, lord2) - - slave = set!(branch, ix_ele, UnionEle(name = "", L = ele.L, super_lords = Vector{Ele}([lord1]))) - slave.slave_status = Slave.SUPER - lord2.pdict[:slaves] = Vector{Ele}([slave]) - push!(slave.pdict[:super_lords], lord2) - push!(lord1.pdict[:slaves], slave) - - else # Is super_slave and not Drift - if typeof(ele) != UnionEle # That is, has a single super_lord - set!(branch, ix_ele, UnionEle(name = "", L = ele.L, super_lords = ele.super_lords)) - old_lord = ele.super_lords[1] - for (ix, slave) in enumerate(old_lord.slaves) - if slave === ele; old_lord.slaves[ix] = branch.ele[ix_ele]; end - end + # If super_ele has zero length just insert it. + if L_super == 0 + if machine_ref_origin == Loc.DOWNSTREAM_END + ele_at, _ = split!(branch, s1, select = Select.DOWNSTREAM, ele_near = ref_ele) + else + ele_at, _ = split!(branch, s1, select = Select.UPSTREAM, ele_near = ref_ele) end - - slave = branch.ele[ix_ele] - push!(slave.pdict[:super_lords], lord1) - push!(lord1.pdict[:slaves], slave) + push!(super_list, insert!(branch, ele_at.ix_ele, super_ele)) + continue end - end - index_and_s_bookkeeper!(branch) + # Below is for a super_ele that has a nonzero length + branch_len = branch.ele[end].s_downstream - branch.ele[1].s - for lord in lord_list - set_super_slave_names!(lord) - if length(lord.slaves) == 1; continue; end - initial_superlord_bookkeeping(lord) - end - - end # for this_ref in collect(ref) + if s1 < branch.ele[1].s + if wrap + s1 = s1 + branch_len + else + @ele drift = Drift(L = branch.ele[1].s - s1) + insert!(branch, 2, drift) + end + end - for lat in lat_list - lat.autobookkeeping = lat.old_autobookkeeping - pop!(lat.pdict, :old_autobookkeeping) - lat.record_changes = true - if lat.autobookkeeping; bookkeeper!(lat); end - end + if s2 > branch.ele[end].s_downstream + if wrap + s2 = s2 - branch_len + else + @ele drift = Drift(L = s2 - branch.ele[end].s_downstream) + insert!(branch.ele, length(branch.ele), drift) + end + end - return super_list -end + # Split points are chosen to avoid creating elements with non-zero length below the minimum. + # And super lord length will be adjusted accordingly. + ele1 = ele_at_s(branch, s1, select = Select.UPSTREAM, ele_near = ref_ele) + ele2 = ele_at_s(branch, s2, select = Select.DOWNSTREAM, ele_near = ref_ele) -#--------------------------------------------------------------------------------------------------- -# initial_superlord_bookkeeping + min_len = min_ele_length(branch.lat) -""" - Internal: initial_superlord_bookkeeping(lord::Ele) + if abs(ele1.s - s1) < min_len + s1 = ele1.s + super_ele.L = super_ele.L + s1 - ele1.s + elseif abs(ele1.s_downstream - s1) < min_len + s1 = ele1.s_downstream + super_ele.L = super_ele.L + s1 - ele1.s_downstream + end -Internal routine for the initial bookkeeping of a newly formed superlord element. -The only parameters that need bookkeeping are ones that are not the same in the lord and the slave. -""" initial_superlord_bookkeeping + if abs(ele2.s - s2) < min_len + s2 = ele2.s + super_ele.L = super_ele.L + ele2.s - s2 + elseif abs(ele2.s_downstream - s2) < min_len + s2 = ele2.s_downstream + super_ele.L = super_ele.L + ele2.s_downstream - s2 + end -function initial_superlord_bookkeeping(lord::Ele) - if lord.L == 0; return; end + # `select` is set to minimize number of elements in the superposition region. + # The superposition region is from beginning of ele1 to the beginning of ele2. - for (ixs, slave) in enumerate(lord.slaves) - # UnionEle elements don't have any parameters to be bookkeeped - if typeof(slave) == UnionEle; continue; end - L_rel = slave.L / lord.L + ele2, _ = split!(branch, s2, select = Select.UPSTREAM) # Notice that s2 split must be done first! + ele1, _ = split!(branch, s1, select = Select.DOWNSTREAM) - # All non-UnionEle slaves will only have one lord. - # dE_ref bookkeeping - if lord.dE_ref != 0 - slave.dE_ref = lord.dE_ref * L_rel - end + # If there are just drifts here then no superimpose needed. + # Note: In this case there cannot be wrap around. - # Multipole with length bookkeeping - if haskey(lord.pdict, :BMultipoleGroup) - for (ix, pole) in enumerate(lord.BMultipoleGroup.pole) - if !pole.integrated; continue; end - slave.BMultipoleGroup[ix].Kn = pole.Kn * L_rel - slave.BMultipoleGroup[ix].Ks = pole.Ks * L_rel - slave.BMultipoleGroup[ix].Bn = pole.Bn * L_rel - slave.BMultipoleGroup[ix].Bs = pole.Bs * L_rel + all_drift = true + n_ele = 0 + for ele in Region(ele1, ele2, false) + n_ele += 1 + if typeof(ele) != Drift; all_drift = false; end end - end - if haskey(lord.pdict, :EMultipoleGroup) - for (ix, pole) in enumerate(lord.EMultipoleGroup.pole) - if !pole.integrated; continue; end - slave.EMultipoleGroup[ix].En = pole.En * L_rel - slave.EMultipoleGroup[ix].Es = pole.Bs * L_rel + # + + if all_drift + ix_super = ele1.ix_ele + super_ele = set!(branch, ix_super, super_ele) + push!(super_list, super_ele) + if n_ele > 1; deleatat!(branch.ele, ix_super+1:ix_super+n_ele-1); end + if typeof(branch.ele[ix_super-1]) == Drift; set_drift_slice_names(branch.ele[ix_super-1]); end + if typeof(branch.ele[ix_super+1]) == Drift; set_drift_slice_names(branch.ele[ix_super+1]); end + continue end - end - # Bend bookkeeping - if haskey(lord.pdict, :BendGroup) - slave.angle = lord.angle * L_rel + # Here if a super lord element needs to be constructed. + lord_list = [] + sbranch = branch.lat.branch["super"] + + lord1 = push!(sbranch, super_ele) + lord1.lord_status = Lord.SUPER + lord1.pdict[:slaves] = Ele[] + push!(lord_list, lord1) + push!(super_list, lord1) + + for ele in Region(ele1, ele2, false) + if ele.L == 0; continue; end + ix_ele = ele.ix_ele + + if typeof(ele) == Drift + ele2 = set!(branch, ix_ele, super_ele) + ele2.slave_status = Slave.SUPER + ele2.L = ele.L + ele2.pdict[:super_lords] = Vector{Ele}([lord1]) + push!(lord1.slaves, ele2) + set_drift_slice_names(ele) + + elseif ele.slave_status != Slave.SUPER + lord2 = push!(sbranch, ele) + lord2.lord_status = Lord.SUPER + push!(lord_list, lord2) + + slave = set!(branch, ix_ele, UnionEle(name = "", L = ele.L, super_lords = Vector{Ele}([lord1]))) + slave.slave_status = Slave.SUPER + lord2.pdict[:slaves] = Vector{Ele}([slave]) + push!(slave.pdict[:super_lords], lord2) + push!(lord1.pdict[:slaves], slave) + + else # Is super_slave and not Drift + if typeof(ele) != UnionEle # That is, has a single super_lord + set!(branch, ix_ele, UnionEle(name = "", L = ele.L, super_lords = ele.super_lords)) + old_lord = ele.super_lords[1] + for (ix, slave) in enumerate(old_lord.slaves) + if slave === ele; old_lord.slaves[ix] = branch.ele[ix_ele]; end + end + end - if ixs < length(lord.slaves) - slave.e2 = 0 - slave.e2_rect = 0.5 * slave.angle + slave = branch.ele[ix_ele] + push!(slave.pdict[:super_lords], lord1) + push!(lord1.pdict[:slaves], slave) + end end - if ixs > 1 - slave.e1 = 0 - slave.e1_rect = 0.5 * slave.angle + index_and_s_bookkeeper!(branch) + + for lord in lord_list + set_super_slave_names!(lord) end + end # for this_ref in collect(ref) - ele.g == 0 ? ele.L_chord = L : ele.L_chord = 2 * sin(ele.angle/2) / ele.g + catch this_err + for lat in lat_list + pop_bookkeeping_state!(lat) end + rethrow(this_err) + end - # Misalignment bookkeeping - if haskey(lord.pdict, :AlignmentGroup) - dL = 0.5 * slave.L + slave.s - lord.s - - if haskey(lord.pdict, :BendGroup) - # Need transformation from lord alignment point to slave alignment point - # Translate from lord alignment point to beginning of lord point - floor = OrientationGroup(r = [0, 0, -0.5*lord.l_chord]) - # Rotate from z parallel to lord chord to z tangent to bend curve. - if lord.ref_tilt != 0 - q = [] - end - # On the bend curve: From beginning of lord point to beginning of slave point - # From z tangent to bend curve to z parallel to slave chord. - # Translate from beginning of slave point to slave alignment point. - # Apply total transformation of AlignmentGroup. + # End stuff - else - slave.r_floor = lord.r_floor + dL * rot(lord.q_floor, [0.0, 0.0, dL]) - end - end + for lat in lat_list + pop_bookkeeping_state!(lat) + if lat.autobookkeeping; bookkeeper!(lat); end end -end + return super_list +end diff --git a/src/traversal.jl b/src/traversal.jl index 8dfc44a..0ad0cd6 100644 --- a/src/traversal.jl +++ b/src/traversal.jl @@ -20,8 +20,8 @@ To include `end_ele` in the iteration range, use the construct: for ele in Region(start_ele, end_ele, false) ``` -It is permissible for `start_ele` and/or `end_ele` to be a `super_lord`. In any case, the -iteration region will never include any `super_lord` elements. but rather their corresponding `super_slave`s. +It is permissible for `start_ele` and/or `end_ele` to be a super lord. In any case, the +iteration region will never include any super lord elements. but rather their corresponding `super_slave`s. """ Region @kwdef mutable struct Region @@ -48,8 +48,8 @@ function Base.iterate(x::Region) x.start_ele.lord_status == Lord.SUPER ? start_ele = x.start_ele.slaves[1] : start_ele = x.start_ele x.end_ele.lord_status == Lord.SUPER ? end_ele = x.end_ele.slaves[1] : end_ele = x.end_ele if !(start_ele.branch === end_ele.branch); error("Start and end elements are not in the same branch"); end - if start_ele.lord_status != Lord.NOT; error("Start element may not be a non-super_lord lord."); end - if end_ele.lord_status != Lord.NOT; error("End element may not be a non-super_lord lord."); end + if start_ele.lord_status == Lord.MULTIPASS; error("Start element may not be a multipass lord."); end + if end_ele.lord_status == Lord.MULTIPASS; error("End element may not be a multipass lord."); end return (start_ele, start_ele) end diff --git a/test/superimpose_test.jl b/test/superimpose_test.jl index 544a2fd..ce7b2a5 100644 --- a/test/superimpose_test.jl +++ b/test/superimpose_test.jl @@ -25,11 +25,11 @@ lat = Lattice([ln1]) sup_zs2 = superimpose!(zs2, eles_search(lat, "d1"), offset = 0.25) sup_m1 = superimpose!(m1, lat.branch[1], offset = 0, ref_origin = BodyLoc.ENTRANCE_END) -sup_sm1 = superimpose!(zm1, eles_search(lat, "m1"), ref_origin = BodyLoc.ENTRANCE_END); -sup_zm4 = superimpose!(zm4, eles_search(lat, "lc1"), offset = 0.2); +sup_sm1 = superimpose!(zm1, eles_search(lat, "m1"), ref_origin = BodyLoc.ENTRANCE_END) +sup_zm4 = superimpose!(zm4, eles_search(lat, "lc1"), offset = 0.2) sup_m2 = superimpose!(m2, lat.branch[1], offset = 2.7) -sup_zm2 = superimpose!(zm2, eles_search(lat, "m1"), ref_origin = BodyLoc.CENTER); -sup_zm3 = superimpose!(zm3, eles_search(lat, "m1"), ref_origin = BodyLoc.EXIT_END); +sup_zm2 = superimpose!(zm2, eles_search(lat, "m1"), ref_origin = BodyLoc.CENTER) +sup_zm3 = superimpose!(zm3, eles_search(lat, "m1"), ref_origin = BodyLoc.EXIT_END) b1 = lat.branch[1] b2 = lat.branch[2]