Symbolic Reference

See the Interferometer Geometries section for examples of how to use the mwave.symbolic module.

Interferometer class

class mwave.symbolic.Interferometer(init_node=None)

Class representing an interferometer geometry. The user defines the interferometer geometry by applying unitary operations (i.e. beamsplitter, free evolution, etc.). Internally the class applies each unitary operation to nodes in a directed tree based on the InterferometerNode class. These nodes should be thought of as representing terms in the waverfunction propagating through the interferometer.

Each application of a unitary operator creates child nodes that depend on the type of unitary operator applied and the properties of their respective parent nodes. For example, a free evolution operator would create one child node for each node in the bottom layer of the tree with a position, phase, etc. dependent on the parent node. A beamsplitter operator would create two or more child nodes for each node in the bottom layer of the tree.

The Interferometer class also provides methods are provided to visualize the tree representing the interferometer, to plot trajectories of its arms, to compute the analytic phase of the interferometer, and to generate a code outline that can be used to perform numerical simulations with the specified geometry.

Parameters:

init_node – The InterferometerNode instance to use as the inital interferometer state. If not provided an InterferometerNode is created with default parameters.

For an example of how the Interferometer class can be used see sci_example.

apply(unitary)

Applies the provided unitary operation to the interferometer. The interferometer tree depth is increased by 1 to accomodate the states that occur after the unitary operation.

Internally this is performed by looping over each InterferometerNode at the current depth and passing it to the apply function of the provided Unitary object.

Parameters:

unitary – The unitary operation to apply.

generate_code_outline(iports)

This function generates an outline of python code that can be used to compute the population at all of the output ports of an arbitrary interferometer geometry. The code will not be immediately ready to run. The user must adapt the automatically generated functions for their own use.

Parameters:

iports – A dictionary of interfering port populations to generate a numerical computation for.

Returns:

The code as a string.

from mwave import symbolic as ifr
import sympy as sp

# Set up symbols for symbolic computation
m, c, hbar, k, omega_r, delta, delta_bloch, n, N, omega_m, T, Tp, t_traj = sp.symbols('m c hbar k omega_r delta delta_bloch n N omega_m T Tp t_traj', real=True)
k_eff = 2*k

# Register constants
ifr.set_constants(m=m, c=c, hbar=hbar, t_traj=t_traj)

# Define unitary operators needed for an SCI
bs1 = ifr.Beamsplitter(0, n, delta, k_eff)

bo1 = ifr.Beamsplitter(n, n+N, delta_bloch, k_eff)
bo2 = ifr.Beamsplitter(0, -N, delta_bloch, k_eff)

bs2u = ifr.Beamsplitter(n+N, 2*n+N, delta + omega_m, k_eff)
bs2d = ifr.Beamsplitter(0-N, -n-N, delta - omega_m, k_eff)

fe1 = ifr.FreeEv(T)
fe2 = ifr.FreeEv(Tp/2)

# Create interferometer object, perform SCI sequence
ii = ifr.Interferometer()
ii.apply(bs1)
ii.apply(fe1)
ii.apply(bs1)
ii.apply(fe2)
ii.apply(bo1)
ii.apply(bo2)
ii.apply(fe2)
ii.apply(bs2u)
ii.apply(bs2d)
ii.apply(fe1)
ii.apply(bs2u)
ii.apply(bs2d)

# Interfere
ii.interfere()

# Assign nodes to output ports
interfering_ports, junk_ports, no_ports = ii.get_ports({N + 2*n: 'A', N + n: 'B', -N: 'C', -N-n: 'D'})

# Make code outline, export
with open('example.py', 'w') as f:
    f.write(ii.generate_code_outline(interfering_ports))
generate_graph()

Returns a graphviz.Digraph object containing the nodes in the inteferometer tree.

Returns:

The graphviz.Digraph object.

get_nodes()

Returns the list of the InterferometerNode s at the bottom of the tree (i.e. the nodes produced by the latest call to apply).

