def _get_coorbital_frame_spins_at_idx(self, chiA, chiB, omega, lNhat, phi, \ idx): """ Computes PN spins and dynamics at a given idx. Inputs: chiA: Dimless spin evolution of BhA in inertial frame. chiB: Dimless spin evolution of BhB in inertial frame. omega: Orbital frequency evolution in dimless units. lNhat: Orbital angular momentum direction evolution. phi: Orbital phase evolution. idx: Index for output. Outputs (all are time series): chiA_at_idx_coorb: Spin of BhA at idx, in coorbital frame. chiB_at_idx_coorb: Spin of BhB at idx, in coorbital frame. quat_copr_at_idx: Coprecessing frame quaternion at idx. phi_at_idx: Orbital phase in the coprecessing frame at idx. omega_at_idx Dimensionless orbital frequency at idx. The inertial frame is assumed to be aligned to the coorbital frame at the first index. """ # Compute omega, inertial spins, angular momentum direction and orbital # phase at idx omega_at_idx = omega[idx] chiA_at_idx = chiA[idx] chiB_at_idx = chiB[idx] lNhat_at_idx = lNhat[idx] phi_at_idx = phi[idx] # Align the z-direction along orbital angular momentum direction # at idx. This moves us in to the coprecessing frame. quat_copr_at_idx = utils.alignVec_quat(lNhat_at_idx) chiA_at_idx_copr = utils.transformTimeDependentVector( np.array([quat_copr_at_idx]).T, np.array([chiA_at_idx]).T, inverse=1).T[0] chiB_at_idx_copr = utils.transformTimeDependentVector( np.array([quat_copr_at_idx]).T, np.array([chiB_at_idx]).T, inverse=1).T[0] # get coorbital frame spins at idx chiA_at_idx_coorb = utils.rotate_in_plane(chiA_at_idx_copr, phi_at_idx) chiB_at_idx_coorb = utils.rotate_in_plane(chiB_at_idx_copr, phi_at_idx) return chiA_at_idx_coorb, chiB_at_idx_coorb, quat_copr_at_idx, \ phi_at_idx, omega_at_idx
def _evolve_spins(self, q, chiA0, chiB0, omega0, \ return_spin_evolution=False, **kwargs): """ Evolves spins of the component BHs from an initial orbital frequency = omega0 until t=-100 M from the peak of the waveform. If omega0 < omega_switch_IG, use PN to evolve the spins until t=t_sur_switch. Then evolves further with the NRSur7dq4 waveform model until t=-100M from the peak. Returns spins in the coorbital frame at t=-100M, as well as the coprecessing frame quaternion and orbital phase in the coprecessing frame at this time. If return_spin_evolution is given, also returns the PN and surrogate spin times series. """ PN_approximant = kwargs.pop('PN_approximant', 'SpinTaylorT4') PN_dt = kwargs.pop('PN_dt', 0.1) PN_spin_order = kwargs.pop('PN_spin_order', 7) PN_phase_order = kwargs.pop('PN_phase_order', 7) # Initial guess for surrogate omega0, this should be large enough for # all q=6 cases omega_switch_IG = kwargs.pop('omega_switch_IG', 0.03) # The surrogate begins at -4300, use -4100 to be safe t_sur_switch = kwargs.pop('t_sur_switch', -4100) self._check_unused_kwargs(kwargs) # Load NRSur7dq4 if not previously loaded if self.nrsur is None: self._load_NRSur7dq4() # If omega0 is below the NRSur7dq4 initial guess frequency, we use PN # to evolve the spins. We get the initial spins and omega_init_sur such # that should go into the surrogate such that the inital time is # t_sur_switch. if omega0 < omega_switch_IG: chiA0_nrsur_coorb, chiB0_nrsur_coorb, quat0_nrsur_copr, \ phi0_nrsur, omega_init_sur, chiA_PN, chiB_PN, omega_PN \ = self._get_PN_spins_at_surrogate_start(PN_approximant, q, \ omega0, chiA0, chiB0, PN_dt, PN_spin_order, PN_phase_order, \ omega_switch_IG, t_sur_switch) # If omega0 >= omega_switch_IG, we evolve spins directly with NRSur7dq4 # waveform model. We set the coprecessing frame quaternion to identity # and orbital phase to 0 at omega=omega0, hence the coprecessing frame # is the same as the inertial frame here. else: # Note that here we set omega_init_sur to omega0 chiA0_nrsur_coorb, chiB0_nrsur_coorb, quat0_nrsur_copr, \ phi0_nrsur, omega_init_sur, chiA_PN, chiB_PN, omega_PN \ = chiA0, chiB0, [1,0,0,0], 0, omega0, None, None, None # Now evaluate the surrogate dynamics using PN spins at omega_init_sur quat_sur, orbphase_sur, chiA_copr_sur, chiB_copr_sur \ = self.nrsur._sur_dimless.get_dynamics(q, chiA0_nrsur_coorb, \ chiB0_nrsur_coorb, init_quat=quat0_nrsur_copr, init_orbphase=phi0_nrsur, omega_ref=omega_init_sur) # get data at time node where remnant fits are done dyn_times = self.nrsur._sur_dimless.tds nodeIdx = np.argmin(np.abs(dyn_times - self.fitnode_time)) quat_fitnode = quat_sur.T[nodeIdx] orbphase_fitnode = orbphase_sur[nodeIdx] # get coorbital frame spins at the time node chiA_coorb_fitnode = utils.rotate_in_plane(chiA_copr_sur[nodeIdx], orbphase_fitnode) chiB_coorb_fitnode = utils.rotate_in_plane(chiB_copr_sur[nodeIdx], orbphase_fitnode) if return_spin_evolution: # Transform spins to the reference inertial frame chiA_inertial_sur = utils.transformTimeDependentVector(quat_sur, \ chiA_copr_sur.T).T chiB_inertial_sur = utils.transformTimeDependentVector(quat_sur, \ chiB_copr_sur.T).T spin_evolution = { 't_sur': dyn_times, 'chiA_sur': chiA_inertial_sur, 'chiB_sur': chiB_inertial_sur, 'orbphase_sur': orbphase_sur, 'quat_sur': quat_sur, 'omega_PN': omega_PN, 'chiA_PN': chiA_PN, 'chiB_PN': chiB_PN, 'omega_init_sur': omega_init_sur, } else: spin_evolution = None return chiA_coorb_fitnode, chiB_coorb_fitnode, quat_fitnode, \ orbphase_fitnode, spin_evolution
def _evolve_spins(self, q, chiA0, chiB0, omega0, PN_approximant, PN_dt, PN_spin0, PN_phase0, omega0_nrsur): """ Evolves spins of the component BHs from an initial orbital frequency = omega0 until t=-100 M from the peak of the waveform. If omega0 < omega0_nrsur, use PN to evolve the spins until orbital frequency = omega0. Then evolves further with the NRSur7dq2 waveform model until t=-100M from the peak. Assumes chiA0 and chiB0 are defined in the inertial frame defined at orbital frequency = omega0 as: The z-axis is along the Newtonian orbital angular momentum when the PN orbital frequency = omega0. The x-axis is along the line of separation from the smaller BH to the larger BH at this frequency. The y-axis completes the triad. Returns spins in the coorbital frame at t=-100M, as well as the coprecessing frame quaternion and orbital phase in the coprecessing frame at this time. """ if omega0 < omega0_nrsur: # If omega0 is below the NRSur7dq2 start frequency, we use PN # to evolve the spins until orbital frequency = omega0_nrsur. # Note that we update omega0_nrsur here with the PN # frequency that was closest to the input omega0_nrsur. chiA0_nrsur_copr, chiB0_nrsur_copr, quat0_nrsur_copr, \ phi0_nrsur, omega0_nrsur \ = evolve_pn_spins(q, chiA0, chiB0, omega0, omega0_nrsur, approximant=PN_approximant, dt=PN_dt, spinO=PN_spin0, phaseO=PN_phase0) else: # If omega0>= omega0_nrsur, we evolve spins directly with NRSur7dq2 # waveform model. We set the coprecessing frame quaternion to # identity and orbital phase to 0 at omega=omega0, hence the # coprecessing frame is the same as the inertial frame here. # Note that we update omega0_nrsur here and set it to omega0 chiA0_nrsur_copr, chiB0_nrsur_copr, quat0_nrsur_copr, \ phi0_nrsur, omega0_nrsur \ = chiA0, chiB0, [1,0,0,0], 0, omega0 # Load NRSur7dq2 if needed if self.nrsur is None: self._load_NRSur7dq2() # evaluate NRSur7dq2 dynamics # We set allow_extrapolation=True always since we test param limits # independently quat, orbphase, chiA_copr, chiB_copr = self.nrsur.get_dynamics( q, chiA0_nrsur_copr, chiB0_nrsur_copr, init_quat=quat0_nrsur_copr, init_phase=phi0_nrsur, omega_ref=omega0_nrsur, allow_extrapolation=True) # get data at time node where remnant fits are done fitnode_time = -100 nodeIdx = np.argmin(np.abs(self.nrsur.tds - fitnode_time)) quat_fitnode = quat.T[nodeIdx] orbphase_fitnode = orbphase[nodeIdx] # get coorbital frame spins at the time node chiA_coorb_fitnode = utils.rotate_in_plane(chiA_copr[nodeIdx], orbphase_fitnode) chiB_coorb_fitnode = utils.rotate_in_plane(chiB_copr[nodeIdx], orbphase_fitnode) return chiA_coorb_fitnode, chiB_coorb_fitnode, quat_fitnode, \ orbphase_fitnode