I was taking a look at a code sample to familiarize myself with how the stuff in this repo is used. One thing caught my eye on how it seems like lasers are defined:
The laser variable is partially instantiated at first because of the way that the E-field and wavelength are derived properties from the trap variable. But the trap variable itself needs to know which way the laser is pointing in the first place.
This is a little awkward and is vulnerable to regression because it requires that users of the code remember to "circle back" and remember to set these parameters in order to fully define the struct after it's been created.
I think two things need to happen here.
Item 1: Reduce Laser Struct to Inherent Properties of Laser Light
I think we can reduce the Laser struct down to these properties:
λ: wavelength
E: E-field magnitude (make this strictly a function)
ϵ: polarization direction
k: propagation direction
ϕ: phase
Note that the pointing property has been removed. I thought this was the correct thing to do, because setting that property requires that you have knowledge of the trap ahead of time that it's going to be associated with, which breaks the abstraction. (after all, why can't you use the same laser on different traps?)
I also removed Δ from the proposition above, because it seems that property is also associated with the environment the laser is part of (ie: not inherent to the laser). Please correct me if I'm wrong about that.
Item 2: Create Dedicated Constructors for Different Contexts
With the struct definition in item 1, we can rewrite the code example to look like this instead:
# Define your individual components up here first.`
magnetic_field = MagneticField(
B=4e-4,
Bhat=ẑ,
δB=0
)
chain = LinearChain(ions=[ion],
com_frequencies=(x=3e6,y=3e6,z=1e6),
vibrational_modes=(;z=[1]))
laser = build_laser_from_pi_time_and_transition(
k=x̂, # Laser is built with a custom constructor
ϵ=y, # that is purpose-built for defining the
pi_time=2e-6, # E-field and wavelength from the ion it's
ion=ion, # pointing at. If there are other
transition=("S", "D"), # ways you need to infer these or different
B=magnetic_field # parameters, it's just a matter of defining
) # new constructors.
# Now you can create your trap here at the very end by putting them all
# together. No more need to "circle back". Everything should be fully
# defined by this point.
trap = Trap(
configuration=chain,
B=magnetic_field,
lasers=[laser],
pointing_configuration={
laser: [(1.0, ion)]
}
)
Note that this example also implies the creation of a new struct for magnets.
The function build_laser_from_pi_time_and_transition is just a custom constructor that fits this specific purpose of inferring the E-field and wavelength from the pi time and the ion transition you're exciting. There are perhaps other contexts in which you would want to build the laser with different input parameters. And all you'd have to do in those cases is define new constructors for those circumstances.
Also note that the pointing property that used to belong to the laser has now moved to the trap, and it is a dictionary that maps each laser to a list of ions that it points to, annotated with the modifiers that you use to indicate how strongly incident each laser is on each ion.
I was taking a look at a code sample to familiarize myself with how the stuff in this repo is used. One thing caught my eye on how it seems like lasers are defined:
The
laser
variable is partially instantiated at first because of the way that the E-field and wavelength are derived properties from thetrap
variable. But thetrap
variable itself needs to know which way the laser is pointing in the first place.This is a little awkward and is vulnerable to regression because it requires that users of the code remember to "circle back" and remember to set these parameters in order to fully define the struct after it's been created.
I think two things need to happen here.
Item 1: Reduce
Laser
Struct to Inherent Properties of Laser LightI think we can reduce the
Laser
struct down to these properties:Note that the
pointing
property has been removed. I thought this was the correct thing to do, because setting that property requires that you have knowledge of the trap ahead of time that it's going to be associated with, which breaks the abstraction. (after all, why can't you use the same laser on different traps?)I also removed Δ from the proposition above, because it seems that property is also associated with the environment the laser is part of (ie: not inherent to the laser). Please correct me if I'm wrong about that.
Item 2: Create Dedicated Constructors for Different Contexts
With the struct definition in item 1, we can rewrite the code example to look like this instead:
Note that this example also implies the creation of a new struct for magnets.
The function
build_laser_from_pi_time_and_transition
is just a custom constructor that fits this specific purpose of inferring the E-field and wavelength from the pi time and the ion transition you're exciting. There are perhaps other contexts in which you would want to build the laser with different input parameters. And all you'd have to do in those cases is define new constructors for those circumstances.Also note that the
pointing
property that used to belong to the laser has now moved to the trap, and it is a dictionary that maps each laser to a list of ions that it points to, annotated with the modifiers that you use to indicate how strongly incident each laser is on each ion.