class test_molecule(unittest.TestCase): """Testing class for abstract base class Molecules. """ def setUp(self): """Instantiate rotor object with quantum number m.""" self.rotor = Rotor(const.m) def test_rotor_init(self): """Test rotor init function with state array generated.""" state_prob = np.zeros(2 * const.m + 1) state_prob[const.m] = 1 np.testing.assert_array_equal(self.rotor.state.value, state_prob) def test_update_attr(self): """Test update functions to update attributes of Rotor""" self.rotor.update_time(1.0) self.rotor.update_state(State(const.m)) self.rotor.update_field(np.array([1, 1])) for attr in self.rotor.history: self.assertTrue(len(self.rotor.history[attr]) == 2) def test_evolve(self): """Test evolve function over 5 timesteps and the corresponding history of state array generated. """ for i in range(5): self.rotor.evolve(0.1) self.assertTrue(len(self.rotor.history['state']) == 6) def test_get_history_asarray(self): """Test function to return history of states as array.""" for i in range(5): self.rotor.evolve(0.1) self.rotor.update_field(np.array([0, 0])) self.assertEqual(self.rotor.get_states_asarray().shape, (2 * const.m + 1, 6)) self.assertEqual(self.rotor.get_fields_asarray().shape, (6, 2))
class PathToField(Solver): """PathToField is a solver that solves the control fields for a given path of dipole moment projection. Class PathToField is used to solve a set of control fields that drive the dipole moment projection of the system of interest to follow a given path. The system of interest by default is a rotor (molecule.Rotor.) Parameters ---------- path_desired: numpy.array, shape=(n,2) A desired path of molecule dipole moment projection. dt: float, optional (default=1000) Difference of time between two adjacent time points. This is path-specific and is currently calculated when a dataContainer.DataContainer object is instantiated with a desired path. molecule: Molecule object, optional (default=Rotor) System of interest. Default to a Rotor molecule with a system dimension of m=8 specified in constants.py. Attributes ---------- molecule: Molecule object System of interest. path: numpy.array, shape=(n,2) Path specified. n: int Number of time points. dt: float Delta t between two adjacent time points. time: numpy.array, shape=(n,) Time vector in atomic units. """ def __init__(self, path_desired, dt=1000, molecule=None): # Create a Rotor object as the system of interest if not # provided by the user if molecule is None: ## System of interest. ## Default value is a rotor (solver.molecule.Rotor) self.molecule = Rotor(const.m) else: self.molecule = rotor ## Path specified self.path = path_desired self._path_predicted = np.zeros_like(path_desired) ## Number of time points self.n = path_desired.shape[0] ## Delta t between two adjacent time points. self.dt = dt self._t_final = self.n * self.dt ## Time vector in unit of ? self.time = np.arange(self._t_final, step=self.dt, dtype=float) self._ddpath = np.stack((f.d2dt2( self.path[:, 0], self.dt), f.d2dt2(self.path[:, 1], self.dt)), axis=1) # operators used only by private methods within class instance m = self.molecule.m self._op1 = (f.cosphi(m) + 4 * f.sinphi(m) @ f.ddphi(m) - 4 * f.cosphi(m) @ f.d2dphi2(m)) self._op2 = (f.sinphi(m) - 4 * f.cosphi(m) @ f.ddphi(m) - 4 * f.sinphi(m) @ f.d2dphi2(m)) self._cosphi2 = f.cosphi(m) @ f.cosphi(m) self._sinphi2 = f.sinphi(m) @ f.sinphi(m) self._cosphi_sinphi = f.cosphi(m) @ f.sinphi(m) self._sinphi_cosphi = f.sinphi(m) @ f.cosphi(m) #calc and set initial field, but not using molecule.update_field() field = self._get_field(0, real=True) self.molecule.set_field(field) def solve(self): """Calculate the control field required for each time step. """ for j in tqdm.tqdm(range(1, self.n)): self.molecule.evolve(self.dt) field = self._get_field(j, real=True) self.molecule.update_field(field) # self._velidate() def export(self): """Export calculated time vector, fields, path, and states as np.ndarray. Returns ------- time: numpy.array, shape=(n,) Time vector based on dt. In unit of picoseconds. fields: numpy.array, shape=(n,2) Control fields required for the path of interest. In unit of V/angstrom. path: numpy.array, shape=(n,2) Resulting path based on the calculated fields. states: numpy.array, shape=(2m+1,n) State amplitudes of the system at every time point. """ time = self.molecule.get_time_asarray() time = time * 2.418 * 10**(-17) * 10**12 #time in picoseconds states = self.molecule.get_states_asarray() fields = self.molecule.get_fields_asarray() field_const = 5.142 * 10**11 * 10**(-10) #amplitude in V/angstrom fields = fields * field_const path = np.zeros((self.n, 2)) oper_x = self.molecule.dipole_x oper_y = self.molecule.dipole_y states_list = self.molecule.history['state'] for i in range(self.n): path[i, 0] = states_list[i].get_expt(oper_x).real path[i, 1] = states_list[i].get_expt(oper_y).real return time, fields, path, states def _get_field(self, j, real=False): """Calculate the required field for the next step. Parameters ---------- j: int System is at the j-th time point. real: bool, optional (default=False) If True, force the returned value to be only the real part of a complex number. Returns ------- field: numpy.array, shape=(2,) An array of size 2 containing x- and y-component of the control field for the next step. """ field = self._get_Ainv() @ self._get_b(j) if real: field = field.real return field.flatten() def _get_det(self): """Calculate determinant of matrix A""" state = self.molecule.state c = 4 * const.B**2 * const.mu**2 / const.hbar**4 det = (c * (state.get_expt(self._sinphi2) * state.get_expt(self._cosphi2) - state.get_expt(self._sinphi_cosphi)**2)) return det def _get_Ainv(self): """Calculate inverse of matrix A""" state = self.molecule.state c = 2 * const.B * const.mu / const.hbar**2 a11 = c * state.get_expt(self._sinphi2) a12 = -c * state.get_expt(self._cosphi_sinphi) a21 = -c * state.get_expt(self._sinphi_cosphi) a22 = c * state.get_expt(self._cosphi2) det = self._get_det() A_inv = 1 / det * np.array([[a22, -a12], [-a21, a11]]) return A_inv def _get_b(self, i): """Calculate b vector""" state = self.molecule.state c = const.B**2 / const.hbar**2 b1 = self._ddpath[i, 0] + np.real(c * state.get_expt(self._op1)) b2 = self._ddpath[i, 1] + np.real(c * state.get_expt(self._op2)) return np.array([b1, b2])