def estimate_dt_nsteps(w0, potential, nperiods, nsteps_per_period, dE_threshold=1E-9, return_periods=False): """ Estimate the timestep and number of steps to integrate for given a potential and set of initial conditions. Parameters ---------- w0 : array_like potential : :class:`~gary.potential.Potential` nperiods : int Number of (max) periods to integrate. nsteps_per_period : int Number of steps to take per (max) orbital period. dE_threshold : numeric (optional) Maximum fractional energy difference -- used to determine initial timestep. Set to ``None`` to ignore this. return_periods : bool (optional) Also return the estimated periods for the orbit. """ # integrate orbit dt = _autodetermine_initial_dt(w0, potential, dE_threshold=dE_threshold) nsteps = int(round(10000 / dt)) t,w = potential.integrate_orbit(w0, dt=dt, nsteps=nsteps) # if loop, align circulation with Z and take R period loop = gd.classify_orbit(w[:,0]) if np.any(loop): w = gd.align_circulation_with_z(w[:,0], loop) # convert to cylindrical coordinates R = np.sqrt(w[:,0]**2 + w[:,1]**2) phi = np.arctan2(w[:,1], w[:,0]) z = w[:,2] T = np.array([gd.peak_to_peak_period(t, f) for f in [R, phi, z]]) else: T = np.array([gd.peak_to_peak_period(t, f) for f in w.T[:3,0]]) # timestep from number of steps per period Tmax = T.max() if np.isnan(Tmax): T = T[np.isfinite(T)] Tmax = T.max() if np.isnan(Tmax): raise RuntimeError("Failed to find period.") dt = float(Tmax) / float(nsteps_per_period) nsteps = int(round(nperiods * Tmax / dt)) if dt == 0.: raise ValueError("Timestep is zero!") if return_periods: return dt, nsteps, T else: return dt, nsteps
def elliptical_orbit_to_events(t, w): """ Convert an orbit to MIDI events using Cartesian coordinates and rules. Parameters ---------- t : array_like w : array_like midi_pool : array_like """ loop = gd.classify_orbit(w) # apocenters x,y,z = w.T[:3] r = np.sqrt(x**2 + y**2 + z**2) apo = np.array([argrelmax(rr)[0] for rr in r]) # get periods periods = [] for i in range(w.shape[1]): if np.any(loop[i] == 1): w2 = gd.align_circulation_with_z(w[:,i], loop[i]) R = np.sqrt(w2[:,0]**2 + w2[:,1]**2) phi = np.arctan2(w2[:,1], w2[:,0]) % (2*np.pi) z = w2[:,2] # loop T1 = gd.peak_to_peak_period(t, R) T2 = gd.peak_to_peak_period(t, phi) T3 = gd.peak_to_peak_period(t, z) else: # box T1 = gd.peak_to_peak_period(t, w[:,i,0]) T2 = gd.peak_to_peak_period(t, w[:,i,1]) T3 = gd.peak_to_peak_period(t, w[:,i,2]) periods.append([T1,T2,T3]) freqs = (2*np.pi / np.array(periods)) * 10000. delays = [] notes = [] for j in range(w.shape[0]): _no = [] for i in range(w.shape[1]): if j in apo[i]: _no.append(freqs[i].tolist()) if len(_no) > 0: delays.append(t[j]) notes.append(np.unique(_no).tolist()) delays = np.array(delays) notes = np.array(notes) return delays, notes
def elliptical_orbit_to_events(t, w): """ Convert an orbit to MIDI events using Cartesian coordinates and rules. Parameters ---------- t : array_like w : array_like midi_pool : array_like """ loop = gd.classify_orbit(w) # apocenters x, y, z = w.T[:3] r = np.sqrt(x**2 + y**2 + z**2) apo = np.array([argrelmax(rr)[0] for rr in r]) # get periods periods = [] for i in range(w.shape[1]): if np.any(loop[i] == 1): w2 = gd.align_circulation_with_z(w[:, i], loop[i]) R = np.sqrt(w2[:, 0]**2 + w2[:, 1]**2) phi = np.arctan2(w2[:, 1], w2[:, 0]) % (2 * np.pi) z = w2[:, 2] # loop T1 = gd.peak_to_peak_period(t, R) T2 = gd.peak_to_peak_period(t, phi) T3 = gd.peak_to_peak_period(t, z) else: # box T1 = gd.peak_to_peak_period(t, w[:, i, 0]) T2 = gd.peak_to_peak_period(t, w[:, i, 1]) T3 = gd.peak_to_peak_period(t, w[:, i, 2]) periods.append([T1, T2, T3]) freqs = (2 * np.pi / np.array(periods)) * 10000. delays = [] notes = [] for j in range(w.shape[0]): _no = [] for i in range(w.shape[1]): if j in apo[i]: _no.append(freqs[i].tolist()) if len(_no) > 0: delays.append(t[j]) notes.append(np.unique(_no).tolist()) delays = np.array(delays) notes = np.array(notes) return delays, notes
def run(cls, w0, potential, **kwargs): c = dict() for k in cls.config_defaults.keys(): if k not in kwargs: c[k] = cls.config_defaults[k] else: c[k] = kwargs[k] # return dict result = dict() # get timestep and nsteps for integration try: # integrate orbit t,w = potential.integrate_orbit(w0.copy(), dt=0.2, nsteps=50000) # radial oscillations r = np.sqrt(np.sum(w[:,0,:3]**2, axis=-1)) T = gd.peak_to_peak_period(t, r) # timestep from number of steps per period dt = float(T) / float(c['nsteps_per_period']) nsteps = int(round(c['nperiods'] * T / dt)) except RuntimeError: logger.warning("Failed to integrate orbit when estimating dt,nsteps") result['success'] = False result['error_code'] = 1 return result # integrate orbit logger.debug("Integrating orbit with dt={0}, nsteps={1}".format(dt, nsteps)) try: t,ws = potential.integrate_orbit(w0.copy(), dt=dt, nsteps=nsteps, Integrator=gi.DOPRI853Integrator, Integrator_kwargs=dict(atol=1E-11)) except RuntimeError: # ODE integration failed logger.warning("Orbit integration failed.") dEmax = 1E10 else: logger.debug('Orbit integrated successfully, checking energy conservation...') # check energy conservation for the orbit E = potential.total_energy(ws[:,0,:3].copy(), ws[:,0,3:].copy()) dE = np.abs(E[1:] - E[0]) dEmax = dE.max() / np.abs(E[0]) logger.debug('max(∆E) = {0:.2e}'.format(dEmax)) if dEmax > c['energy_tolerance']: logger.warning("Failed due to energy conservation check.") result['freqs'] = np.ones((2,3))*np.nan result['success'] = False result['error_code'] = 2 return result # find apos, peris r = np.sqrt(np.sum(ws[:,0,:3]**2, axis=-1)) pc = r[argrelmin(r)[0]] ac = r[argrelmax(r)[0]] pc.resize(c['nperiods']+2) ac.resize(c['nperiods']+2) result['dE_max'] = dEmax result['dt'] = float(dt) result['nsteps'] = nsteps result['success'] = True result['error_code'] = 0 result['pericenters'] = pc result['apocenters'] = ac return result
def cyl_orbit_to_events2(t, w, midi_pool_hi, midi_pool_lo): """ Convert an orbit to MIDI events using cylindrical coordinates and rules. For cylindrical orbits, crossing the disk midplane (x-y plane) triggers a high note with pitch set by the vertical oscillation frequency. Crossing the x-z plane triggers a low note with pitch set by the azimuthal frequency. The radial oscillations modulate the volume of the note. Parameters ---------- t : array_like w : array_like """ ntimes,norbits,_ = w.shape R = np.sqrt(w[:,:,0]**2 + w[:,:,1]**2) phi = np.arctan2(w[:,:,1], w[:,:,0]) % (2*np.pi) z = w[:,:,2] # normalized R for oscillations normed_R = (R - R.min()) / (R.max() - R.min()) # variable length arrays phi_cross = np.array([argrelmin(pphi)[0] for pphi in phi.T]) z_cross = np.array([argrelmin(zz**2)[0] for zz in z.T]) # estimate periods T_z = np.array([gd.peak_to_peak_period(t, z[:,i]) for i in range(norbits)]) T_phi = np.array([gd.peak_to_peak_period(t, phi[:,i]) for i in range(norbits)]) # quantize the periods and map on to notes # q_z = quantize(T_z, nbins=len(midi_pool_hi), min=T_z.max(), max=T_z.min()) # q_phi = quantize(T_phi, nbins=len(midi_pool_lo), min=T_phi.max(), max=T_phi.min()) q_z = quantize(T_z, nbins=len(midi_pool_hi), min=120., max=30.) # TOTAL HACk q_phi = quantize(T_phi, nbins=len(midi_pool_lo), min=350., max=50.) # TOTAL HACk delays = [] notes = [] Rphase = [] for j in range(w.shape[0]): _no = [] _ph = [] for i in range(w.shape[1]): if j in z_cross[i]: _no.append(midi_pool_hi[q_z[i]]) _ph.append(normed_R[j,i]) if j in phi_cross[i]: _no.append(midi_pool_lo[q_phi[i]]) _ph.append(1.) if len(_no) > 0: delays.append(t[j]) notes.append(np.unique(_no).tolist()) Rphase.append(_ph) delays = np.array(delays) notes = np.array(notes) Rphase = np.array(Rphase) return delays, notes, Rphase
def cyl_orbit_to_events2(t, w, midi_pool_hi, midi_pool_lo): """ Convert an orbit to MIDI events using cylindrical coordinates and rules. For cylindrical orbits, crossing the disk midplane (x-y plane) triggers a high note with pitch set by the vertical oscillation frequency. Crossing the x-z plane triggers a low note with pitch set by the azimuthal frequency. The radial oscillations modulate the volume of the note. Parameters ---------- t : array_like w : array_like """ ntimes, norbits, _ = w.shape R = np.sqrt(w[:, :, 0]**2 + w[:, :, 1]**2) phi = np.arctan2(w[:, :, 1], w[:, :, 0]) % (2 * np.pi) z = w[:, :, 2] # normalized R for oscillations normed_R = (R - R.min()) / (R.max() - R.min()) # variable length arrays phi_cross = np.array([argrelmin(pphi)[0] for pphi in phi.T]) z_cross = np.array([argrelmin(zz**2)[0] for zz in z.T]) # estimate periods T_z = np.array( [gd.peak_to_peak_period(t, z[:, i]) for i in range(norbits)]) T_phi = np.array( [gd.peak_to_peak_period(t, phi[:, i]) for i in range(norbits)]) # quantize the periods and map on to notes # q_z = quantize(T_z, nbins=len(midi_pool_hi), min=T_z.max(), max=T_z.min()) # q_phi = quantize(T_phi, nbins=len(midi_pool_lo), min=T_phi.max(), max=T_phi.min()) q_z = quantize(T_z, nbins=len(midi_pool_hi), min=120., max=30.) # TOTAL HACk q_phi = quantize(T_phi, nbins=len(midi_pool_lo), min=350., max=50.) # TOTAL HACk delays = [] notes = [] Rphase = [] for j in range(w.shape[0]): _no = [] _ph = [] for i in range(w.shape[1]): if j in z_cross[i]: _no.append(midi_pool_hi[q_z[i]]) _ph.append(normed_R[j, i]) if j in phi_cross[i]: _no.append(midi_pool_lo[q_phi[i]]) _ph.append(1.) if len(_no) > 0: delays.append(t[j]) notes.append(np.unique(_no).tolist()) Rphase.append(_ph) delays = np.array(delays) notes = np.array(notes) Rphase = np.array(Rphase) return delays, notes, Rphase