Orbital classify

Orbit classification based on the resonance angle behaviour.

[1]:
import numpy as np
import matplotlib.pyplot as plt

import galport

Load coordinates, velocities and actions for the set of orbits

[2]:
xv_act = np.load('data/xv_act_0.npy')

t = np.arange(0, 600.125, 0.125)
xv = xv_act[:,:,0:6]
act = xv_act[:,:,6:9]

theta_p = np.load('data/phi_p.npy')
Omega_p = np.load('data/omega_p.npy')

Find actions, angles and frequencies of the every orbit and Orbit classification at time \(t=400\)

[3]:
%%time

OT = galport.OrbitTools(t=t, xv=xv, act=act)
data = OT.calculate_actions(secular=True, parallel=True)
CPU times: user 228 ms, sys: 129 ms, total: 357 ms
Wall time: 650 ms
[5]:
%%time

otype_ILR = OT.classify_orbits(t_out=400, family='ILR', theta_p=theta_p)
otype_vILR = OT.classify_orbits(t_out=400, family='vILR', theta_p=theta_p)
otype_uha = OT.classify_orbits(t_out=400, family='uha', theta_p=theta_p)
CPU times: user 42.3 ms, sys: 8.11 ms, total: 50.4 ms
Wall time: 49.5 ms

Equivalent code for classification (calling the OrbitClassifier module directly)

[38]:
ang = OT.angles
# or
ang = data[:, :, 3:6] # if dJdt=False
# ang = OT.data[:, :, 6:9] # if dJdt=True

OC = galport.OrbitClassifier(t, angles=ang, theta_p=theta_p, time_resolution=5.)
# Not all angles are used in the classification; they are taken with a time step of `time_resolution`
result = OC(t_out=400, family='ILR', time_around_res=False, amplitude_res=False)
# or, you can also set the resonant angle yourself
OC = galport.OrbitClassifier(t, angles=2*(ang[:,:,2] - theta_p) - ang[:,:,0], time_resolution=5.)
result = OC(t_out=400, time_around_res=True, amplitude_res=False)

List of types

0: Not classify

1: Increasing angle

2: Decreasing angle

3: Resonance around \(0\)

4: Resonance around \(\pi\)

5: Passage through \(0\) from \(\Omega_{res} > 0\) to \(\Omega_{res} < 0\)

6: Passage through \(\pi\) from \(\Omega_{res} > 0\) to \(\Omega_{res} < 0\)

7: Passage through \(0\) from \(\Omega_{res} < 0\) to \(\Omega_{res} > 0\)

8: Passage through \(\pi\) from \(\Omega_{res} < 0\) to \(\Omega_{res} > 0\)

Additional variables

time_around_res: returns the resonance entry and exit times for resonant orbits

amplitude_res: returns the maximum libration amplitude of the resonant angle

Secular actions and frequencies

[36]:
t0 = 400
n_t = int(t0)*8

JR_sec = data[:,n_t,9]
Jz_sec = data[:,n_t,10]
Lz_sec = data[:,n_t,11]

kappa_sec = data[:,n_t,12]
omegaz_sec = data[:,n_t,13]
Omega_sec = data[:,n_t,14] - Omega_p[n_t]

Plot different orbital types on the plane \(2(\Omega - \Omega_p)/\kappa\) vs \(\omega_z\kappa\)

[37]:
fig, ax = plt.subplots(figsize=(5,5))
ax.set_aspect('equal')

ax.grid(zorder=-1)

test_bar = (otype_ILR > 2)
test_notbarnotdisk = (otype_ILR <= 2) & (otype_uha == 1) & (otype_vILR == 2)
test_disk = ~test_bar & ~test_notbarnotdisk


ax.scatter(
    2*Omega_sec[test_bar]/kappa_sec[test_bar],
    omegaz_sec[test_bar]/kappa_sec[test_bar],
    s=3, label='Bar'
)

ax.scatter(
    2*Omega_sec[test_disk]/kappa_sec[test_disk],
    omegaz_sec[test_disk]/kappa_sec[test_disk],
    s=3, label='Disk'
)