Returns:

The list of :code:`InterferometerNode`s.

get_ports(nstate_port_map)

Returns a dictionary of nodes in each port. Ports are defined by the momentum states in the provided momentum state to port mapping nstate_port_map. Ports are returned in the interfering port dictionary if there are determined to be interfering ports by the interfere() method. Otherwise they are put in the junk port dictionary. Nodes that do not have a port mapping are put in the no port list.

The user must have called interfere prior to invoking this method.

Parameters:

nstate_port_map – A dictionary mapping momentum states to port labels.

Returns:

A tuple containing (interfering_port_dict, junk_port_dict, no_port_list)

from mwave import symbolic as ifr
import sympy as sp

# Set up symbols for symbolic computation
m, c, hbar, k, omega_r, delta, delta_bloch, n, N, omega_m, T, Tp, t_traj = sp.symbols('m c hbar k omega_r delta delta_bloch n N omega_m T Tp t_traj', real=True)
k_eff = 2*k

# Register constants
ifr.set_constants(m=m, c=c, hbar=hbar, t_traj=t_traj)

# Define unitary operators needed for an SCI
bs1 = ifr.Beamsplitter(0, n, delta, k_eff)

bo1 = ifr.Beamsplitter(n, n+N, delta_bloch, k_eff)
bo2 = ifr.Beamsplitter(0, -N, delta_bloch, k_eff)

bs2u = ifr.Beamsplitter(n+N, 2*n+N, delta + omega_m, k_eff)
bs2d = ifr.Beamsplitter(0-N, -n-N, delta - omega_m, k_eff)

fe1 = ifr.FreeEv(T)
fe2 = ifr.FreeEv(Tp/2)

# Create interferometer object, perform SCI sequence
ii = ifr.Interferometer()
ii.apply(bs1)
ii.apply(fe1)
ii.apply(bs1)
ii.apply(fe2)
ii.apply(bo1)
ii.apply(bo2)
ii.apply(fe2)
ii.apply(bs2u)
ii.apply(bs2d)
ii.apply(fe1)
ii.apply(bs2u)
ii.apply(bs2d)

# Interfere
ii.interfere()

# Assign nodes to output ports
interfering_ports, junk_ports, no_ports = ii.get_ports({N + 2*n: 'A', N + n: 'B', -N: 'C', -N-n: 'D'})
interfere(subs=None, progress=False, recompute=False)

Determines which nodes are interfering at the current depth and returns a list of tuples where each tuples contains two interfering nodes.

Two nodes are defined as interfering if they have equal positions and are in equal momentum states. This means that if the interferometer does not perfectly close (i.e. in the case of a gravity gradient), the nodes will not be detected as interfering. To ensure that the function still works in this case the user can pass subs={gamma: 0} (or similar), which sets the gamma symbol to zero during the search for interfering nodes.

Parameters:
  • subs – A dictionary of sympy symbols to substitute. This can be useful if you want to do something like remove dependence on the gravity gradient.

  • progress – If true a progress bar will be displayed when computing interferences.

  • recompute – If true interferences will be recomputed, even if they have been computed previously. Use this if you compute interferances at multiple stages of the interferometer.

phases(include_separation_phase=True)

Computes the phase between all interfering node pairs. The user must have called interfere prior to invoking this method. Returns a list of phases, where each element in the list corresponds to the phase between the node pair computed by the interfere method.

Parameters:

include_separation_phase – If True the separation phase in the z direction is computed and included.

Returns:

The list of phase differences between each interfering node pair.

Unitary operators

class mwave.symbolic.Unitary

Represents a unitary transformation (i.e. free evolution, beamsplitter, etc.). The unitary transformation is applied to the provided state (represented as a InterferometerNode) via the apply method. The transformed state is added as a child node(s) of the provided InterferometerNode. The new child node represents the state after the unitary transformation has been performed. Note that the phase of any given node is only the phase imparted by the unitary operator that generated it.

