File: test_amuse.py

package info (click to toggle)
galpy 1.10.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 7,336 kB
  • sloc: python: 117,850; ansic: 13,779; makefile: 4
file content (310 lines) | stat: -rw-r--r-- 10,237 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# Test consistency between galpy and amuse
import numpy
from amuse.couple import bridge
from amuse.datamodel import Particles
from amuse.lab import *  # nopycln: import
from astropy import units as apy_u

from galpy import potential
from galpy.orbit import Orbit
from galpy.potential import to_amuse
from galpy.util import conversion


def test_amuse_potential_with_physical():
    ro, vo = 8.0, 220.0
    amp = 1e8 / conversion.mass_in_msol(ro=ro, vo=vo)
    a = 0.8 / ro

    amp_u = 1e8 * apy_u.solMass
    a_u = 0.8 * apy_u.kpc
    ro_u, vo_u = 8.0 * apy_u.kpc, 220.0 * apy_u.km / apy_u.s

    x, y, z = 3 | units.kpc, 4 | units.kpc, 2 | units.kpc
    r = 5 | units.kpc

    # ------------------------------------
    # get_potential_at_point
    pot1 = potential.TwoPowerSphericalPotential(amp=amp, a=a, ro=ro, vo=vo)
    gg1 = pot1(5 / ro, 2 / ro)
    gg1 = gg1.to_value(apy_u.km**2 / apy_u.s**2) if hasattr(gg1, "unit") else gg1
    amuse_pot1 = to_amuse(pot1)
    ag1 = amuse_pot1.get_potential_at_point(0, x, y, z)

    assert numpy.abs(gg1 - ag1.value_in(units.kms**2)) < 1e-10

    pot2 = potential.TwoPowerSphericalPotential(amp=amp_u, a=a_u, ro=ro_u, vo=vo_u)
    gg2 = pot1(5 * apy_u.kpc, 2 * apy_u.kpc)
    gg2 = gg2.to_value(apy_u.km**2 / apy_u.s**2) if hasattr(gg2, "unit") else gg2
    amuse_pot2 = to_amuse(pot2)
    ag2 = amuse_pot2.get_potential_at_point(0, x, y, z)

    assert numpy.abs(gg2 - ag2.value_in(units.kms**2)) < 1e-10

    assert numpy.abs(ag1 - ag2) < 1e-10 | units.kms**2

    # ------------------------------------
    # test get_gravity_at_point
    pot1 = potential.TwoPowerSphericalPotential(amp=amp, a=a, ro=ro, vo=vo)
    amuse_pot1 = to_amuse(pot1)
    ax1, ay1, az1 = amuse_pot1.get_gravity_at_point(0, x, y, z)

    pot2 = potential.TwoPowerSphericalPotential(amp=amp_u, a=a_u, ro=ro_u, vo=vo_u)
    amuse_pot2 = to_amuse(pot2)
    ax2, ay2, az2 = amuse_pot2.get_gravity_at_point(0, x, y, z)

    assert numpy.abs(ax1 - ax2) < 1e-10 | units.kms / units.Myr
    assert numpy.abs(ay1 - ay2) < 1e-10 | units.kms / units.Myr
    assert numpy.abs(az1 - az2) < 1e-10 | units.kms / units.Myr

    # ------------------------------------
    # test mass_density
    pot1 = potential.TwoPowerSphericalPotential(amp=amp, a=a, ro=ro, vo=vo)
    grho1 = pot1.dens(5 / ro, 2 / ro)
    grho1 = (
        grho1.to_value(apy_u.solMass / apy_u.pc**3) if hasattr(grho1, "unit") else grho1
    )
    amuse_pot1 = to_amuse(pot1)
    arho1 = amuse_pot1.mass_density(x, y, z)

    assert numpy.abs(grho1 - arho1.value_in(units.MSun / units.parsec**3)) < 1e-10

    pot2 = potential.TwoPowerSphericalPotential(amp=amp_u, a=a_u, ro=ro_u, vo=vo_u)
    grho2 = pot2.dens(5 * apy_u.kpc, 2 * apy_u.kpc)
    grho2 = (
        grho2.to_value(apy_u.solMass / apy_u.pc**3) if hasattr(grho2, "unit") else grho2
    )
    amuse_pot2 = to_amuse(pot2)
    arho2 = amuse_pot2.mass_density(x, y, z)

    assert numpy.abs(grho2 - arho2.value_in(units.MSun / units.parsec**3)) < 1e-10

    assert numpy.abs(arho1 - arho2) < 1e-10 | units.MSun / units.parsec**3

    # ------------------------------------
    # test circular_velocity
    pot1 = potential.TwoPowerSphericalPotential(amp=amp, a=a, ro=ro, vo=vo)
    gv1 = pot1.vcirc(1 * apy_u.kpc)
    gv1 = gv1.to_value(apy_u.km / apy_u.s) if hasattr(gv1, "unit") else gv1
    amuse_pot1 = to_amuse(pot1)
    av1 = amuse_pot1.circular_velocity(1 | units.kpc)

    assert numpy.abs(gv1 - av1.value_in(units.kms)) < 1e-10

    pot2 = potential.TwoPowerSphericalPotential(amp=amp_u, a=a_u, ro=ro_u, vo=vo_u)
    gv2 = pot2.vcirc(1 * apy_u.kpc)
    gv2 = gv2.to_value(apy_u.km / apy_u.s) if hasattr(gv2, "unit") else gv2
    amuse_pot2 = to_amuse(pot2)
    av2 = amuse_pot2.circular_velocity(1 | units.kpc)

    assert numpy.abs(gv2 - av2.value_in(units.kms)) < 1e-10

    assert numpy.abs(av1 - av2) < 1e-10 | units.kms

    # ------------------------------------
    # test enclosed_mass

    pot1 = potential.TwoPowerSphericalPotential(amp=amp, a=a, ro=ro, vo=vo)
    gm1 = pot1.mass(1 / ro)
    gm1 = gm1.to_value(apy_u.solMass) if hasattr(gm1, "unit") else gm1
    amuse_pot1 = to_amuse(pot1)
    am1 = amuse_pot1.enclosed_mass(1 | units.kpc)

    assert numpy.abs(gm1 - am1.value_in(units.MSun)) < 3e-8

    pot2 = potential.TwoPowerSphericalPotential(amp=amp_u, a=a_u, ro=ro_u, vo=vo_u)
    gm2 = pot2.mass(1 * apy_u.kpc)
    gm2 = gm2.to_value(apy_u.solMass) if hasattr(gm2, "unit") else gm2
    amuse_pot2 = to_amuse(pot2)
    am2 = amuse_pot2.enclosed_mass(1 | units.kpc)

    assert numpy.abs(gm2 - am2.value_in(units.MSun)) < 3e-8

    assert numpy.abs(am1 - am2) < 1e-10 | units.MSun

    return None


