Source code for pylcp.governingeq

import copy
import numpy as np
from .fields import magField as magFieldObject
from .fields import laserBeams as laserBeamsObject
from scipy.optimize import root_scalar, root

[docs]class governingeq(object): """ Governing equation base class This class is the basis for making all the governing equations in `pylcp`, including the rate equations, heuristic equation, and the optical Bloch equations. Its methods are available to other governing equations. Parameters ---------- laserBeams : dictionary of pylcp.laserBeams, pylcp.laserBeams, or list of pylcp.laserBeam The laserBeams that will be used in constructing the optical Bloch equations. which transitions in the block diagonal hamiltonian. It can be any of the following: * A dictionary of pylcp.laserBeams: if this is the case, the keys of the dictionary should match available :math:`d^{nm}` matrices in the pylcp.hamiltonian object. The key structure should be `n->m`. * pylcp.laserBeams: a single set of laser beams is assumed to address the transition `g->e`. * a list of pylcp.laserBeam: automatically promoted to a pylcp.laserBeams object assumed to address the transtion `g->e`. magField : pylcp.magField or callable The function or object that defines the magnetic field. hamiltonian : pylcp.hamiltonian or None The internal hamiltonian of the particle. a : array_like, shape (3,), optional A default acceleraiton to apply to the particle's motion, usually gravity. Default: [0., 0., 0.] r0 : array_like, shape (3,) Initial position. Default: [0.,0.,0.] v0 : array_like, shape (3,) Initial velocity. Default: [0.,0.,0.] """ def __init__(self, laserBeams, magField, hamiltonian=None, a=np.array([0., 0., 0.]), r0=np.array([0., 0., 0.]), v0=np.array([0., 0., 0.])): self.set_initial_position_and_velocity(r0, v0) # Add lasers: self.laserBeams = {} # Laser beams are meant to be dictionary, if isinstance(laserBeams, list): self.laserBeams['g->e'] = copy.copy(laserBeamsObject(laserBeams)) # Assume label is g->e elif isinstance(laserBeams, laserBeamsObject): self.laserBeams['g->e'] = copy.copy(laserBeams) # Again, assume label is g->e elif isinstance(laserBeams, dict): for key in laserBeams.keys(): if not isinstance(laserBeams[key], laserBeamsObject): raise TypeError('Key %s in dictionary lasersBeams ' % key + 'is in not of type laserBeams.') self.laserBeams = copy.copy(laserBeams) # Now, assume that everything is the same. else: raise TypeError('laserBeams is not a valid type.') # Add in magnetic field: if callable(magField) or isinstance(magField, np.ndarray): self.magField = magFieldObject(magField) elif isinstance(magField, magFieldObject): self.magField = copy.copy(magField) else: raise TypeError('The magnetic field must be either a lambda ' + 'function or a magField object.') # Add the Hamiltonian: if hamiltonian is not None: self.hamiltonian = copy.copy(hamiltonian) self.hamiltonian.make_full_matrices() # Next, check to see if there is consistency in k: self.__check_consistency_in_lasers_and_d_q() # Check the acceleration: if not isinstance(a, np.ndarray): raise TypeError('Constant acceleration must be an numpy array.') elif a.size != 3: raise ValueError('Constant acceleration must have length 3.') else: self.constant_accel = a # Set up a dictionary to store any resulting force profiles. self.profile = {} # Set the initial sol to zero: self.sol = None # Set an attribute for the equillibrium position: self.r_eq = None def __check_consistency_in_lasers_and_d_q(self): # Check that laser beam keys and Hamiltonian keys match. for laser_key in self.laserBeams.keys(): if not laser_key in self.hamiltonian.laser_keys.keys(): raise ValueError('laserBeams dictionary keys %s ' % laser_key + 'does not have a corresponding key the '+ 'Hamiltonian d_q.')
[docs] def set_initial_position_and_velocity(self, r0, v0): """ Sets the initial position and velocity Parameters ---------- r0 : array_like, shape (3,) Initial position. Default: [0.,0.,0.] v0 : array_like, shape (3,) Initial velocity. Default: [0.,0.,0.] """ self.set_initial_position(r0) self.set_initial_velocity(v0)
[docs] def set_initial_position(self, r0): """ Sets the initial position Parameters ---------- r0 : array_like, shape (3,) Initial position. Default: [0.,0.,0.] """ self.r0 = r0 self.sol = None
[docs] def set_initial_velocity(self, v0): """ Sets the initial velocity Parameters ---------- v0 : array_like, shape (3,) Initial position. Default: [0.,0.,0.] """ self.v0 = v0 self.sol = None
def evolve_motion(self): pass
[docs] def find_equilibrium_force(self): """ Find the equilibrium force at the initial conditions Returns ------- force : array_like Equilibrium force experienced by the atom """ pass
[docs] def force(self): """ Find the instantaneous force Returns ------- force : array_like Force experienced by the atom """ pass
[docs] def generate_force_profile(self): """ Map out the equilibrium force vs. position and velocity Parameters ---------- R : array_like, shape(3, ...) Position vector. First dimension of the array must be length 3, and corresponds to :math:`x`, :math:`y`, and :math:`z` components, repsectively. V : array_like, shape(3, ...) Velocity vector. First dimension of the array must be length 3, and corresponds to :math:`v_x`, :math:`v_y`, and :math:`v_z` components, repsectively. name : str, optional Name for the profile. Stored in profile dictionary in this object. If None, uses the next integer, cast as a string, (i.e., '0') as the name. progress_bar : boolean, optional Displays a progress bar as the proceeds. Default: False Returns ------- profile : pylcp.common.base_force_profile Resulting force profile. """ pass
[docs] def find_equilibrium_position(self, axes, **kwargs): """ Find the equilibrium position Uses the find_equilibrium force() method to calculate the where the :math:`\\mathbf{f}(\mathbf{r}, \mathbf{v}=0)=0`. Parameters ---------- axes : array_like A list of axis indices to compute the trapping frequencies along. Here, :math:`\hat{x}` is index 0, :math:`\hat{y}` is index 1, and :math:`\hat{z}` is index 2. For example, `axes=[2]` calculates the trapping frquency along :math:`\hat{z}`. kwargs : Any additional keyword arguments to pass to find_equilibrium_force() Returns ------- r_eq : list or float The equilibrium positions along the selected axes. """ if self.r_eq is None: self.r_eq = np.zeros((3,)) def simple_wrapper(r_changing): r_wrap = self.r_eq.copy() r_wrap[axes] = r_changing self.set_initial_position_and_velocity(r_wrap, np.array([0.0, 0.0, 0.0])) F = self.find_equilibrium_force() return F[axes] #print('Initial guess: %s' % r_eqi[axes]) if len(axes)>1: result = root(simple_wrapper, **kwargs) self.r_eq[axes] = result.x else: result = root_scalar(simple_wrapper, **kwargs) self.r_eq[axes] = result.root return self.r_eq
[docs] def trapping_frequencies(self, axes, r=None, eps=0.01, **kwargs): """ Find the trapping frequency Uses the find_equilibrium force() method to calculate the trapping frequency for the particular configuration. Parameters ---------- axes : array_like A list of axis indices to compute the trapping frequencies along. Here, :math:`\hat{x}` is index 0, :math:`\hat{y}` is index 1, and :math:`\hat{z}` is index 2. For example, `axes=[2]` calculates the trapping frquency along :math:`\hat{z}`. r : array_like, optional The position at which to calculate the damping coefficient. By default r=None, which defaults to calculating at the equilibrium position as found by the find_equilibrium_position() method. If this method has not been run, it defaults to the origin. eps : float, optional The small numerical :math:`\epsilon` parameter used for calculating the :math:`df/dr` derivative. Default: 0.01 kwargs : Any additional keyword arguments to pass to find_equilibrium_force() Returns ------- omega : list or float The trapping frequencies along the selected axes. """ self.omega = np.zeros(3,) if isinstance(eps, float): eps = np.array([eps]*3) if r is None and self.r_eq is None: r = np.array([0., 0., 0.]) elif r is None: r = self.r_eq if hasattr(self, 'mass'): mass = self.mass else: mass = self.hamiltonian.mass for axis in axes: if not np.isnan(r[axis]): rpmdri = np.tile(r, (2,1)).T rpmdri[axis, 1] += eps[axis] rpmdri[axis, 0] -= eps[axis] F = np.zeros((2,)) for jj in range(2): self.set_initial_position_and_velocity(rpmdri[:, jj], np.zeros((3,))) f = self.find_equilibrium_force(**kwargs) F[jj] = f[axis] if np.diff(F)<0: self.omega[axis] = np.sqrt(-np.diff(F)/(2*eps[axis]*mass)) else: self.omega[axis] = 0 else: self.omega[axis] = 0 return self.omega[axes]
[docs] def damping_coeff(self, axes, r=None, eps=0.01, **kwargs): """ Find the damping coefficent Uses the find_equilibrium force() method to calculate the damping coefficient for the particular configuration. Parameters ---------- axes : array_like A list of axis indices to compute the damping coefficient(s) along. Here, :math:`\hat{x}` is index 0, :math:`\hat{y}` is index 1, and :math:`\hat{z}` is index 2. For example, `axes=[2]` calculates the damping parameter along :math:`\hat{z}`. r : array_like, optional The position at which to calculate the damping coefficient. By default r=None, which defaults to calculating at the equilibrium position as found by the find_equilibrium_position() method. If this method has not been run, it defaults to the origin. eps : float The small numerical :math:`\epsilon` parameter used for calculating the :math:`df/dv` derivative. Default: 0.01 kwargs : Any additional keyword arguments to pass to find_equilibrium_force() Returns ------- beta : list or float The damping coefficients along the selected axes. """ self.beta = np.zeros(3,) if isinstance(eps, float): eps = np.array([eps]*3) if r is None and self.r_eq is None: r = np.array([0., 0., 0.]) elif r is None: r = self.r_eq for axis in axes: if not np.isnan(r[axis]): vpmdvi = np.zeros((3,2)) vpmdvi[axis, 1] += eps[axis] vpmdvi[axis, 0] -= eps[axis] F = np.zeros((2,)) for jj in range(2): self.set_initial_position_and_velocity(r, vpmdvi[:, jj]) f = self.find_equilibrium_force(**kwargs) F[jj] = f[axis] self.beta[axis] = -np.diff(F)/(2*eps[axis]) else: self.beta[axis] = 0 return self.beta[axes]