If applying the unitary operator requires a numerical calculation, this should be implemented by overriding the gen_numeric method. An example of how to do this is in the example Numerical calculation of the SCI diffraction phase.

abstract apply(node)

Applies the unitary operator to the provided node. All unitary operators must implement this method.

Parameters:

node – The node to apply the unitary operator to.

gen_numeric(node, args, subs)

Function that child classes of mwave.symbolic.Unitary can override in order to include numerically calculated corrections in phase calculations. See the example numercal_evaluation_sci for more information on how to use this function.

is_numeric()

Returns true if the get_numeric function has been implemented. This will return false unless a subclass implements the function.

class mwave.symbolic.FreeEv(T, gravity=None, gravity_grad=None)

Creates a unitary evolution operator for propagating a particle in free space.

Parameters:
  • T – The time to propagate the particle for.

  • gravity – The gravity to evolve the particle under.

  • gravity_grad – The gravity gradient to evolve the particle under. If gravity=None this variable has no effect.

apply(node)

Applies the unitary operator to the provided node. All unitary operators must implement this method.

Parameters:

node – The node to apply the unitary operator to.

class mwave.symbolic.Beamsplitter(n1, n2, delta, k, theta_transfer=pi / 4, include_half_pi_shift=True)

Creates a unitary beamsplitter operator. The phase imparted by the beamsplitter is the theoretical phase obtained from a perfect matterwave beamsplitter.

A beamsplitter is always resonant between two momentum states. These must be specified for the beamsplitter to work.

Parameters:
  • n1 – One of the two momentum states in units of \(2\hbar k\).

  • n2 – The other momentum state in units of \(2\hbar k\)

  • delta – The detuning between the two lasers

  • k – The effective wavevector. In the case that the beamsplitter provides two photon kicks to the particle this should be \(2k_\text{laser}\)

  • theta_transfer – Describes the amplitude \(c\) in the diffracted and undiffracted states after the beamsplitter via \(c_\text{diffracted}=\cos(\theta_\text{transfer})\) and \(c_\text{undiffracted}=\sin(\theta_\text{transfer})\)

  • include_half_pi_shift – If true then each diffracted node has \(\pi/2\) added to its phase. This phase shift happens for all beamsplitters, but is typically ignored in the phase calculation. You might want to include it if you want to calculate the population between output ports, as ignoring it causes the output port populations to be in phase instead of \(\pi/2\) out of phase.

apply(node)

Applies the unitary operator to the provided node. All unitary operators must implement this method.

Parameters:

node – The node to apply the unitary operator to.

class mwave.symbolic.Mirror(n1, n2, delta, k)

Creates a unitary mirror operator. The phase imparted by the mirror is the theoretical phase obtained from a perfect matterwave mirror.

Mirror always flips population perfectly between two momentum states.

Parameters:
  • n1 – One of the two momentum states in units of \(2\hbar k\).

  • n2 – The other momentum state in units of \(2\hbar k\)

  • delta – The detuning between the two lasers

  • k – The effective wavevector. In the case that the beamsplitter provides two photon kicks to the particle this should be \(2k_\text{laser}\)

apply(node)

Applies the unitary operator to the provided node. All unitary operators must implement this method.

Parameters:

node – The node to apply the unitary operator to.

Nodes

class mwave.symbolic.TreeNode(parent_node=None)

Basic implementation of a directed tree. Nodes are assigned a unique ID. Children are stored in a dictionary, with the ID of the child serving as the dictionary key.

Parameters:

parent_node – The parent node of the new node. Must be a TreeNode. Optional.

For example, to create a tree where node a has child node b:

from mwave.symbolic import TreeNode
a = TreeNode()
b = TreeNode(a)
a.children
set_parent(parent_node)

Sets the parent node to the provided node.

Parameters:

parent_node – The parent node of the new node. Must be a TreeNode.

class mwave.symbolic.InterferometerNode(n=0, v=0, z=0, t=0, x=0, y=0, parent_node=None, unitary=None)