def test_amuse_MN3ExponentialDiskPotential():
    mn = potential.MN3ExponentialDiskPotential(normalize=1.0, hr=0.5, hz=0.1)
    tmax = 3.0
    vo, ro = 215.0, 8.75
    o = Orbit([1.0, 0.1, 1.1, 0.3, 0.1, 0.4], ro=ro, vo=vo)
    run_orbitIntegration_comparison(o, mn, tmax, vo, ro)
    return None


def test_amuse_MiyamotoNagaiPotential():
    mp = potential.MiyamotoNagaiPotential(normalize=1.0, a=0.5, b=0.1)
    tmax = 4.0
    vo, ro = 220.0, 8.0
    o = Orbit([1.0, 0.1, 1.1, 0.3, 0.1, 0.4], ro=ro, vo=vo)
    run_orbitIntegration_comparison(o, mp, tmax, vo, ro)
    return None


def test_amuse_NFWPotential():
    np = potential.NFWPotential(normalize=1.0, a=3.0)
    tmax = 3.0
    vo, ro = 200.0, 7.0
    o = Orbit([1.0, 0.5, 1.3, 0.3, 0.1, 0.4], ro=ro, vo=vo)
    run_orbitIntegration_comparison(o, np, tmax, vo, ro)
    return None


def test_amuse_HernquistPotential():
    hp = potential.HernquistPotential(normalize=1.0, a=3.0)
    tmax = 3.0
    vo, ro = 210.0, 7.5
    o = Orbit([1.0, 0.25, 1.4, 0.3, -0.1, 0.4], ro=ro, vo=vo)
    run_orbitIntegration_comparison(o, hp, tmax, vo, ro, tol=0.02)
    return None


def test_amuse_PowerSphericalPotentialwCutoffPotential():
    pp = potential.PowerSphericalPotentialwCutoff(normalize=1.0, alpha=1.0, rc=0.4)
    tmax = 2.0
    vo, ro = 180.0, 9.0
    o = Orbit([1.0, 0.03, 1.03, 0.2, 0.1, 0.4], ro=ro, vo=vo)
    run_orbitIntegration_comparison(o, pp, tmax, vo, ro)
    return None


def test_amuse_LogarithmicHaloPotential():
    lp = potential.LogarithmicHaloPotential(normalize=1.0)
    tmax = 2.0
    vo, ro = 210.0, 8.5
    o = Orbit([1.0, 0.1, 1.1, 0.3, 0.1, 0.4], ro=ro, vo=vo)
    run_orbitIntegration_comparison(o, lp, tmax, vo, ro, tol=0.03)
    return None