ax.scatter(
    2*Omega_sec[test_notbarnotdisk]/kappa_sec[test_notbarnotdisk],
    omegaz_sec[test_notbarnotdisk]/kappa_sec[test_notbarnotdisk],
    s=3, label='not Bar, not Disk'
)


ax.set_xlabel('$2(\\Omega - \\Omega_p)/\\kappa$')
ax.set_ylabel('$\\omega_z\\kappa$')
ax.legend(
    loc='upper center',
    bbox_to_anchor=(0.5, 1.15),
    ncols=3
)

ax.set_xlim(-1.0,1.5)
ax.set_ylim(0.5, 2)

plt.show()
../_images/notebooks_classify_13_0.png

Orbits can be classified at different points in time. Classification for each point in time is parallelised.

[7]:
%%time
t_out = np.arange(100, 505, 5)
otypes_ILR = OT.classify_orbits(t_out=t_out, family='ILR', theta_p=theta_p, parallel=True, n_jobs=4)
otypes_vILR = OT.classify_orbits(t_out=t_out, family='vILR', theta_p=theta_p, parallel=True, n_jobs=4)
otypes_uha = OT.classify_orbits(t_out=t_out, family='uha', theta_p=theta_p, parallel=True, n_jobs=4)
CPU times: user 57.1 ms, sys: 166 ms, total: 223 ms
Wall time: 692 ms
[5]:
%%time
t_out = np.arange(100, 505, 5)
otypes_ILR = OT.classify_orbits(t_out=t_out, family='ILR', theta_p=theta_p, parallel=False)
otypes_vILR = OT.classify_orbits(t_out=t_out, family='vILR', theta_p=theta_p, parallel=False)
otypes_uha = OT.classify_orbits(t_out=t_out, family='uha', theta_p=theta_p, parallel=False)
CPU times: user 1.45 s, sys: 4.6 ms, total: 1.46 s
Wall time: 1.46 s
[41]:
fig, axes = plt.subplots(1, 4, figsize=(20,5))

i_plot = [0, 20, 40, 60]
t_plot = t_out[i_plot]
for j, (i, t) in enumerate(zip(i_plot, t_plot)):
    ax = axes[j]
    otype_ILR = otypes_ILR[i]
    otype_uha = otypes_uha[i]
    otype_vILR = otypes_vILR[i]

    n_t = int(t)*8
    JR_sec = data[:,n_t,9]
    Jz_sec = data[:,n_t,10]
    Lz_sec = data[:,n_t,11]

    kappa_sec = data[:,n_t,12]
    omegaz_sec = data[:,n_t,13]
    Omega_sec = data[:,n_t,14] - Omega_p[n_t]

    ax.set_aspect('equal')

    ax.grid(zorder=-1)

    test_bar = (otype_ILR > 2)
    test_notbarnotdisk = (otype_ILR <= 2) & (otype_uha == 1) & (otype_vILR == 2)
    test_disk = ~test_bar & ~test_notbarnotdisk


    ax.scatter(
        2*Omega_sec[test_bar]/kappa_sec[test_bar],
        omegaz_sec[test_bar]/kappa_sec[test_bar],
        s=3, label='Bar'
    )

    ax.scatter(
        2*Omega_sec[test_disk]/kappa_sec[test_disk],
        omegaz_sec[test_disk]/kappa_sec[test_disk],
        s=3, label='Disk'
    )

    ax.scatter(
        2*Omega_sec[test_notbarnotdisk]/kappa_sec[test_notbarnotdisk],
        omegaz_sec[test_notbarnotdisk]/kappa_sec[test_notbarnotdisk],
        s=3, label='not Bar, not Disk'
    )


    ax.set_xlabel('$2(\\Omega - \\Omega_p)/\\kappa$')
    ax.set_ylabel('$\\omega_z\\kappa$')
    ax.legend(
        loc='upper center',
        bbox_to_anchor=(0.5, 1.15),
        ncols=3
    )

    ax.text(
        0.82, 0.92, f"$t={t}$",
        transform=ax.transAxes
    )


    ax.set_xlim(-1.0,1.5)
    ax.set_ylim(0.5, 2.5)

plt.show()
../_images/notebooks_classify_17_0.png