Used to represent terms in the wavefunction of an interferometer. Inherits from TreeNode. Each InterferometerNode contains a reference to the unitary object that generated it and can optionally contain an analytic expression for the trajectory between its parent node and itself.

Interferometers have a tree structure where paths often split into multiple paths, which is why a tree structure is appropriate for representing their wavefunctions.

Parameters:
  • n – The momentum state of the node.

  • v – The velocity of the node.

  • z – The z position of the node.

  • t – The final time that the node represents.

  • x – The x position of the node.

  • y – The y position of the node.

  • parent_node – The parent of the new node. If provided the parent_node for n, v, z, t, x, and y overwrite the ones passed in by the user.

  • unitary – The unitary generator that produced the node. Optional.

gen_numeric_wf_func(subs={}, filter=None, extended=False)

Returns a function that numerically computes the wavefunction term represented by the node. For detailed example usage please see the Numerical calculation of the SCI diffraction phase example.

Parameters:
  • subs – The substitutions to make to the node parameters in the sympy subsitution format (i.e. {m: 1, n: 3, delta: 4*3}). This is required if any node parameters used by the get_numeric function depend on a sympy expression.

  • filter – A function used for filtering which numeric functions are included in the calculation. The function must accept two arguments of the form (node, unitary), which are instances of InterferometerNode and Unitary. If the function returns true then the numeric function is included in the calculation. If false the numeric function is omitted from the calculation.

  • extended – If true a list of individual functions is returned along with the function that computes the numerical phase of the node. The return then has form (total_phase_fnc, [phase_fnc1, phase_fnc2, ...]).

get_amp()

Returns the the amplitude of the state by multiplying the amplitudes of all ancestor states togeather.

get_amp_diff()

Gets the amplitude of the state relative to the previous state.

get_generator()

Returns the unitary operator that generated the node.

get_partial_trajectory()

Returns the trajectory taken from the parent node to this node as a sympy expression where time is parameterized by constants.t_traj. The second returned parameter is the final time at which the trajectory is valid (note that the initial time at which the trajectory is valid is not returned).

get_phase()

Returns the the phase of the state by summing the phase differences of all ancestor states togeather.

get_phase_diff()

Returns the phase difference between this state and its parent state.

get_trajectory(subs=None)

Returns a piecewise function describing the trajectory of the node and all of its ancestor nodes where time is parameterized by constants.t_traj. If subs is passed in then a function of the form fnc_trajectory(t) that returns a numerical value will be returned. Otherwise a sympy expression will be returned.

get_wf_term()

Returns the wavefunction of the state.

has_generator()

Returns true if a generator has been assigned for this node, false otherwise.

lineage()

Returns a string that describes the lineage of the node (i.e. the ancestor nodes it is derived from).

set_amp_diff(amp)

Sets the amplitude of the state relative to the previous state.

set_phase_diff(phase)

Sets the phase of the state. This phase should be the phase difference between this state and its parent state.

Parameters:

phase – The phase difference between this state and its parent state.

Helper functions

mwave.symbolic.set_constants(**kwargs)

Each key value pair provided by the user is stored in the constants object. The value must be either a number or a sympy symbol.

If the user provides a key that has already been set a warning is thrown.

from mwave import symbolic as ifr
import sympy as sp

m, c = sp.symbols('m c', real=True)
ifr.set_constants(m=m,c=c,g=9.8)
for const in ifr.constants_keys:
    print(f"The constant {const} is set to {getattr(ifr.constants, const)}")
mwave.symbolic.eval_sympy_var(var, subs)

Numerically evaluates the provided variable and returns the result. The provided variable can either be a python number or a sympy expression. If a sympy expression is provided evaluation is attempted using subs. If this fails an error is raised.

This function is useful for evaluating variables that could either be a python number or a sympy expression.

Parameters:
  • var – The variable to evaluate

  • subs – The substitutions to make (via sympy).