import numpy as np
from spherical_functions import LM_total_size
from .. import ModesTimeSeries
from .. import Inertial
[docs]
class AsymptoticBondiData:
"""Class to store asymptotic Bondi data
This class stores time data, along with the corresponding values of psi0 through psi4 and sigma.
For simplicity, the data are stored as one contiguous array. That is, *all* values are stored
at all times, even if they are zero, and all Modes objects are stored with ell_min=0, even when
their spins are not zero.
The single contiguous array is then viewed as 6 separate ModesTimeSeries objects, which enables
them to track their spin weights, and provides various convenient methods like `eth` and
`ethbar`; `dot` and `ddot` for time-derivatives; `int` and `iint` for time-integrations; `norm`
to take the norm of a function over the sphere; `bar` for conjugation of the functions (which is
different from just conjugating the mode weights); etc. It also handles algebra correctly --
particularly addition (which is disallowed when the spin weights differ) and multiplication
(which can be delicate with regards to the resulting ell values).
This may lead to some headaches when the user tries to do things that are disabled by Modes
objects. The goal is to create headaches if and only if the user is trying to do things that
really should never be done (like conjugating mode weights, rather than the underlying function;
adding modes with different spin weights; etc.). Please open issues for any situations that
don't meet this standard.
This class also provides various convenience methods for computing things like the mass aspect,
the Bondi four-momentum, the Bianchi identities, etc.
"""
def __init__(self, time, ell_max, multiplication_truncator=sum, frameType=Inertial):
"""Create new storage for asymptotic Bondi data
Parameters
==========
time: int or array_like
Times at which the data will be stored. If this is an int, an empty array of that size
will be created. Otherwise, this must be a 1-dimensional array of floats.
ell_max: int
Maximum ell value to be stored
multiplication_truncator: callable [defaults to `sum`, even though `max` is nicer]
Function to be used by default when multiplying Modes objects together. See the
documentation for spherical_functions.Modes.multiply for more details. The default
behavior with `sum` is the most correct one -- keeping all ell values that result -- but
also the most wasteful, and very likely to be overkill. The user should probably always
use `max`. (Unfortunately, this must remain an opt-in choice, to ensure that the user
is aware of the situation.)
"""
import functools
if np.ndim(time) == 0:
# Assume this is just the size of the time array; construct an empty array
time = np.empty((time,), dtype=float)
elif np.ndim(time) > 1:
raise ValueError(f"Input `time` parameter must be an integer or a 1-d array; it has shape {time.shape}")
if time.dtype != float:
raise ValueError(f"Input `time` parameter must have dtype float; it has dtype {time.dtype}")
ModesTS = functools.partial(ModesTimeSeries, ell_max=ell_max, multiplication_truncator=multiplication_truncator)
shape = [6, time.size, LM_total_size(0, ell_max)]
self.frame = np.array([])
self.frameType = frameType
self._time = time.copy()
self._raw_data = np.zeros(shape, dtype=complex)
self._psi0 = ModesTS(self._raw_data[0], self._time, spin_weight=2)
self._psi1 = ModesTS(self._raw_data[1], self._time, spin_weight=1)
self._psi2 = ModesTS(self._raw_data[2], self._time, spin_weight=0)
self._psi3 = ModesTS(self._raw_data[3], self._time, spin_weight=-1)
self._psi4 = ModesTS(self._raw_data[4], self._time, spin_weight=-2)
self._sigma = ModesTS(self._raw_data[5], self._time, spin_weight=2)
@property
def time(self):
return self._time
@time.setter
def time(self, new_time):
self._time[:] = new_time
return self._time
u = time
t = time
@property
def n_times(self):
return self.time.size
@property
def n_modes(self):
return self._raw_data.shape[-1]
@property
def ell_min(self):
return self._psi2.ell_min
@property
def ell_max(self):
return self._psi2.ell_max
@property
def LM(self):
return self.psi2.LM
@property
def sigma(self):
return self._sigma
@sigma.setter
def sigma(self, sigmaprm):
self._sigma[:] = sigmaprm
return self.sigma
@property
def psi4(self):
return self._psi4
@psi4.setter
def psi4(self, psi4prm):
self._psi4[:] = psi4prm
return self.psi4
@property
def psi3(self):
return self._psi3
@psi3.setter
def psi3(self, psi3prm):
self._psi3[:] = psi3prm
return self.psi3
@property
def psi2(self):
return self._psi2
@psi2.setter
def psi2(self, psi2prm):
self._psi2[:] = psi2prm
return self.psi2
@property
def psi1(self):
return self._psi1
@psi1.setter
def psi1(self, psi1prm):
self._psi1[:] = psi1prm
return self.psi1
@property
def psi0(self):
return self._psi0
@psi0.setter
def psi0(self, psi0prm):
self._psi0[:] = psi0prm
return self.psi0
[docs]
def copy(self):
import copy
new_abd = type(self)(self.t, self.ell_max)
state = copy.deepcopy(self.__dict__)
new_abd.__dict__.update(state)
return new_abd
[docs]
def interpolate(self, new_times):
new_abd = type(self)(new_times, self.ell_max)
new_abd.frameType = self.frameType
# interpolate waveform data
new_abd.sigma = self.sigma.interpolate(new_times)
new_abd.psi4 = self.psi4.interpolate(new_times)
new_abd.psi3 = self.psi3.interpolate(new_times)
new_abd.psi2 = self.psi2.interpolate(new_times)
new_abd.psi1 = self.psi1.interpolate(new_times)
new_abd.psi0 = self.psi0.interpolate(new_times)
# interpolate frame data if necessary
if self.frame.shape[0] == self.n_times:
import quaternion
new_abd.frame = quaternion.squad(self.frame, self.t, new_times)
return new_abd
from .from_initial_values import from_initial_values
from .constraints import (
bondi_constraints,
bondi_violations,
bondi_violation_norms,
bianchi_0,
bianchi_1,
bianchi_2,
constraint_3,
constraint_4,
constraint_mass_aspect,
)
from .transformations import transform
from .bms_charges import (
mass_aspect,
bondi_rest_mass,
bondi_four_momentum,
bondi_angular_momentum,
CWWY_angular_momentum,
bondi_dimensionless_spin,
bondi_boost_charge,
bondi_CoM_charge,
supermomentum,
)
from .map_to_superrest_frame import map_to_superrest_frame
from .map_to_abd_frame import map_to_abd_frame