diff --git a/manual/coordinates.tex b/manual/coordinates.tex index 494b09e..f036934 100644 --- a/manual/coordinates.tex +++ b/manual/coordinates.tex @@ -317,17 +317,17 @@ \section{Global Coordinates} % \item[$\theta(s)$ Azimuth (yaw) angle:] Angle in the $(X, Z)$ plane between the $Z$--axis and the projection of the $z$--axis onto the $(X, -Z)$ plane. Corresponds to the \vn{y_rot} element attribute (\sref{s:offset}). A positive angle of +Z)$ plane. Corresponds to the \vn{y_rot} element parameter (\sref{s:offset}). A positive angle of $\theta = \pi/2$ corresponds to the projected $z$--axis pointing in the negative $X$-direction. % \item[$\phi(s)$ Pitch (elevation) angle:] -Angle between the $z$--axis and the $(X,Z)$ plane. Corresponds to the \vn{x_rot} element attribute +Angle between the $z$--axis and the $(X,Z)$ plane. Corresponds to the \vn{x_rot} element parameter (\sref{s:offset}). A positive angle of $\phi = \pi/2$ corresponds to the $z$--axis pointing in the positive $Y$ direction. % \item[$\psi(s)$ Roll angle:] Angle of the $x$--axis with respect to the line formed by the intersection of the $(X, Z)$ plane -with the $(x, y)$ plane. Corresponds to the \vn{tilt} element attribute (\sref{s:offset}). A +with the $(x, y)$ plane. Corresponds to the \vn{tilt} element parameter (\sref{s:offset}). A positive $\psi$ forms a right--handed screw with the $z$--axis. \end{description} @@ -714,8 +714,8 @@ \subsection{Bend Element Misalignment Transformation} \index{rbend!coordinate transformation}\index{sbend!coordinate transformation} \index{roll!coordinate transformation}\index{tilt!coordinate transformation} -For \vn{rbend} and \vn{sbend} elements there is no \vn{tilt} attribute. Rather, there is the -\vn{roll} attribute and a \vn{ref_tilt} attribute. The latter affects both the reference orbit and +For \vn{rbend} and \vn{sbend} elements there is no \vn{tilt} parameter. Rather, there is the +\vn{roll} parameter and a \vn{ref_tilt} parameter. The latter affects both the reference orbit and the bend position (\sref{s:bend.orient}). Furthermore, \vn{ref_tilt} is calculated with respect to the coordinates at the beginning of the bend while, like straight elements, \vn{roll}, \vn{x_rot}, \vn{y_rot}, and offsets are calculated with respect to the center of the bend. The different reference frame used @@ -821,7 +821,7 @@ \subsection{Reference Particle, Reference Energy, and Reference Time} is equal to the reference energy. The energy of the particle at the downstream end is the reference energy of the element. Note: Tracking through an element to determine the reference energy is always done with the element turned on independent of the setting of the element's \vn{is_on} -(\sref{s:is.on}) parameter. Reference energy tracking is also done ignoring any orientation attributes +(\sref{s:is.on}) parameter. Reference energy tracking is also done ignoring any orientation parameters (\sref{s:offset}) and errors like \vn{voltage_err}. \index{wiggler!reference time} diff --git a/manual/multipass.tex b/manual/multipass.tex index c3606cf..94b5af8 100644 --- a/manual/multipass.tex +++ b/manual/multipass.tex @@ -48,11 +48,11 @@ \section{Multipass Fundamentals} These types of elements are known as \vn{multipass_slave} elements. In addition to the \vn{multipass_slave} elements there will be a \vn{multipass_lord} element (that doesn't get tracked through) called \vn{RF1} in the \vn{multipass_lord} branch of the lattice (\sref{s:lord.slave}). -Changes to the attributes of the lord \vn{RF1} element will be passed to the slave elements by the \accellat +Changes to the parameters of the lord \vn{RF1} element will be passed to the slave elements by the \accellat bookkeeping routines. Assuming that the phase of \vn{RF1!mp1} gives acceleration, to make \vn{RF1!mp2} -decelerate, the \vn{multipass_phase} attribute of \vn{RF1!mp2} is set to pi. This is the one attribute -that \accellat's bookkeeping routines will not touch when transferring attribute values from \vn{RF1} to -its slaves. Notice that the \vn{multipass_phase} attribute had to be set after the lattice is formed +decelerate, the \vn{multipass_phase} parameter of \vn{RF1!mp2} is set to pi. This is the one parameter +that \accellat's bookkeeping routines will not touch when transferring parameter values from \vn{RF1} to +its slaves. Notice that the \vn{multipass_phase} parameter had to be set after the lattice is formed using the \vn{expand} function (\sref{s:expand}). This is true since \vn{RF1!mp2} does not exist before the lattice is expanded. \vn{multipass_phase} is useful with relative time tracking \sref{s:rf.time}. However, \vn{multipass_phase} is ``unphysical'' and is just diff --git a/manual/superimpose.tex b/manual/superimpose.tex index e7008ac..05a644a 100644 --- a/manual/superimpose.tex +++ b/manual/superimpose.tex @@ -103,7 +103,7 @@ \section{Superposition on a Drift} superimpose!(ss, ref_ele, offset = 0.2) \end{example} -attribute of element \vn{S} superimposes \vn{S} over the lattice \vn{(Q, +parameter of element \vn{S} superimposes \vn{S} over the lattice \vn{(Q, D)}. The placement of \vn{S} is such that the beginning of \vn{S} is coincident with the center of \vn{Q} (this is is explained in more detail below). Additionally, a marker \vn{M} is superimposed at a distance of +1~meter from the center of \vn{S}. The tracking part of the lattice @@ -188,7 +188,7 @@ \section{Superposition on a Drift} The placement of a superimposed element is illustrated in \fig{f:superimpose}. The placement of a superimposed element is determined by three factors: An origin point on the superimposed element, an -origin point on the reference element, and an offset between the points. The attributes that +origin point on the reference element, and an offset between the points. The parameters that determine these three quantities are: \index{ref}\index{offset} \index{ref_origin}\index{ele_origin} @@ -237,7 +237,7 @@ \section{Superposition on a Drift} The element begin superimposed may be any type of element except \vn{drift}, \vn{group}, \vn{overlay}, and \vn{girder} control elements. The reference element used to position a superimposed element may be a \vn{group} or \vn{overlay} element as long as the \vn{group} or -\vn{overlay} controls the attributes of exactly one element. In this case, the controlled element is +\vn{overlay} controls the parameters of exactly one element. In this case, the controlled element is used as the reference element. \index{geometry}\index{open} @@ -338,10 +338,10 @@ \section{Superposition on a Drift} my_oct: octupole, ..., superimpose, ref = a_drft##2 ! This is an error \end{example} -When the attributes of a super_slave are computed from the attributes of its super_lords, some types -of attributes may be ``missing''. For example, it is, in general, not possible to set appropriate -aperture attributes (\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 attributes stored in +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 the lord elements to correctly calculate things. When superposition is done in a line where there is \vn{element reversal} (\sref{s:ele.reverse}), @@ -434,8 +434,8 @@ \section{Jumbo Super_Slaves} \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 -attributes of each lord will be set to show the position of the lord within the slave. These two -attributes are +parameters of each lord will be set to show the position of the lord within the slave. These two +parameters are \begin{example} lord_pad1 ! offset at upstream end lord_pad2 ! offset at downstream end @@ -464,7 +464,7 @@ \section{Jumbo Super_Slaves} \index{field_overlaps} Another possible way to handle overlapping fields is to use the \vn{field_overlaps} element -attribute as discussed in \sref{s:overlap}. +parameter as discussed in \sref{s:overlap}. %----------------------------------------------------------------------------- \section{Changing Element Lengths when there is Superposition} diff --git a/src/core.jl b/src/core.jl index 36f5d9b..6d4a1fb 100644 --- a/src/core.jl +++ b/src/core.jl @@ -1,25 +1,3 @@ -#--------------------------------------------------------------------------------------------------- -# holly_type - -""" - holly_type(atype::AbstractString, ctypes::Vector) - -Makes an abstract type from the first word and makes concrete types that inherit from the abstract type -from the other words in the string. -""" holly_type - -function holly_type(atype::AbstractString, ctypes::Vector) - eval( Meta.parse("abstract type $atype end") ) - eval( Meta.parse("export $atype") ) - - for ct in ctypes - eval( Meta.parse("struct $ct <: $atype; end") ) - end -end - -holly_type("EleGeometrySwitch", ["Straight", "Circular", "ZeroLength", - "PatchGeom", "GirderGeom", "CrystalGeom", "MirrorGeom"]) - #--------------------------------------------------------------------------------------------------- # Exceptions @@ -39,49 +17,3 @@ eval_str(str::AbstractString) = eval(Meta.parse(str)) field_names(x) = fieldnames(typeof(x)) -#--------------------------------------------------------------------------------------------------- -# it_ismutable & it_isimmutable - -""" - function it_ismutable(x) - -Work around for the problem that ismutable returns True for strings. -See: https://github.com/JuliaLang/julia/issues/30210 -""" it_ismutable - -function it_ismutable(x) - if typeof(x) <: AbstractString; return false; end - return ismutable(x) -end - -""" - function it_isimmutable(x) - -Work around for the problem that isimmutable returns True for strings. -See: https://github.com/JuliaLang/julia/issues/30210 -""" it_isimmutable - -function it_isimmutable(x) - if typeof(x) <: AbstractString; return true; end - return isimmutable(x) -end - -#--------------------------------------------------------------------------------------------------- - -function integer(str::AbstractString, default::Number) - try - ix = parse(Int, str) - return ix - catch - return default - end -end - -function float(str::AbstractString, default::Number) - try - flt = parse(Float, str) - return ix - catch - return default - end -end \ No newline at end of file diff --git a/src/enum.jl b/src/enum.jl index 81649dd..1e75577 100644 --- a/src/enum.jl +++ b/src/enum.jl @@ -12,18 +12,17 @@ return `OPEN` and `CLOSED`. macro enumit(str::AbstractString) eval( Meta.parse("@enumx $str") ) - str_split = split(str) - str2 = join(str_split, ',') + str_words = split(str) + str2 = join(str_words, ',') eval( Meta.parse("export $str2") ) - s = "Base.string(z::$(str_split[1]).T) = \"$(str_split[1]).\" * string(Symbol(z))" - if str_split[1] != "BranchGeometry"; eval( Meta.parse(s) ); end + s = "Base.string(z::$(str_words[1]).T) = \"$(str_words[1]).\" * string(Symbol(z))" + if str_words[1] != "BranchGeometry"; eval( Meta.parse(s) ); end end @enumit("ApertureShape RECTANGULAR ELLIPTICAL") @enumit("BendType SECTOR RECTANGULAR") @enumit("BodyLoc ENTRANCE_END CENTER EXIT_END BOTH_ENDS NOWHERE EVERYWHERE") @enumit("BranchGeometry OPEN CLOSED") -## @enumit("EleGeometry STRAIGHT CIRCULAR ZEROLENGTH PATCH GIRDER CRYSTAL MIRROR") # See core.jl @enumit("Cavity STANDING_WAVE TRAVELING_WAVE") @enumit("SlaveControl DELTA ABSOLUTE NOT_SET") @enumit("FieldCalc MAP STANDARD") @@ -42,3 +41,25 @@ CLOSED::BranchGeometry.T = BranchGeometry.CLOSED OPEN::BranchGeometry.T = BranchGeometry.OPEN +#--------------------------------------------------------------------------------------------------- +# holly_type + +""" + holly_type(atype::AbstractString, ctypes::Vector) + +Makes an abstract type from the first word and makes concrete types that inherit from the abstract type +from the other words in the string. +""" holly_type + +function holly_type(atype::AbstractString, ctypes::Vector) + eval( Meta.parse("abstract type $atype end") ) + eval( Meta.parse("export $atype") ) + + for ct in ctypes + eval( Meta.parse("struct $ct <: $atype; end") ) + end +end + +holly_type("EleGeometrySwitch", ["Straight", "Circular", "ZeroLength", + "PatchGeom", "GirderGeom", "CrystalGeom", "MirrorGeom"]) + diff --git a/src/find.jl b/src/find.jl index 2c67d12..844e191 100644 --- a/src/find.jl +++ b/src/find.jl @@ -79,202 +79,229 @@ function ele_at_offset(reference::Union{Ele,Branch}, offset::Int; wrap::Bool = t end #--------------------------------------------------------------------------------------------------- -# eles_base +# eles_atomic """ - Internal: function eles_base(where_search::Union{Lat,Branch}, who::Union{AbstractString,Regex}; + Internal: function eles_atomic(where_search, who::Union{AbstractString,Regex}; wrap::Bool = true) -> ele_vector::Ele[] -Internal. Called by `eles` function. -""" eles_base +Internal function. Called by `eles` function. +`who` is an "atomic" search string. See the documentation in `eles` for more details. +""" eles_atomic -function eles_base(where_search::Union{Lat,Branch}, who::Union{AbstractString,Regex}; wrap::Bool = true) +function eles_atomic(where_search::Union{Lat,Branch}, who::Union{AbstractString,Regex}; wrap::Bool = true) julia_regex = (typeof(who) == Regex) - who = string(who) - typeof(where_search) == Lat ? branch_vec = where_search.branch : branch_vec = [where_search] - eles = Ele[] - if !julia_regex; who = replace(who, "'" => "\""); end + if typeof(where_search) == Lat + branch_vec = where_search.branch + else + branch_vec = collect(where_search) + end - branch_id = ""; offset = 0 - nth_match = -1 + branch_id = "" + ele_type = nothing + param_id = :name + match_str = "" + offset = 0 + nth_instance = -1 - if occursin(">>", who); branch_id, who = split(who, ">>"); end + # Parse who - # attrib->pattern construct - if occursin("->", who) - attrib, pattern = split(who, "->") - attrib = Meta.parse(attrib) # Makes attrib a Symbol + if julia_regex + match_str = who - words = str_split(pattern, "+-") - if length(words) == 2 || length(words) > 3; error(f"ParseError: Bad lattice element name: {who}"); end - pattern = str_unquote(words[1]) - if length(words) == 3; offset = parse(Int, words[2]*words[3]); end + else + this_who = replace(who, "'" => "\'") + chunks = str_split(this_who, [">>", "::", "#", "+", "-", "=", "`", " "]) - for branch in branch_vec - if !matches_branch(branch, branch_id); continue; end - for ele in branch.ele - if !haskey(ele.pdict, attrib); continue; end - if julia_regex - if occursin(pattern, ele.pdict[attrib]); push!(eles, ele_at_offset(ele, offset, wrap)); end - else - if str_match(pattern, ele.pdict[attrib]); push!(eles, ele_at_offset(ele, offset, wrap)); end - end - end + if length(chunks) > 2 && chunks[2] == ">>" + branch_id = chunks[1] + chunks = chunks[3:end] end - # ele_id or key::ele_id construct - else - key = nothing - if occursin("::", who) - key, who = split(who, "::") - key = Symbol(key) + if length(chunks) > 2 && chunks[2] == "::" + ele_type = Symbol(chunks[1]) + chunks = chunks[3:end] + end + + if length(chunks) > 4 && chunks[2] == "=" + if chunks[3] != "`" || chunks[5] != "`" error("Malformed match string for reference element(s): $who"); end + param_id = Symbol(chunks[1]) + match_str = chunks[4] + chunks = chunks[6:end] + else + match_str = chunks[1] + chunks = chunks[2:end] end - ix_ele = -1 - if !julia_regex - words = str_split(who, "+-#", doubleup = true) # EG: ["Marker::*", "-", "1"] + if length(chunks) > 0 && chunks[1] == "#" + if length(chunks) == 1; error("Malformed match string for reference elements(s): $who"); end + nth_instance = integer(chunks[2]) + chunks = chunks[3:end] + end - if length(words) > 2 && occursin(words[end-1], "+-") - offset = parse(Int, words[end-1]*words[end]) - words = words[1:end-2] - end + if length(chunks) > 0 && (chunks[1] == "-" || chunks[1] == "+") + if length(chunks) == 1; error("Malformed match string for reference elements(s): $who"); end + offset = integer(chunks[1] * chunks[2]) + chunks = chunks[3:end] + end + end - if length(words) > 2 && words[end-1] == "#" - nth_match = parse(Int, words[end]) - words = words[1:end-2] - end + # Search for elements - if length(words) != 1; error(f"ParseError: Bad lattice element name: {who}"); end - ele_id = words[1] - ix_ele = str_to_int(ele_id, -1) - if ix_ele != -1 && nth_match != -1; return eles; end + eles = Ele[] + ix_ele = integer(match_str, -1) + + if ix_ele != -1 && param_id == :name + if nth_instance != -1; error("Specifying element index and using `#N` construct not allowed in: $who"); end + for branch in branch_vec + if !matches_branch(branch_id, branch); continue; end + push!(eles, ele_at_offset(branch.ele[ix_ele], offset, wrap)) end + # + + else for branch in branch_vec if !matches_branch(branch_id, branch); continue; end - if ix_ele != -1 - push!(eles, ele_at_offset(branch, branch.ele[ix_ele], offset, wrap)) - continue - end + if !julia_regex && branch_id == "" && branch.type == SuperLordBranch; continue; end ix_match = 0 for ele in branch.ele if julia_regex - if match(ele_id, ele.who); push!(eles, ele); end + if match(match_str, ele.name); push!(eles, ele); end else - if !isnothing(key) && Symbol(typeof(ele)) != key; continue; end - if !str_match(ele_id, ele.name); continue; end + # Match to super_lord elements in case `#N` construct has been used and the testing order of the + # element is important. To not double count, only check lords where ele is the first super_slave. + if haskey(ele.pdict, :super_lords) + for lord in ele.pdict[:super_lords] + if !(lord.slaves[1] === ele); continue; end + if !haskey(lord.pdict, param_id); continue; end + if !isnothing(ele_type) && Symbol(typeof(lord)) != ele_type; continue; end + if !str_match(match_str, lord.pdict[param_id]); continue; end + ix_match += 1 + if nth_instance != -1 && ix_match > nth_instance; continue; end + if ix_match == nth_instance || nth_instance == -1; push!(eles, ele_at_offset(lord, offset, wrap)); end + end + end + + if !haskey(ele.pdict, param_id); continue; end + if !isnothing(ele_type) && Symbol(typeof(ele)) != ele_type; continue; end + if !str_match(match_str, ele.pdict[param_id]); continue; end ix_match += 1 - if nth_match != -1 && ix_match > nth_match; continue; end - if ix_match == nth_match || nth_match == -1; push!(eles, ele_at_offset(ele, offset, wrap)); end + if nth_instance != -1 && ix_match > nth_instance; continue; end + if ix_match == nth_instance || nth_instance == -1; push!(eles, ele_at_offset(ele, offset, wrap)); end end end - end # branch loop - - return eles + end end + + return eles end #--------------------------------------------------------------------------------------------------- # eles """ - function eles(where_search::Union{Lat,Branch}, who::Union{AbstractString,Regex}; wrap::Bool = True) - -> ele_vector::Ele[] + function eles(where_search, who::Union{AbstractString,Regex}; wrap::Bool = True) -> Ele[] Returns a vector of all elements that match `who`. -There are two types of `who`. -On type uses a Julia `Regex` expression to match to element names. For example: -``` - who = r"q\\d" # Matches "qN" where "N" is a digit 0-9. -``` -See the Julia regex documentation for more information. +## Arguments +- `where_search` Where to search. Either a lattice (all branches searched), lattice branch, or + vector of lattice branches. +- `who` The string to match elements to. + Either a Julia `Regex` expression to match to element names or a string with matching + governed by the "AcceleratorLattice" (AL) regex syntax (see below). +- `wrap` Used if there is an `offset` specified in the search string (see below). + +## AL Regex -The other types of matches are those using the "AcceleratorLattice" (AL) regex syntax. -This syntax has wild card characters “*” and “%”. -The “*” character will match any number of characters (including zero) while “%” maches to any single character. +The "AcceleratorLattice" (AL) regex syntax has wild card characters `“*”` and `“%”`. +The `“*”` character will match any number of characters (including zero) while +`“%”` maches to any single character. -All AL regex expressions are built up from "atomic" expressions. Atomic expressions are of -one of two forms: One atomic form is: +All AL regex expressions are built up from "atomic" expressions of +the form: ``` - {branch_id>>}{ele_class::}ele_id{#N}{+/-offset}` + {branch_id>>}{ele_class::}ele_id{#N}{+/-offset} ``` Curly brackets `{...}` denote optional fields. - `branch_id` Optional lattice branch index or name. Alternative is to specify the branch - using the `where_search` argument. + using the `where_search` argument. A branch is searched if it matches both `where_search` + and `branch_id`. - `ele_class` Optional element class (EG: `Quadrupole`). -- `ele_id` Element name with or element index. The element name can contain wild card characters. +- `ele_id` Element name, index, or `parameter='match_str'` construct. + The element name can contain wild card characters. - `#N` If present, return only the Nth instance matched to. - `+/-offset` If present, return element(s) whose index is offset from the elements matched to. -Examples: +Atomic expressions may be combined using the operators `","` (union), `"~"` (negation) or `"&"` (intersection): +If there are more than two atomic expressions involved, evaluation is left to right. For example: ``` - eles(lat, "d") All elements named "d" - eles(lat, "Marker::*") - eles(lat, "Marker::*-1") - eles(lat, "m1#2") - eles(lat, "m1#2+1") + ", " # Union of and . + ", & " # Intersection of with the union of and . + " ~" # All elements in that are not in . ``` +## Notes -or - `{branch_id>>}attribute->'match_str'{+/-offset}` -where `attribute` is something like `alias`, `description`, `type`, or any custom field. - - key selection EG: "Quadrupole::" - ranges EG: ":" - negation EG: " ~" - intersection EG: " & " -Note: negation and intersection evaluated left to right - -## Input - -- `where_search` `Lat` or `Branch` to search. -- `who` `String` or `Regex` to use in the search. +- The `parameter='match_str'` construct allows for matching to element parameters other than the element name. + Typically used with the standard element "string parameters" `alias`, `type`, and `description` + but matching is not limited to these parameters. +- If `ele_id` is an integer (element index), Specifying `#N` is not permitted. -## Notes: - -- Element order is not guaranteed. Use +## Examples +``` + eles(lat, "r>>d") # All elements named "d" in the branch with name "r". + eles(lat, "Marker::*") # All Marker elements + eles(lat, "Marker::%5-1") # All elements just before Marker elements with two character names + # ending in the digit "5" + eles(lat, "1>>m1#2") # Second element named "m1" in branch 1. + eles(lat.branch[1], "m1#2") # Equivalent to eles(lat, "1>>m1#2"). + eles(lat, "alias=`abc`") +``` """ function eles(where_search::Union{Lat,Branch}, who::Union{AbstractString,Regex}; wrap::Bool = true) # Julia regex is simple - if typeof(who) == Regex; return eles_base(where_search, who); end + if typeof(who) == Regex; return eles_atomic(where_search, who); end - # Intersection - list = str_split(who, "~&", limit = 3) - if length(list) == 2 || list[1] == "~" || list[1] == "&" || - (length(list) == 3 && (list[3] == "~" || list[3] == "&")) - error(f"ParseError: Cannot parse: {who}") - end + # Not Julia regex + list = str_split(who, "~&,") + + eles1 = eles_atomic(where_search, list[1]) + list = list[2:end] - eles1 = eles_base(where_search, list[1]) - if length(list) == 1; return eles1; end + while true + if length(list) == 0; return eles1; end + if length(list) == 1; error("Bad `who` argument: $who"); end - eles2 = eles(lat, list[3]) - eles = [] + eles2 = eles(where_search, list[2], wrap = wrap) - if list[2] == "&" - for ele1 in eles1 - for ele2 in eles2 - if ele1 === ele2; push!(eles, ele1); continue; end + if list[1] == "&" + ele_list = [] + for ele1 in eles1 + if ele1 in eles2; push!(ele_list, ele1); continue; end end - end + eles1 = ele_list - elseif list[2] == "~" - for ele1 in eles1 - found = false - for ele2 in eles2 - if ele1 === ele2; found = true; continue; end + elseif list[1] == "~" + ele_list = [] + for ele1 in eles1 + if ele1 ∉ eles2; push!(ele_list, ele1); continue; end end - if !found; push!(eles, ele1); continue; end + eles1 = ele_list + + elseif list[1] == "," + eles1 = append!(eles1, eles2) + + else + error("ParseError: Cannot parse: $who") end - else - error(f"ParseError: Cannot parse: {who}") + + list = list[3:end] end - return eles end #--------------------------------------------------------------------------------------------------- diff --git a/src/init_bookkeeper.jl b/src/init_bookkeeper.jl index 2217412..04aad24 100644 --- a/src/init_bookkeeper.jl +++ b/src/init_bookkeeper.jl @@ -73,7 +73,7 @@ function init_ele_bookkeeper!(ele::Controller) # Put controls in place pdict[:control] = pop!(pdict[:inbox], :control) for ctl in pdict[:control] - loc = Vector{LatEleLocation}() + loc = LatEleLocation[] for ele_id in ctl.eles if typeof(ele_id) == LatEleLocation push!(loc, ele_id) @@ -128,7 +128,7 @@ function init_multipass_bookkeeper!(lat::Lat) delete!(lord.pdict, :multipass_id) lord.pdict[:branch] = multipass_branch lord.pdict[:ix_ele] = length(multipass_branch.ele) - lord.pdict[:slaves] = Vector{Ele}() + lord.pdict[:slaves] = Ele[] for (ix, ele) in enumerate(val) ele.name = ele.name * "!mp" * string(ix) ele.pdict[:multipass_lord] = lord diff --git a/src/lat_construction.jl b/src/lat_construction.jl index 741fd7d..7843af6 100644 --- a/src/lat_construction.jl +++ b/src/lat_construction.jl @@ -246,7 +246,7 @@ function new_tracking_branch!(lat::Lat, bline::Union{BeamLine, Tuple}) bline = beamline(bline[2].name, [bline[1], bline[2]], geometry = bline[2].pdict[:geometry]) end - push!(lat.branch, Branch(bline.name, Vector{Ele}(), Dict{Symbol,Any}(:geometry => bline.pdict[:geometry]))) + push!(lat.branch, Branch(bline.name, Ele[], Dict{Symbol,Any}(:geometry => bline.pdict[:geometry]))) branch = lat.branch[end] branch.pdict[:lat] = lat branch.pdict[:ix_branch] = length(lat.branch) @@ -274,7 +274,7 @@ function new_tracking_branch!(lat::Lat, bline::Union{BeamLine, Tuple}) end function new_lord_branch!(lat::Lat, name::AbstractString, branch_type::Type{T}) where T <: BranchType - push!(lat.branch, Branch(name, Vector{Ele}(), Dict{Symbol,Any}())) + push!(lat.branch, Branch(name, Ele[], Dict{Symbol,Any}())) branch = lat.branch[end] branch.pdict[:lat] = lat branch.pdict[:ix_branch] = length(lat.branch) @@ -305,7 +305,7 @@ Returns a `Lat` containing branches for the expanded beamlines and branches for """ expand function expand(name::AbstractString, root_line::Union{BeamLine,Vector}) - lat = Lat(name, Vector{Branch}(), Dict{Symbol,Any}(:LatticeGlobal => LatticeGlobal())) + lat = Lat(name, Branch[], Dict{Symbol,Any}(:LatticeGlobal => LatticeGlobal())) if root_line isa BeamLine new_tracking_branch!(lat, root_line) diff --git a/src/manipulation.jl b/src/manipulation.jl index 6a7e978..54f8629 100644 --- a/src/manipulation.jl +++ b/src/manipulation.jl @@ -68,7 +68,7 @@ All elements with indexes of `ix_ele` and higher are pushed one element down the Inserted is a (shallow) copy of `ele` and this copy is returned. - `adjust_orientation` If `true`, and the `branch.type` is a `TrackingBranch`, the orientation -attribute of `ele` is adjusted to match the neighboring elements. +parameter of `ele` is adjusted to match the neighboring elements. """ Base.insert!(branch::Branch, ix_ele::Int, ele::Ele) diff --git a/src/query.jl b/src/query.jl index d363286..4345912 100644 --- a/src/query.jl +++ b/src/query.jl @@ -135,10 +135,10 @@ end """ matches_branch(name::AbstractString, branch::Branch) -Returns `true`/`false` if `name` matches/does not match `branch`. +Tests if `name` matches/does not match `branch`. A match can match branch.name or the branch index. A blank name matches all branches. -Bmad standard wildcard characters "*" and "%" can be used. +Bmad standard wildcard characters `"*"` and `"%"` can be used. """ matches_branch function matches_branch(name::AbstractString, branch::Branch) @@ -163,4 +163,31 @@ This is used by, for example, the `split!` function which will not create "runt" whose length is below min_ele_length. The returned value is `2 * lat.LatticeGlobal.significant_length` """ min_ele_length -min_ele_length(lat::Lat) = 2 * lat.LatticeGlobal.significant_length \ No newline at end of file +min_ele_length(lat::Lat) = 2 * lat.LatticeGlobal.significant_length + +#--------------------------------------------------------------------------------------------------- +# it_ismutable & it_isimmutable + +""" + function it_ismutable(x) + +Work around for the problem that ismutable returns True for strings. +See: https://github.com/JuliaLang/julia/issues/30210 +""" it_ismutable + +function it_ismutable(x) + if typeof(x) <: AbstractString; return false; end + return ismutable(x) +end + +""" + function it_isimmutable(x) + +Work around for the problem that isimmutable returns True for strings. +See: https://github.com/JuliaLang/julia/issues/30210 +""" it_isimmutable + +function it_isimmutable(x) + if typeof(x) <: AbstractString; return true; end + return isimmutable(x) +end diff --git a/src/string.jl b/src/string.jl index 85ed624..cc27f96 100644 --- a/src/string.jl +++ b/src/string.jl @@ -2,98 +2,160 @@ # str_split """ - str_split(str::AbstractString, delims::AbstractString; doubleup=false, group_quotation=true, limit::Integer=0) + str_split(str::AbstractString, delims::AbstractString; skip_quoted=true, limit::Integer=0) + str_split(str::AbstractString, delims::Vector{T}; + skip_quoted=true, limit::Integer=0) where T <: AbstractString Function to split a string and return a vector of words. -Included in the vector are elements of the delimiters. +Unlike the standard `split` function, the output vector with `str_split` will includ delimiter terms. + +## Arguments + +- `delims` This is either a vector of strings with each element being a delimiter or + a string where each character is taken to be a delimiter. Notice that with the vector form, + any given delimiter may contain multiple characters. + In terms of matching, If there is ambiguity, the first delim in the list that is a match is used. + For example, if `delims` is `["<<", "<"]`, the output with `str` = `"a<b>>c", ">", doubleup = true) -will return - ["a", ">", "b", ">>", "c"] + # - • limit: the maximum size of the result. limit=0 implies no maximum (default) -""" str_split + if limit == 1; return [str]; end + if limit < 0; error("`limit` argument cannot be negative: $limit"); end + + has_blank_delim = (" " in delims) + if has_blank_delim; str = strip(str); end -function str_split(str::AbstractString, delims::AbstractString; doubleup=false, group_quotation=true, limit::Integer=0) words = [] - this_word = "" + ix1_word = 1 # Start index of current word quote_mark = ' ' # Blank means not in a quote block - at_end = false - - for s in str - if at_end - this_word = this_word * s - - elseif group_quotation && quote_mark == ' ' && (s == '"' || s == '\'') - if this_word != ""; push!(words, this_word); end - if limit > 0 && size(words,1) == limit-1; at_end = true; continue; end - this_word = s - quote_mark = s - - elseif quote_mark != ' ' # Can only happen if group_quotation = true - this_word = this_word * s - if s == quote_mark - push!(words, this_word) - this_word = "" - quote_mark = ' ' - if limit > 0 && size(words,1) == limit-1; at_end = true; end - end + n_skip = 0 # Characters to skip for a multi-character delim. + n_str = length(str) - elseif s in delims - if doubleup && this_word == "" && s != ' ' && size(words, 1) > 0 && words[end] == string(s) - words[end] = s * s + # - elseif s == ' ' && size(words, 1) > 0 && words[end] == " " - continue + for (indx, c) in enumerate(str) + + if n_skip > 0 + n_skip -= 1 + continue + end - else - if this_word != "" - push!(words, this_word) - this_word = "" + + + # + + if skip_quoted + if c == '"' || c == '\'' || c == '`' + if quote_mark == ' ' + quote_mark = c + elseif c == quote_mark + quote_mark = ' ' end + end + end + + # + + if has_blank_delim && c == ' ' && quote_mark == ' ' && length(words) > 0 && words[end] == " " && str[ix1_word] == ' ' + ix1_word += 1 + + elseif !skip_quoted || quote_mark == ' ' || quote_mark == c + for delim in delims + if c != delim[1]; continue; end + + n = length(delim) + if n > 1 && (indx+n-1 > n_str || delim[2:end] != str[indx+1:indx+n-1]) continue; end + + push!(words, this_word(str[ix1_word:indx-1], has_blank_delim)) - if limit > 0 && size(words,1) == limit-1 - at_end = true - this_word = s - else - push!(words, string(s)) + if limit > 0 && length(words) == limit - 1 + push!(words, this_word(str[indx:end], has_blank_delim)) + return words end - end - else - this_word = this_word * s + ix1_word = indx + n + push!(words, delim) + n_skip = n - 1 + + if limit > 0 && length(words) == limit - 1 + push!(words, this_word(str[ix1_word:end], has_blank_delim)) + return words + end + + if indx+n-1 == n_str + push!(words, "") + return words + end + + break + end end + end - if this_word != ""; push!(words, this_word); end - if quote_mark != ' '; error(f"ParseError: Unbalanced quotation marks in: {str}"); end + if ix1_word <= n_str && (limit == 0 || length(words) < limit) + push!(words, this_word(str[ix1_word:n_str], has_blank_delim)) + end return words end +# + +function str_split(str::AbstractString, delims::AbstractString; skip_quoted=true, limit::Integer=0) + str_split(str, [string(z) for z in delims], skip_quoted = skip_quoted, limit = limit) +end + #--------------------------------------------------------------------------------------------------- # str_match """ - str_match(pattern::AbstractString, who::AbstractString) + str_match(pattern::AbstractString, who::AbstractString)::Bool Function to match a string against a regular expression using the Bmad standard wild cards. -The whole string is matched to by inserting "^" and "\$" at the ends of the search pattern. +The whole string is matched to by inserting `"^"` and `"\$"` at the ends of the search pattern. -Wild card characters are: - "*" -- Match to any number of characters. - "%" -- Match to any single character. +## Wild card characters are: +- `"*"` -- Match to any number of characters. +- `"%"` -- Match to any single character. -To use literal "*" and "%" in a string +To search for a literal `"*"` or `"%"` in a string prefix in `pattern` using a double backslash `"\\\\"`. Output: true/false """ str_match @@ -138,15 +200,16 @@ Returns string with end double-quote characters added str_quote(str::AbstractString) = '"' * str * '"' #--------------------------------------------------------------------------------------------------- -# str_to_int +# integer """ - str_to_int(str::AbstractString, default = nothing) + integer(str::AbstractString, default = nothing) + integer(str::AbstractString) -Converts a string to an integer -""" str_to_int +Converts a string to an integer. Throws an error if `default` is `nothing`. +""" integer -function str_to_int(str::AbstractString, default = nothing) +function integer(str::AbstractString, default) try return parse(Int, str) catch @@ -156,4 +219,8 @@ function str_to_int(str::AbstractString, default = nothing) return default end end -end \ No newline at end of file +end + +# + +integer(str::AbstractString) = integer(str, nothing) diff --git a/src/struct.jl b/src/struct.jl index e877abe..0a647e4 100644 --- a/src/struct.jl +++ b/src/struct.jl @@ -888,7 +888,7 @@ The constant NULL_BRANCH is defined as a placeholder for signaling the absense o The test is_null(branch) will test if a branch is a NULL_BRANCH. """ NULL_BRANCH -const NULL_BRANCH = Branch("NULL_BRANCH", Vector{Ele}(), Dict{Symbol,Any}(:ix_branch => -1)) +const NULL_BRANCH = Branch("NULL_BRANCH", Ele[], Dict{Symbol,Any}(:ix_branch => -1)) #--------------------------------------------------------------------------------------------------- # Branch types diff --git a/src/superimpose.jl b/src/superimpose.jl index 67e420f..828c65c 100644 --- a/src/superimpose.jl +++ b/src/superimpose.jl @@ -207,7 +207,7 @@ function superimpose!(super_ele::Ele, ref::T; ele_origin::BodyLoc.T = BodyLoc.CE lord1 = copy(super_ele) push!(sbranch.ele, lord1) lord1.lord_status = Lord.SUPER - lord1.pdict[:slaves] = Vector{Ele}() + lord1.pdict[:slaves] = Ele[] index_and_s_bookkeeper!(sbranch) for ele in Region(ele1, ele2, false) diff --git a/src/traversal.jl b/src/traversal.jl index 9b1650f..01339e4 100644 --- a/src/traversal.jl +++ b/src/traversal.jl @@ -78,11 +78,9 @@ function Base.iterate(x::Region, ele::Ele) end #--------------------------------------------------------------------------------------------------- -# Base.iterate(::BeamLine) +# collect(::Branch) -""" - Base.iterate( [, state]) -> Union{Nothing, Tuple{Any, Any}} -""" Base.iterate +Base.collect(b::Branch) = [b] #--------------------------------------------------------------------------------------------------- # next_ele diff --git a/test/find_test.jl b/test/find_test.jl index 0e4985a..2278b13 100644 --- a/test/find_test.jl +++ b/test/find_test.jl @@ -23,7 +23,6 @@ lat = expand("mylat", [(beginning, fodo), (beginning, ln2), ln3]); superimpose!(z2, lat.branch[3], offset = 0.1, ele_origin = BodyLoc.ENTRANCE_END) superimpose!(z1, eles(lat, "1>>d#1"), offset = 0.1) -eles(lat, "z1+1") #--------------------------------------------------------------------------------------------------- # Notice element d2 has a negative length @@ -42,23 +41,23 @@ b = lat.branch[1] end @testset "eles" begin - @test [e.ix_ele for e in eles(lat, "d")] == [8, 20, 2] - @test [e.ix_ele for e in eles(lat, "Marker::*")] == [10, 11, 23, 5, 5, 8] - @test [e.ix_ele for e in eles(lat, "Marker::*-1")] == [9, 10, 22, 4, 4, 7] - @test [e.ix_ele for e in eles(lat, "m1#2")] == [11] - @test [e.ix_ele for e in eles(lat, "m1#2+1")] == [12] - @test [e.ix_ele for e in eles(lat.branch[5], "d")] == [2] - @test [(e.branch.ix_branch, e.ix_ele) for e in eles(lat, "multipass_lord>>d")] == [(5, 2)] - @test [e.ix_ele for e in eles(lat, "%d")] == [22, 1, 3] -# @test [e.ix_ele for e in ] == [] -# @test [e.ix_ele for e in ] == [] -# @test [e.ix_ele for e in ] == [] -# @test [e.ix_ele for e in ] == [] -# @test [e.ix_ele for e in ] == [] -# @test [e.ix_ele for e in ] == [] -# @test [e.ix_ele for e in ] == [] -# @test [e.ix_ele for e in ] == [] -# @test [e.ix_ele for e in ] == [] -# @test [e.ix_ele for e in ] == [] -# @test [e.ix_ele for e in ] == [] + @test eles(lat, "d") == eles(lat, "fodo>>8, fodo>>20, multipass_lord>>2") + @test eles(lat, "Marker::*") == eles(lat, "fodo>>10, fodo>>11, fodo>>23, ln2>>5, ln3>>5, ln3>>8") + @test eles(lat, "Marker::*-1") == eles(lat, "fodo>>9, fodo>>10, fodo>>22, ln2>>4, ln3>>4, ln3>>7") + @test eles(lat, "m1#2") == eles(lat, "fodo>>11") + @test eles(lat, "m1#2+1") == eles(lat, "fodo>>12") + @test eles(lat.branch[5], "d") == eles(lat, "multipass_lord>>2") + @test eles(lat, "multipass_lord>>d") == eles(lat, "multipass_lord>>2") + @test eles(lat, "%d") == eles(lat, "fodo>>22, multipass_lord>>1, multipass_lord>>3") + @test eles(lat, "z1-1") == eles(lat, "1>>4") + @test eles(lat, "z1+1") == eles(lat, "fodo>>6") + @test eles(lat, "z2-1") == eles(lat, "ln3>>2") end + +#function toe(vec) +# str = "eles(lat, \"" +# for ele in vec +# str *= ele_name(ele, "!#") * ", " +# end +# print(str[1:end-2] * "\")") +#end; diff --git a/test/runtests.jl b/test/runtests.jl index 4e2717d..bfd35f4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,25 +1,23 @@ using AcceleratorLattice, Test -@testset "AcceleratorLattice" begin - t0 = time() +@testset verbose = true "AcceleratorLattice" begin + + @testset "string_test" begin + include("string_test.jl") + end @testset "find_test" begin - println("##### Testing find_test.jl...") - t = @elapsed include("find_test.jl") - println("##### done (took $t seconds).") + include("find_test.jl") end # @testset "lat_construction_test" begin -# println("##### Testing lat_construction_test.jl...") -# t = @elapsed include("lat_construction_test.jl") -# println("##### done (took $t seconds).") +# include("lat_construction_test.jl") # end # @testset "superimpose_test" begin -# println("##### Testing superimpose_test.jl...") -# t = @elapsed include("superimpose_test.jl") -# println("##### done (took $t seconds).") +# include("superimpose_test.jl") # end - println("##### Running all AcceleratorLattice tests took $(time() - t0) seconds.") -end +end; + +print("") # To surpress trailing garbage output \ No newline at end of file diff --git a/test/string_test.jl b/test/string_test.jl new file mode 100644 index 0000000..f1056ad --- /dev/null +++ b/test/string_test.jl @@ -0,0 +1,14 @@ +using AcceleratorLattice, Test + + +@testset "str_split" begin + @test str_split("a,b,, ", [",,"]) == ["a,b", ",,", " "] + @test str_split("a,b,, ", [",,"], limit = 2) == ["a,b", ",, "] + @test str_split("a,b,, ", [",,"], limit = 3) == ["a,b", ",,", " "] + @test str_split(",a,', 'b,,", [","]) == ["", ",", "a", ",", "', 'b", ",", "", ",", ""] + @test str_split("a, b, c", ",", limit = 3) == ["a", ",", " b, c"] + @test str_split(" a b", " ") == ["a", " ", "b"] + @test str_split("<< a< < b<