def test_amuse_PlummerPotential():
    pp = potential.PlummerPotential(normalize=1.0, b=2.0)
    tmax = 3.0
    vo, ro = 213.0, 8.23
    o = Orbit([1.0, 0.1, 1.1, 0.3, 0.1, 0.4], ro=ro, vo=vo)
    run_orbitIntegration_comparison(o, pp, tmax, vo, ro, tol=0.03)
    return None


def test_amuse_MWPotential2014():
    mp = potential.MWPotential2014
    tmax = 3.5
    vo, ro = 220.0, 8.0
    o = Orbit([1.0, 0.1, 1.1, 0.2, 0.1, 1.4], ro=ro, vo=vo)
    run_orbitIntegration_comparison(o, mp, tmax, vo, ro)
    return None


def run_orbitIntegration_comparison(orb, pot, tmax, vo, ro, tol=0.01):
    # Integrate in galpy
    ts = numpy.linspace(0.0, tmax / conversion.time_in_Gyr(vo, ro), 1001)
    orb.integrate(ts, pot)

    # Integrate with amuse
    x, y, z, vx, vy, vz = integrate_amuse(orb, pot, tmax | units.Gyr, vo, ro)

    # Read and compare

    xdiff = numpy.fabs((x - orb.x(ts[-1])) / x)
    ydiff = numpy.fabs((y - orb.y(ts[-1])) / y)
    zdiff = numpy.fabs((z - orb.z(ts[-1])) / z)
    vxdiff = numpy.fabs((vx - orb.vx(ts[-1])) / vx)
    vydiff = numpy.fabs((vy - orb.vy(ts[-1])) / vy)
    vzdiff = numpy.fabs((vz - orb.vz(ts[-1])) / vz)
    assert xdiff < tol, (
        "galpy and amuse orbit integration inconsistent for x by %g" % xdiff
    )
    assert ydiff < tol, (
        "galpy and amuse orbit integration inconsistent for y by %g" % ydiff
    )
    assert zdiff < tol, (
        "galpy and amuse orbit integration inconsistent for z by %g" % zdiff
    )
    assert vxdiff < tol, (
        "galpy and amuse orbit integration inconsistent for vx by %g" % vxdiff
    )
    assert vydiff < tol, (
        "galpy and amuse orbit integration inconsistent for vy by %g" % vydiff
    )
    assert vzdiff < tol, (
        "galpy and amuse orbit integration inconsistent for vz by %g" % vzdiff
    )

    return None


def integrate_amuse(orb, pot, tmax, vo, ro):
    """Integrate a snapshot in infile until tmax in Gyr, save to outfile"""

    time = 0.0 | tmax.unit
    dt = tmax / 10001.0

    orbit = Particles(1)

    orbit.mass = 1.0 | units.MSun
    orbit.radius = 1.0 | units.RSun

    orbit.position = [orb.x(), orb.y(), orb.z()] | units.kpc
    orbit.velocity = [orb.vx(), orb.vy(), orb.vz()] | units.kms
    galaxy_code = to_amuse(pot, ro=ro, vo=vo)

    orbit_gravity = drift_without_gravity(orbit)
    orbit_gravity.particles.add_particles(orbit)
    channel_from_gravity_to_orbit = orbit_gravity.particles.new_channel_to(orbit)

    gravity = bridge.Bridge(use_threading=False)
    gravity.add_system(orbit_gravity, (galaxy_code,))
    gravity.add_system(
        galaxy_code,
    )
    gravity.timestep = dt

    while time <= tmax:
        time += dt
        gravity.evolve_model(time)

    channel_from_gravity_to_orbit.copy()
    gravity.stop()

    return (
        orbit.x[0].value_in(units.kpc),
        orbit.y[0].value_in(units.kpc),
        orbit.z[0].value_in(units.kpc),
        orbit.vx[0].value_in(units.kms),
        orbit.vy[0].value_in(units.kms),
        orbit.vz[0].value_in(units.kms),
    )


class drift_without_gravity:
    def __init__(self, convert_nbody, time=0 | units.Myr):
        self.model_time = time
        self.particles = Particles()

    def evolve_model(self, t_end):
        dt = t_end - self.model_time
        self.particles.position += self.particles.velocity * dt
        self.model_time = t_end

    @property
    def potential_energy(self):
        return quantities.zero

    @property
    def get_potential_at_point(self):
        return quantities.zero

    @property
    def kinetic_energy(self):
        return (
            0.5 * self.particles.mass * self.particles.velocity.lengths() ** 2
        ).sum()

    @property
    def angular_momenum(self):
        return numpy.cross(self.particles.position, self.particles.velocity)

    def stop(self):
        pass