dipole module
Module contains the Dipole
class.
The Dipole
class represents a pair of oscillating point charges whose
trajectories are updated at each time step during the simulation. The positive
and negative charge pair are represented as _DipoleCharge
objects, which
are a subclass of the Charge
base class. The trajectories of these charges
are determined at each time step using the run
method from the instantiated
Simulation
object, which accepts Charge
and Dipole
objects as
initialization parameters.
All units are in SI.
Dipole
Oscillating dipole with a moment that is dependent on driving E field.
Class simulates an oscillating dipole with a time-dependent origin and
has a dipole moment that is determined by the Lorentz oscillator equation
of motion. The dipole moment is updated at each time step in the simulation
by the Simulation
object.
The Lorentz oscillator equation of motion is given by (Novotny Eq. 8.135):
\(d''(t) + \gamma_0*d'(t) + \omega_0^2*d(t) = E_d(t)*q^2/m_{eff}\)
where \(E_d(t)\) is the driving electric field (i.e. the component of the external electric field along the dipole's axis of polarization).
Parameters:
Name | Type | Description | Default |
---|---|---|---|
omega_0 |
float |
Natural angular frequency of dipole (units: rad/s). |
required |
origin |
Union[Tuple[float, float, float], Callable[[float], ndarray]] |
List of x, y, and z values of dipole's origin (center of mass) or function with one input parameter for time that returns a 3 element list for x, y, and z values. |
required |
initial_r |
Tuple[float, float, float] |
List of x, y, and z values for the initial displacement vector between the two point charges. |
required |
q |
float |
Magnitude of the charge value of each point charge. Default
is |
required |
m |
Union[float, Tuple[float, float]] |
Mass of the two point charges or
a 2 element list of the two masses if they are different. Default
is |
required |
Exceptions:
Type | Description |
---|---|
ValueError |
Raised if the magnitude of the initial moment is zero. |
Examples:
Below is an origin function that oscillates along the x-axis with an
amplitude of 1e-10 m
and angular frequency of 1e12*2*pi rad/s
:
def fun_origin(t):
return np.array((1e-10*np.cos(1e12*2*np.pi*t), 0, 0))
get_E_driving(self, field_type='Total')
Return the magnitude of the driving electric field.
The driving electric field is the component of the external electric
field experienced by the charge along the direction of polarization.
The returned field type (Total
, Velocity
, or Acceleration
) can
be specified.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
field_type |
str |
Return either the |
'Total' |
Exceptions:
Type | Description |
---|---|
ValueError |
Input for |
Returns:
Type | Description |
---|---|
ndarray |
Magnitude of the driving electric field at each time step. |
Source code in pycharge/dipole.py
def get_E_driving(self, field_type: str = 'Total') -> ndarray:
"""Return the magnitude of the driving electric field.
The driving electric field is the component of the external electric
field experienced by the charge along the direction of polarization.
The returned field type (`Total`, `Velocity`, or `Acceleration`) can
be specified.
Args:
field_type: Return either the `Total`, `Velocity`, or
`Acceleration` field. Defaults to `Total`.
Raises:
ValueError: Input for `field_type` argument is invalid.
Returns:
Magnitude of the driving electric field at each time step.
"""
if field_type == 'Total':
return np.linalg.norm(self.E_total, axis=0)
if field_type == 'Velocity':
return np.linalg.norm(self.E_vel, axis=0)
if field_type == 'Acceleration':
return np.linalg.norm(self.E_acc, axis=0)
raise ValueError('Invalid field')
get_kinetic_energy(self, exclude_origin=True)
Return the kinetic energy of the dipole at each time step.
The kinetic energy of just the dipole moment can be determined by excluding the kinetic energy from the origin's movement.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
exclude_origin |
bool |
Kinetic energy calculation excludes the movement of
the dipole's origin. Defaults to |
True |
Returns:
Type | Description |
---|---|
ndarray |
Kinetic energy at each time step. |
Source code in pycharge/dipole.py
def get_kinetic_energy(self, exclude_origin: bool = True) -> ndarray:
"""Return the kinetic energy of the dipole at each time step.
The kinetic energy of just the dipole moment can be determined by
excluding the kinetic energy from the origin's movement.
Args:
exclude_origin: Kinetic energy calculation excludes the movement of
the dipole's origin. Defaults to `True`.
Returns:
Kinetic energy at each time step.
"""
if exclude_origin:
return 0.5*self.m_eff*np.linalg.norm(self.moment_vel, axis=0)**2
charge_KE = (0.5*self.m_eff *
np.linalg.norm(self.moment_vel, axis=0)**2)
return charge_KE # Double KE since there are two charges
get_origin_position(self, magnitude=False)
Return the position of the dipole's origin at each time step.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
magnitude |
bool |
Return the magnitude of the origin position instead of a
3D vector if |
False |
Returns:
Type | Description |
---|---|
ndarray |
Origin at each time step, either the magnitude of the position (1D array of size N) or the position vector (2D array of size 3 x N). |
Source code in pycharge/dipole.py
def get_origin_position(self, magnitude: bool = False) -> ndarray:
"""Return the position of the dipole's origin at each time step.
Args:
magnitude: Return the magnitude of the origin position instead of a
3D vector if `True`. Defaults to `False`.
Returns:
Origin at each time step, either the magnitude of the position (1D
array of size N) or the position vector (2D array of size 3 x N).
"""
origin_position = np.zeros((3, self.t_index+1))
for i in np.arange(self.t_index+1):
origin_position[:, i] = self.origin(self.dt*i)
if magnitude:
return np.linalg.norm(origin_position, axis=0)
return origin_position
reset(self, timesteps, dt, save_E)
Initialize the moment and E arrays, and _DipoleCharge
pair.
Source code in pycharge/dipole.py
def reset(self, timesteps: float, dt: float, save_E: bool) -> None:
"""Initialize the moment and E arrays, and `_DipoleCharge` pair."""
self.t_index = 0
self.dt = dt
self.moment_disp = np.ones((3, timesteps))*np.inf
self.moment_vel = np.ones((3, timesteps))*np.inf
self.moment_acc = np.ones((3, timesteps))*np.inf
self.moment_disp[:, 0] = self.initial_r
self.moment_vel[:, 0] = 0
self.moment_acc[:, 0] = 0
if save_E:
self.E_total = np.ones((3, timesteps))*np.inf
self.E_vel = np.ones((3, timesteps))*np.inf
self.E_acc = np.ones((3, timesteps))*np.inf
for charge in self.charge_pair:
charge.t_index = 0
charge.dt = dt
charge.position = np.ones((3, timesteps))*np.inf
charge.velocity = np.ones((3, timesteps))*np.inf
charge.acceleration = np.ones((3, timesteps))*np.inf
m_frac = self.m[1]/(self.m[0]+self.m[1]) # Determine COM
if charge.positive_charge: # Set initial position
charge.position[:, 0] = self.origin(0) + self.initial_r*m_frac
else:
charge.position[:, 0] = (self.origin(0)
- self.initial_r*(1-m_frac))
charge.velocity[:, 0] = 0
charge.acceleration[:, 0] = 0
update_timestep(self, moment_disp, moment_vel, moment_acc, E_driving)
Update the array attributes at each time step.
Source code in pycharge/dipole.py
def update_timestep(
self,
moment_disp: ndarray,
moment_vel: ndarray,
moment_acc: ndarray,
E_driving: Union[None, Tuple[ndarray, ndarray, ndarray]]
) -> None:
"""Update the array attributes at each time step."""
self.t_index += 1
self.moment_disp[:, self.t_index] = moment_disp
self.moment_vel[:, self.t_index] = moment_vel
self.moment_acc[:, self.t_index] = moment_acc
if E_driving is not None:
self.E_total[:, self.t_index] = E_driving[0]
self.E_vel[:, self.t_index] = E_driving[1]
self.E_acc[:, self.t_index] = E_driving[2]
t = self.dt*self.t_index
h = 1e-21 # Limit used to calcuate derivatives of the origin position
origin_vel = (self.origin(t+h)-self.origin(t-h))/(2*h)
origin_acc = (self.origin(t+h)-2*self.origin(t)+self.origin(t-h))/h**2
m_frac = self.m[1]/(self.m[0]+self.m[1]) # Determine COM
self.charge_pair[0].update_timestep( # Update first charge
self.origin(t)+moment_disp*m_frac,
origin_vel+moment_vel*m_frac, origin_acc+moment_acc*m_frac
)
self.charge_pair[1].update_timestep( # Update second charge
self.origin(t)-moment_disp*(1-m_frac),
origin_vel-moment_vel*(1-m_frac), origin_acc-moment_acc*(1-m_frac)
)