Optical systems
A collection of optical elements forms an optical system. Optical systems are used together with beams for the solve_system! function. Depending on the system type, different solver implementations can be activated. Currently the following types are available:
└── BeamletOptics.AbstractSystem
├── StaticSystem
└── System
At least 2 types have been found.Refer to the Tutorials section for examples on how to define optical systems.
BeamletOptics.System Type
System <: AbstractSystemA container storing the optical elements of, i.e. a camera lens or lab setup.
Fields
objects: vector containing the different objects (subtypes ofAbstractObject) and object groups (subtypes ofAbstractObjectGroup) that are part of the system. The constructor copies the vector it is given, such thatpush!anddelete!of the system do not change it.
BeamletOptics.StaticSystem Type
StaticSystem <: AbstractSystemA static container storing the optical elements of, i.e. a camera lens or lab setup. Compared to System this way defining the system is less flexible, i.e. no elements can be added or removed after construction but it allows for more performant ray-tracing.
Warning
This type uses long tuples for storing the elements. This container should not be used for very large optical systems as it puts a lot of stress onto the compiler.
Fields
objects: vector containing the different objects that are part of the system (subtypes ofAbstractObject)
Changing a system
The objects of a System can be changed after its construction, e.g. to build up a setup step by step, starting from an empty system:
system = System()
push!(system, lens, mirror) # objects or object groups
delete!(system, mirror) # removes this object, compared by identity
pop!(system) # removes the last top-level object and returns it
popat!(system, 1) # removes the first top-level object and returns itA StaticSystem can not be changed.
Base.push! Method
push!(system::System, objects::ObjectOrGroup...) -> systemAdds the objects (or object groups) at the top level of the system, such that they are traced by the following calls of solve_system!. An object that is already part of the system, directly or within a group, throws an ArgumentError; in this case nothing is added.
Beams and beam groups that were solved before do not know the new object: solve them again with solve_system!(system, beam). The system must not be changed while it is being solved.
Base.pop! Method
pop!(system::System) -> ObjectOrGroupRemoves the last top-level object (or object group) of the system and returns it. Beams that were solved before must be solved again, see delete!(system, object).
Base.popat! Method
popat!(system::System, i::Integer) -> ObjectOrGroupRemoves the i-th top-level object (or object group) of the system and returns it. The index counts the objects and groups as they were added, a group counts as one. An index out of bounds throws a BoundsError. Beams that were solved before must be solved again, see delete!(system, object).
Base.delete! Method
delete!(system::System, object::ObjectOrGroup) -> systemRemoves the top-level object (or object group) from the system, compared by identity. Nothing happens for an object that is not part of the system. An object within a group can not be removed on its own and throws an ArgumentError: remove the group instead.
Beams and beam groups that were solved before still end on the removed object: solve them again with solve_system!(system, beam). The system must not be changed while it is being solved.
Solving systems
Optical systems are solved non-sequentially by the solve_system! function: for every ray the solver searches the system for the object that is hit next. A component can shortcut this search for the following ray by returning a BeamletOptics.Hint. This is explained in more detail in the section: Tracing logic.
BeamletOptics.solve_system! Function
solve_system!(system::AbstractSystem, beam::AbstractBeam; initialize=false, r_max=get_default_r_max(), depth_max=get_default_depth_max(), check_invariant=true, threshold=get_invariant_threshold())Trace an AbstractBeam and all sub-beams it generates through an optical system. Every call solves the beam from its start: the beam is first reset to its untraced state with empty!, so nothing of an earlier solve is reused. A call after objects were moved, added or removed therefore gives the same result as solving a newly constructed beam. The condition to stop ray tracing is that the last beam intersection is nothing or the beam interaction is nothing. Then, the system is considered to be solved. A maximum number of rays per beam (r_max) can be specified in order to avoid infinite calculations under resonant conditions, i.e. two facing mirrors. Likewise, depth_max limits how many branching levels are explored when new sub-beams are generated (for example, by beamsplitters) so that the tree cannot grow without bound. Sub-beams beyond the depth limit are dropped from the tree.
Between two calls only the beam itself and its first ray keep their identity. The child beams and all later rays are new objects after every call: read children(beam) again instead of keeping a child. The objects of the system are not reset by default: a Detector keeps the hits of earlier solves, such that several sources solved one after another superpose on it. With initialize = true the system is initialized first (see initialize!), i.e. all its detectors are emptied. Otherwise the hits of both solves add up.
Arguments
system::AbstractSystem: The optical system in which the beam will be traced.beam::AbstractBeam: The beam object to be traced through the system.
Keyword Arguments
initialize = false: iftrue,initialize!(system)is called before thebeamis tracedr_max = get_default_r_max(): Maximum number of tracing iterations for each leaf.depth_max = get_default_depth_max(): Maximum number of branching levels explored from the root beamcheck_invariant = true: enables or disables optical invariant checks where applicablethreshold = get_invariant_threshold(): threshold for paraxial invariant checks
solve_system!(system::AbstractSystem, bg::AbstractBeamGroup; progress=true, initialize=false, kwargs...)Trace every beam of the beam group bg through the system, multithreaded over the member beams. All other kwargs are passed on to solve_system! for each beam. Every beam of the group is reset with empty! and solved from its start. The reset happens before the first beam is traced, such that a cancelled or failed solve leaves no beam with an old path.
The hits of all beams accumulate on the same Detectors, and so do the hits of earlier solves unless initialize = true.
Keyword Arguments
progress = true: show a progress bar once tracing has run forget_progress_threshold()seconds (default 5 s). It is only drawn ifstderris a terminal, so documentation builds, CI logs and piped output stay clean.initialize = false: iftrue,initialize!(system)is called once, before the first beam is traced. The beams themselves are solved without initializing, otherwise each beam would discard the hits of the others.
Some objects store data during a solve, e.g. a Detector its hits. This data is kept between solves unless the system is initialized:
BeamletOptics.initialize! Function
initialize!(x)Brings x into the state that a new solve starts from, by discarding the data that its objects have stored during previous calls of solve_system!. x can be a System or StaticSystem, an ObjectGroup (nested groups are included) or a single object. Currently this empties all Detectors. Objects that store no such data are left unchanged. Returns nothing.
solve_system! does not call this function unless its keyword initialize is set: a Detector accumulates the hits of several solves on purpose, such that several sources solved one after another superpose. Initialize before solving again after the setup was changed, e.g. in a parameter scan. The beams are not touched, solve_system! resets them itself.
A new object type that stores data during a solve implements initialize!(object). For an AbstractDetector it calls empty!(detector).
Examples
for shift in shifts
translate_to3d!(mirror, shift)
initialize!(system) # instead of empty!(pd) for every detector
solve_system!(system, beam)
P = optical_power(pd)
endor in one call via solve_system!(system, beam; initialize = true).