""" Tests properties of :class:`choreo.NBodySyst`
.. autosummary::
:toctree: _generated/
:template: tests-formatting/base.rst
:nosignatures:
test_create_destroy
test_all_pos_to_segmpos
test_all_vel_to_segmvel
test_segmpos_to_all_pos
test_capture_co
test_round_trips_pos
test_round_trips_vel
test_changevars
test_params_segmpos_dual
test_kin
test_pot
test_action
test_resize
test_action_indep_resize
test_repeatability
test_ForceGeneralSym
test_ForceGreaterNstore
test_fft_backends
test_action_cst_sym_pairs
test_custom_inter_law
test_periodicity_default
test_ODE_vs_spectral
"""
import pytest
from .test_config import *
import numpy as np
import scipy
import choreo
[docs]
@ParametrizeDocstrings
@pytest.mark.parametrize("name", AllConfigNames_list)
def test_create_destroy(name):
""" Tests initialization and destruction of NBodySyst.
"""
NBS = load_from_config_file(name)
NBS.nint_fac = 2
NBS.nint_fac = 3
NBS.nint_fac = 5
NBS.nint_fac = 2
del NBS
[docs]
@ParametrizeDocstrings
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_dict.items()])
def test_all_pos_to_segmpos(NBS, float64_tols):
""" Tests whether ``all_pos`` and ``segmpos`` agree.
Tests:
* Whether the unoptimized track ``params_buf`` => ``all_pos`` => ``segmpos_noopt`` and the optimized track ``params_buf`` => ``segmpos_cy`` agree.
* That ``all_pos`` and ``segmpos`` respects all symmetry constraints.
"""
NBS.nint_fac = 10
params_buf = np.random.random((NBS.nparams))
# Unoptimized version
all_coeffs = NBS.params_to_all_coeffs_noopt(params_buf)
all_pos = scipy.fft.irfft(all_coeffs, axis=1, norm='forward')
NBS.AssertAllSegmGenConstraintsAreRespected(all_pos, pos=True)
NBS.AssertAllBodyConstraintAreRespected(all_pos, pos=True)
segmpos_noopt = NBS.all_to_segm_noopt(all_pos, pos=True)
# Optimized version
segmpos_cy = NBS.params_to_segmpos(params_buf)
print(np.linalg.norm(segmpos_noopt - segmpos_cy))
assert np.allclose(segmpos_noopt, segmpos_cy, rtol = float64_tols.rtol, atol = float64_tols.atol)
for Sym in NBS.Sym_list:
assert NBS.ComputeSymDefault(segmpos_cy, Sym) < float64_tols.atol
[docs]
@ParametrizeDocstrings
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_dict.items()])
def test_all_vel_to_segmvel(NBS, float64_tols):
""" Tests whether ``all_vel`` and ``segmvel`` agree.
Tests:
* Whether the unoptimized track ``params_buf`` => ``all_vel`` => ``segmvel_noopt`` and the optimized track ``params_buf`` => ``segmvel_cy`` agree.
* That ``all_vel`` and ``segmvel`` respects all symmetry constraints.
"""
NBS.nint_fac = 10
params_buf = np.random.random((NBS.nparams))
# Unoptimized version
all_coeffs = NBS.params_to_all_coeffs_noopt(params_buf)
NBS.all_coeffs_pos_to_vel_inplace(all_coeffs)
all_vel = scipy.fft.irfft(all_coeffs, axis=1, norm='forward')
NBS.AssertAllSegmGenConstraintsAreRespected(all_vel, pos=False)
NBS.AssertAllBodyConstraintAreRespected(all_vel, pos=False)
segmvel_noopt = NBS.all_to_segm_noopt(all_vel, pos=False)
# Optimized version
segmvel_cy = NBS.params_to_segmvel(params_buf)
print(np.linalg.norm(segmvel_noopt - segmvel_cy))
assert np.allclose(segmvel_noopt, segmvel_cy, rtol = float64_tols.rtol, atol = float64_tols.atol)
for Sym in NBS.Sym_list:
assert NBS.ComputeSymDefault(segmvel_cy, Sym.TimeDerivative(), pos = False) < float64_tols.atol
[docs]
@ParametrizeDocstrings
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_dict.items()])
def test_segmpos_to_all_pos(NBS, float64_tols):
""" Tests going from ``segmpos`` to ``all_pos`` and back.
Tests:
* Whether the track ``params_buf`` => ``segmpos`` => ``all_pos`` => ``all_coeffs`` => ``params_buf`` is the identity.
"""
NBS.nint_fac = 10
params_buf = np.random.random((NBS.nparams))
segmpos = NBS.params_to_segmpos(params_buf)
all_pos = NBS.segmpos_to_all_noopt(segmpos, pos=True)
all_coeffs = scipy.fft.rfft(all_pos, axis=1, norm='forward')
params = NBS.all_coeffs_to_params_noopt(all_coeffs)
print(np.linalg.norm(params_buf-params))
assert np.allclose(params_buf, params, rtol = float64_tols.rtol, atol = float64_tols.atol)
[docs]
@ParametrizeDocstrings
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_dict.items()])
def test_capture_co(float64_tols, NBS):
""" Checks consistency of parameters definition for :math:`c_o`.
Tests whether the parameter corresponding to the imaginary part of the coefficient of index 0 in the Fourier expansion is included only if it needs to be.
"""
NBS.nint_fac = 10
nnz = [[] for il in range(NBS.nloop)]
for il in range(NBS.nloop):
nnz_k = NBS.nnz_k(il)
params_basis_pos = NBS.params_basis_pos(il)
if nnz_k.shape[0] > 0:
if nnz_k[0] == 0:
for iparam in range(params_basis_pos.shape[2]):
if np.linalg.norm(params_basis_pos[:,0,iparam].imag) > float64_tols.atol:
nnz[il].append(iparam)
for il in range(NBS.nloop):
co_in = NBS.co_in(il)
for iparam in range(co_in.shape[0]):
assert not(co_in[iparam]) == (iparam in nnz[il])
[docs]
@ParametrizeDocstrings
@ProbabilisticTest()
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_dict.items()])
def test_round_trips_pos(float64_tols, NBS):
""" Tests whether several ways of going back and forth in the chain ``param_buf`` => ``segmpos`` indeed give the same result.
Tests:
* That ``all_pos`` => ``segmpos`` => ``all_pos`` is the identity.
* That ``all_coeffs`` => ``all_pos`` => ``all_coeffs`` is the identity.
* That ``params_buf`` => ``all_coeffs`` => ``params_buf`` is the identity.
* That ``params_buf`` => shifted ``all_coeffs`` => ``params_buf`` is the identity.
* That ``all_coeffs`` => shifted ``params_buf`` => ``all_coeffs`` is the identity in cases where it makes sense (i.e. no invariance wrt space orientation reversing transformations).
"""
NBS.nint_fac = 10
params_buf = np.random.random((NBS.nparams))
all_coeffs = NBS.params_to_all_coeffs_noopt(params_buf)
all_pos = scipy.fft.irfft(all_coeffs, axis=1, norm='forward')
segmpos = NBS.all_to_segm_noopt(all_pos, pos=True)
all_pos_rt = NBS.segmpos_to_all_noopt(segmpos, pos=True)
print(np.linalg.norm(all_pos_rt - all_pos))
assert np.allclose(all_pos, all_pos_rt, rtol = float64_tols.rtol, atol = float64_tols.atol)
all_coeffs_rt = scipy.fft.rfft(all_pos, axis=1,norm='forward')
print(np.linalg.norm(all_coeffs_rt - all_coeffs))
assert np.allclose(all_coeffs, all_coeffs_rt, rtol = float64_tols.rtol, atol = float64_tols.atol)
params_buf_rt = NBS.all_coeffs_to_params_noopt(all_coeffs)
print(np.linalg.norm(params_buf - params_buf_rt))
assert np.allclose(params_buf, params_buf_rt, rtol = float64_tols.rtol, atol = float64_tols.atol)
segmpos_cy = NBS.params_to_segmpos(params_buf)
params_buf_rt = NBS.segmpos_to_params(segmpos_cy)
print(np.linalg.norm(params_buf - params_buf_rt))
assert np.allclose(params_buf, params_buf_rt, rtol = float64_tols.rtol, atol = float64_tols.atol)
dt = np.random.random()
all_coeffs = NBS.params_to_all_coeffs_noopt(params_buf, dt=dt)
params_buf_rt = NBS.all_coeffs_to_params_noopt(all_coeffs, dt=dt)
print(np.linalg.norm(params_buf - params_buf_rt))
assert np.allclose(params_buf, params_buf_rt, rtol = float64_tols.rtol, atol = float64_tols.atol)
IsReflexionInvariant = False
for Sym in NBS.Sym_list:
IsReflexionInvariant = IsReflexionInvariant or (Sym.TimeRev == -1)
if not IsReflexionInvariant:
all_coeffs = NBS.params_to_all_coeffs_noopt(params_buf)
dt = np.random.random()
params_buf_rt = NBS.all_coeffs_to_params_noopt(all_coeffs, dt=dt)
all_coeffs_rt = NBS.params_to_all_coeffs_noopt(params_buf_rt, dt=dt)
print(np.linalg.norm(all_coeffs - all_coeffs_rt))
assert np.allclose(all_coeffs, all_coeffs_rt, rtol = float64_tols.rtol, atol = float64_tols.atol)
[docs]
@ParametrizeDocstrings
@ProbabilisticTest()
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_dict.items()])
def test_round_trips_vel(float64_tols, NBS):
""" Tests whether several ways of going back and forth in the chain ``param_buf`` => ``segmvel`` indeed give the same result.
Tests:
* That ``all_vel`` => ``segmvel`` => ``all_vel`` is the identity.
* That ``all_coeffs`` => ``all_vel`` => ``all_coeffs`` is the identity.
"""
NBS.nint_fac = 10 # Else it will sometime fail for huge symmetries
params_buf = np.random.random((NBS.nparams))
all_coeffs = NBS.params_to_all_coeffs_noopt(params_buf)
NBS.all_coeffs_pos_to_vel_inplace(all_coeffs)
all_vel = scipy.fft.irfft(all_coeffs, axis=1, norm='forward')
segmvel = NBS.all_to_segm_noopt(all_vel, pos=False)
all_vel_rt = NBS.segmpos_to_all_noopt(segmvel, pos=False)
print(np.linalg.norm(all_vel_rt - all_vel))
assert np.allclose(all_vel, all_vel_rt, rtol = float64_tols.rtol, atol = float64_tols.atol)
all_coeffs_rt = scipy.fft.rfft(all_vel, axis=1, norm='forward')
print(np.linalg.norm(all_coeffs_rt - all_coeffs))
assert np.allclose(all_coeffs, all_coeffs_rt, rtol = float64_tols.rtol, atol = float64_tols.atol)
[docs]
@ParametrizeDocstrings
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_dict.items()])
def test_changevars(float64_tols, NBS):
""" Tests properties of change of variables of parameters.
Tests:
* That ``params_mom_buf`` => ``params_pos_buf`` => ``params_mom_buf`` is the identity.
* That ``params_pos_buf`` => ``params_mom_buf`` => ``params_pos_buf`` is the identity.
* That scalar poducts are conserved by the (dual) change of variables.
"""
NBS.nint_fac = 10
params_mom_buf = np.random.random((NBS.nparams))
params_pos_buf = NBS.params_changevar(params_mom_buf, inv=False, transpose=False)
params_mom_buf_rt = NBS.params_changevar(params_pos_buf, inv=True, transpose=False)
print(np.linalg.norm(params_mom_buf - params_mom_buf_rt))
assert np.allclose(params_mom_buf, params_mom_buf_rt, rtol = float64_tols.rtol, atol = float64_tols.atol)
params_pos_buf_dual = np.random.random((NBS.nparams_incl_o))
params_mom_buf_dual = NBS.params_changevar(params_pos_buf_dual, inv=False, transpose=True)
dot_mom = np.dot(params_mom_buf, params_mom_buf_dual)
dot_pos = np.dot(params_pos_buf, params_pos_buf_dual)
print(abs(dot_mom - dot_pos))
assert abs(dot_mom - dot_pos) < float64_tols.atol
assert 2*abs(dot_mom - dot_pos) / (dot_mom + dot_pos) < float64_tols.rtol
# ##################################################################################################
params_mom_buf = NBS.params_changevar(params_pos_buf, inv=True, transpose=False)
params_pos_buf_rt = NBS.params_changevar(params_mom_buf, inv=False, transpose=False)
print(np.linalg.norm(params_pos_buf - params_pos_buf_rt))
assert np.allclose(params_pos_buf, params_pos_buf_rt, rtol = float64_tols.rtol, atol = float64_tols.atol)
params_mom_buf_dual = np.random.random((NBS.nparams))
params_pos_buf_dual = NBS.params_changevar(params_mom_buf_dual, inv=True, transpose=True)
dot_mom = np.dot(params_mom_buf, params_mom_buf_dual)
dot_pos = np.dot(params_pos_buf, params_pos_buf_dual)
print(abs(dot_mom - dot_pos))
assert abs(dot_mom - dot_pos) < float64_tols.atol
assert 2*abs(dot_mom - dot_pos) / (dot_mom + dot_pos) < float64_tols.rtol
[docs]
@ParametrizeDocstrings
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_dict.items()])
def test_params_segmpos_dual(float64_tols, NBS):
""" Tests invariance of dot product by the transformation ``params`` => ``segmpos``.
"""
NBS.nint_fac = 10
params_buf = np.random.random((NBS.nparams))
segmpos = NBS.params_to_segmpos(params_buf)
segmpos_dual = np.random.random((NBS.nsegm,NBS.segm_store,NBS.geodim))
params_buf_dual = NBS.segmpos_to_params_T(segmpos_dual)
dot_params = np.dot(params_buf, params_buf_dual)
dot_segmpos = np.dot(segmpos_dual.reshape(-1), segmpos.reshape(-1))
print(abs(dot_params - dot_segmpos))
assert abs(dot_params - dot_segmpos) < float64_tols.atol
assert 2*abs(dot_params - dot_segmpos) / (dot_params + dot_segmpos) < float64_tols.rtol
[docs]
@ParametrizeDocstrings
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_dict.items()])
def test_kin(float64_tols, NBS):
""" Tests computation of kinetic energy.
Tests:
* That optimized and non optimized computations of kinetic energy give the same result.
* Idem for the gradient of the kinetic energy.
* That the gradient of the kinetic energy agrees with its finite difference approximation.
"""
NBS.nint_fac = 10
params_buf = np.random.random((NBS.nparams))
all_coeffs = NBS.params_to_all_coeffs_noopt(params_buf)
kin = NBS.all_coeffs_to_kin_nrg(all_coeffs)
kin_opt = NBS.params_to_kin_nrg(params_buf)
assert abs(kin - kin_opt) < float64_tols.atol
assert 2*abs(kin - kin_opt) / (kin + kin_opt) < float64_tols.rtol
kin_grad_params = NBS.params_to_kin_nrg_grad(params_buf)
kin_grad_coeffs = NBS.all_coeffs_to_kin_nrg_grad(all_coeffs)
kin_grad_params_2 = NBS.all_coeffs_to_params_noopt(kin_grad_coeffs, transpose=True)
assert np.allclose(kin_grad_params, kin_grad_params_2, rtol = float64_tols.rtol, atol = float64_tols.atol)
def grad(x, dx):
return np.dot(NBS.params_to_kin_nrg_grad(x), dx)
err = compare_FD_and_exact_grad(
NBS.params_to_kin_nrg ,
grad ,
params_buf ,
dx=None ,
epslist=None ,
order=2 ,
vectorize=False ,
)
assert err.min() < float64_tols.rtol
[docs]
@ParametrizeDocstrings
@ProbabilisticTest(RepeatOnFail=2)
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_nozerodiv_dict.items()])
def test_pot(float64_tols_loose, NBS):
""" Tests computation of potential energy.
Tests:
* That the gradient of the potential energy agrees with its finite difference approximation.
* That the hessian of the potential energy agrees with its finite difference approximation.
"""
NBS.nint_fac = 10
params_buf = np.random.random((NBS.nparams))
def grad(x,dx):
return np.dot(NBS.params_to_pot_nrg_grad(x), dx)
dx = np.random.random((NBS.nparams))
err = compare_FD_and_exact_grad(
NBS.params_to_pot_nrg ,
grad ,
params_buf ,
dx=dx ,
epslist=None ,
order=2 ,
vectorize=False ,
)
print(err.min())
assert (err.min() < float64_tols_loose.rtol)
err = compare_FD_and_exact_grad(
NBS.params_to_pot_nrg_grad ,
NBS.params_to_pot_nrg_hess ,
params_buf ,
dx=dx ,
epslist=None ,
order=2 ,
vectorize=False ,
)
print(err.min())
assert (err.min() < float64_tols_loose.rtol)
[docs]
@ParametrizeDocstrings
@ProbabilisticTest(RepeatOnFail=2)
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_nozerodiv_dict.items()])
def test_action(float64_tols_loose, NBS):
""" Tests computation of the action.
Tests:
* That the gradient of the action agrees with its finite difference approximation.
* That the hessian of the action agrees with its finite difference approximation.
"""
NBS.nint_fac = 10
params_buf = np.random.random((NBS.nparams))
def grad(x,dx):
return np.dot(NBS.params_to_action_grad(x), dx)
dx = np.random.random((NBS.nparams))
err = compare_FD_and_exact_grad(
NBS.params_to_action ,
grad ,
params_buf ,
dx=dx ,
epslist=None ,
order=2 ,
vectorize=False ,
)
print(err.min())
assert (err.min() < float64_tols_loose.rtol)
err = compare_FD_and_exact_grad(
NBS.params_to_action_grad ,
NBS.params_to_action_hess ,
params_buf ,
dx=dx ,
epslist=None ,
order=2 ,
vectorize=False ,
)
print(err.min())
assert (err.min() < float64_tols_loose.rtol)
[docs]
@ParametrizeDocstrings
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_nozerodiv_dict.items()])
def test_resize(float64_tols, NBS):
""" Tests nested properties of Fourier spaces on positions.
"""
NBS.nint_fac = 10
small_segm_size = NBS.segm_size
params_buf = np.random.random((NBS.nparams))
segmpos = NBS.params_to_segmpos(params_buf)
all_coeffs = NBS.params_to_all_coeffs_noopt(params_buf)
all_pos = scipy.fft.irfft(all_coeffs, axis=1, norm='forward')
segmpos_noopt = NBS.all_to_segm_noopt(all_pos, pos=True)
print(np.linalg.norm(segmpos - segmpos_noopt))
assert np.allclose(segmpos, segmpos_noopt, rtol = float64_tols.rtol, atol = float64_tols.atol)
fac = 4
new_nint_fac = fac * NBS.nint_fac
params_buf_long = NBS.params_resize(params_buf, new_nint_fac)
NBS.nint_fac = new_nint_fac
long_segm_size = NBS.segm_size
segmpos_long = NBS.params_to_segmpos(params_buf_long)
all_coeffs = NBS.params_to_all_coeffs_noopt(params_buf_long)
all_pos = scipy.fft.irfft(all_coeffs, axis=1, norm='forward')
segmpos_long_noopt = NBS.all_to_segm_noopt(all_pos, pos=True)
print(np.linalg.norm(segmpos_long - segmpos_long_noopt))
assert np.allclose(segmpos_long, segmpos_long_noopt, rtol = float64_tols.rtol, atol = float64_tols.atol)
print(np.linalg.norm(segmpos[:,:small_segm_size,:] - segmpos_long[:,:long_segm_size:fac,:]))
assert np.allclose(segmpos[:,:small_segm_size,:], segmpos_long[:,:long_segm_size:fac,:], rtol = float64_tols.rtol, atol = float64_tols.atol)
[docs]
@ParametrizeDocstrings
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_nozerodiv_dict.items()])
def test_action_indep_resize(float64_tols_loose, NBS):
""" Tests that action is left **similar** by resizing.
.. note::
This test is very susceptible to false positives. It should be replaced by a better test.
"""
Passed_any = False
ntries = 100
err = np.zeros((ntries,4))
for itry in range(ntries):
Passed = True
nint_fac_short = 5
nint_fac_mid = 200
nint_fac_big = nint_fac_mid*2
NBS.nint_fac = nint_fac_short
params_buf_short = np.random.random((NBS.nparams))
params_buf_mid = NBS.params_resize(params_buf_short, nint_fac_mid)
params_buf_big = NBS.params_resize(params_buf_short, nint_fac_big)
NBS.nint_fac = nint_fac_mid
kin_nrg = NBS.params_to_kin_nrg(params_buf_mid)
pot_nrg = NBS.params_to_pot_nrg(params_buf_mid)
NBS.nint_fac = nint_fac_big
kin_nrg_big= NBS.params_to_kin_nrg(params_buf_big)
pot_nrg_big= NBS.params_to_pot_nrg(params_buf_big)
err[itry,0] = abs(kin_nrg - kin_nrg_big)
err[itry,1] = 2*abs(kin_nrg - kin_nrg_big) / abs(kin_nrg + kin_nrg_big)
err[itry,2] = abs(pot_nrg - pot_nrg_big)
err[itry,3] = 2*abs(pot_nrg - pot_nrg_big) / abs(pot_nrg + pot_nrg_big)
Passed = Passed and err[itry,0] < float64_tols_loose.rtol
Passed = Passed and err[itry,1] < float64_tols_loose.rtol
Passed = Passed and err[itry,2] < float64_tols_loose.rtol
Passed = Passed and err[itry,3] < float64_tols_loose.rtol
Passed_any = Passed_any or Passed
if Passed_any:
break
if not(Passed_any):
print(err)
assert Passed_any
[docs]
@ParametrizeDocstrings
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_dict.items()])
def test_repeatability(float64_tols, NBS):
""" Tests that computing ``params_buf`` => ``segmpos`` twice give the same result.
"""
NBS.nint_fac = 10
params_buf = np.random.random((NBS.nparams))
params_buf_cp = params_buf.copy()
print(np.linalg.norm(params_buf - params_buf_cp))
assert np.allclose(params_buf, params_buf_cp, rtol = float64_tols.rtol, atol = float64_tols.atol)
segmpos = NBS.params_to_segmpos(params_buf)
segmpos_2 = NBS.params_to_segmpos(params_buf)
print(np.linalg.norm(segmpos - segmpos_2))
assert np.allclose(segmpos, segmpos_2, rtol = float64_tols.rtol, atol = float64_tols.atol)
[docs]
@ParametrizeDocstrings
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_dict.items()])
def test_ForceGeneralSym(float64_tols, NBS):
""" Tests that computations results are independant of :meth:`choreo.NBodySyst.ForceGeneralSym`.
Tests that the following computations are independant of :meth:`choreo.NBodySyst.ForceGeneralSym`:
* ``params`` => ``segmpos``
* ``params`` => ``segmvel``
* ``segmpos`` => ``params``
* ``segmpos`` => ``params_T``
"""
NBS.nint_fac = 10
params_buf = np.random.random((NBS.nparams))
NBS.ForceGeneralSym = False
segmpos = NBS.params_to_segmpos(params_buf)
segmvel = NBS.params_to_segmvel(params_buf)
params = NBS.segmpos_to_params(segmpos)
params_T = NBS.segmpos_to_params_T(segmpos)
NBS.ForceGeneralSym = True
segmpos_f = NBS.params_to_segmpos(params_buf)
segmvel_f = NBS.params_to_segmvel(params_buf)
params_f = NBS.segmpos_to_params(segmpos)
params_T_f = NBS.segmpos_to_params_T(segmpos)
print(np.linalg.norm(segmpos - segmpos_f))
assert np.allclose(segmpos, segmpos_f, rtol = float64_tols.rtol, atol = float64_tols.atol)
print(np.linalg.norm(segmvel - segmvel_f))
assert np.allclose(segmvel, segmvel_f, rtol = float64_tols.rtol, atol = float64_tols.atol)
print(np.linalg.norm(params - params_f))
assert np.allclose(params, params_f, rtol = float64_tols.rtol, atol = float64_tols.atol)
print(np.linalg.norm(params_T - params_T_f))
assert np.allclose(params_T, params_T_f, rtol = float64_tols.rtol, atol = float64_tols.atol)
[docs]
@ParametrizeDocstrings
@ProbabilisticTest(RepeatOnFail=2)
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_dict.items()])
def test_ForceGreaterNstore(float64_tols, NBS):
""" Tests that computations results are independant of :meth:`choreo.NBodySyst.ForceGreaterNStore`.
Tests that the following computations are independant of :meth:`choreo.NBodySyst.ForceGreaterNStore`:
* ``params`` => ``segmpos``
* ``params`` => ``action_grad``
* ``params`` => ``action_hess``
"""
NBS.ForceGreaterNStore = False
NBS.nint_fac = 10
params_buf = np.random.random((NBS.nparams))
dparams_buf = np.random.random((NBS.nparams))
segm_store_ini = NBS.segm_store
segmpos = NBS.params_to_segmpos(params_buf)
action_grad = NBS.params_to_action_grad(params_buf)
action_hess = NBS.params_to_action_hess(params_buf, dparams_buf)
NBS.ForceGreaterNStore = True
assert NBS.segm_store == NBS.segm_size + 1
assert NBS.GreaterNStore
segmpos_f = NBS.params_to_segmpos(params_buf)
action_grad_f = NBS.params_to_action_grad(params_buf)
action_hess_f = NBS.params_to_action_hess(params_buf, dparams_buf)
for isegm in range(NBS.nsegm):
print(isegm, np.linalg.norm(segmpos[isegm,:NBS.segm_size,:] - segmpos_f[isegm,:NBS.segm_size,:]))
assert np.allclose(segmpos[isegm,:NBS.segm_size,:], segmpos_f[isegm,:NBS.segm_size,:], rtol = float64_tols.rtol, atol = float64_tols.atol)
print(np.linalg.norm(action_grad - action_grad_f))
assert np.allclose(action_grad, action_grad_f, rtol = float64_tols.rtol, atol = float64_tols.atol)
print(np.linalg.norm(action_hess - action_hess_f))
assert np.allclose(action_hess, action_hess_f, rtol = float64_tols.rtol, atol = float64_tols.atol)
NBS.ForceGreaterNStore = False
assert segm_store_ini == NBS.segm_store
segmpos_nf = NBS.params_to_segmpos(params_buf)
action_grad_nf = NBS.params_to_action_grad(params_buf)
action_hess_nf = NBS.params_to_action_hess(params_buf, dparams_buf)
print(np.linalg.norm(segmpos - segmpos_nf))
assert np.allclose(segmpos, segmpos_nf, rtol = float64_tols.rtol, atol = float64_tols.atol)
print(np.linalg.norm(action_grad - action_grad_nf))
assert np.allclose(action_grad, action_grad_nf, rtol = float64_tols.rtol, atol = float64_tols.atol)
print(np.linalg.norm(action_hess - action_hess_nf))
assert np.allclose(action_hess, action_hess_nf, rtol = float64_tols.rtol, atol = float64_tols.atol)
[docs]
@ParametrizeDocstrings
@ProbabilisticTest(RepeatOnFail=2)
@pytest.mark.parametrize("NBS", [pytest.param(NBS, id=name) for name, NBS in NBS_dict.items()])
@pytest.mark.parametrize("backend", ["scipy", "mkl", "fftw", "ducc"])
@pytest.mark.parametrize("ForceGeneralSym", [True, False])
def test_fft_backends(float64_tols, ForceGeneralSym, backend, NBS):
""" Tests that computations results are independant of :meth:`choreo.NBodySyst.fft_backend`.
Tests that the following computations are independant of :meth:`choreo.NBodySyst.fft_backend`:
* ``params`` => ``segmpos``
* ``segmpos`` => ``params``
"""
NBS.nint_fac = 10
NBS.fft_backend = "scipy"
NBS.fftw_planner_effort = 'FFTW_ESTIMATE'
NBS.fftw_wisdom_only = False
NBS.fftw_nthreads = 1
params_buf = np.random.random((NBS.nparams))
segmpos_ref = NBS.params_to_segmpos(params_buf)
NBS.ForceGeneralSym = ForceGeneralSym
NBS.fft_backend = backend
params_buf_cp = params_buf.copy()
segmpos = NBS.params_to_segmpos(params_buf_cp)
print(np.linalg.norm(segmpos - segmpos_ref))
assert np.allclose(segmpos, segmpos_ref, rtol = float64_tols.rtol, atol = float64_tols.atol)
params_buf_rt = NBS.segmpos_to_params(segmpos)
print(np.linalg.norm(params_buf - params_buf_rt))
assert np.allclose(params_buf, params_buf_rt, rtol = float64_tols.rtol, atol = float64_tols.atol)
NBS.ForceGeneralSym = False
NBS.fft_backend = "scipy"
[docs]
@ParametrizeDocstrings
@ProbabilisticTest(RepeatOnFail=2)
@pytest.mark.parametrize("NBS_pair", [pytest.param(NBS_pair, id=name) for name, NBS_pair in NBS_pairs_dict.items()])
def test_action_cst_sym_pairs(float64_tols, NBS_pair):
""" Tests that computations results are independant of the prescribed symmetries.
Tests that the following computations results are independant of the prescribed symmetries:
* ``all_coeffs`` => ``kinetic energy``
* ``params`` => ``kinetic energy``
* ``params`` => ``potential energy``
* ``params`` => ``action``
* ``params`` => ``path_stats``
"""
NBS_m, NBS_l = NBS_pair
# m => more symmetry. l => less symmetry
assert NBS_m.nint_min > NBS_l.nint_min
assert NBS_m.nint_min % NBS_l.nint_min == 0
nint_fac = 10
NBS_m.nint_fac = nint_fac
NBS_l.nint = NBS_m.nint
params_buf_m = np.random.random((NBS_m.nparams))
all_coeffs = NBS_m.params_to_all_coeffs_noopt(params_buf_m)
params_buf_l = NBS_l.all_coeffs_to_params_noopt(all_coeffs)
kin_m = NBS_m.all_coeffs_to_kin_nrg(all_coeffs)
kin_l = NBS_l.all_coeffs_to_kin_nrg(all_coeffs)
print(abs(kin_m - kin_l))
assert abs(kin_m - kin_l) < float64_tols.atol
kin_m = NBS_m.params_to_kin_nrg(params_buf_m)
kin_l = NBS_l.params_to_kin_nrg(params_buf_l)
print(abs(kin_m - kin_l))
assert abs(kin_m - kin_l) < float64_tols.atol
pot_m = NBS_m.params_to_pot_nrg(params_buf_m)
pot_l = NBS_l.params_to_pot_nrg(params_buf_l)
print(abs(pot_m - pot_l))
assert abs(pot_m - pot_l) < float64_tols.atol
act_m = NBS_m.params_to_action(params_buf_m)
act_l = NBS_l.params_to_action(params_buf_l)
print(abs(act_m - act_l))
assert abs(act_m - act_l) < float64_tols.atol
segmpos_m = NBS_m.params_to_segmpos(params_buf_m)
segmvel_m = NBS_m.params_to_segmvel(params_buf_m)
segmpos_l = NBS_l.params_to_segmpos(params_buf_l)
segmvel_l = NBS_l.params_to_segmvel(params_buf_l)
loop_len_m, bin_dx_min_m = NBS_m.segm_to_path_stats(segmpos_m, segmvel_m)
loop_len_l, bin_dx_min_l = NBS_m.segm_to_path_stats(segmpos_l, segmvel_l)
for il in range(NBS_m.nloop):
print(abs(loop_len_m[il] - loop_len_l[il]))
assert abs(loop_len_m[il] - loop_len_l[il]) < float64_tols.atol
bin_dx_min_m.sort()
bin_dx_min_l.sort()
for ibin in range(NBS_m.nbin_segm_unique):
print(abs(bin_dx_min_m[ibin] - bin_dx_min_l[ibin]))
assert abs(bin_dx_min_m[ibin] - bin_dx_min_l[ibin]) < float64_tols.atol
[docs]
@ParametrizeDocstrings
def test_custom_inter_law(float64_tols):
""" Tests that custom interaction laws are correctly handled.
"""
geodim = 2
nbody = 3
bodymass = np.array([1., 2., 3.])
bodycharge = np.array([4., 5., 6.])
Sym_list = []
# Classical gravity_pot
inter_law = scipy.LowLevelCallable.from_cython(choreo.cython._NBodySyst, "gravity_pot")
NBS = choreo.NBodySyst(geodim, nbody, bodymass, bodycharge, Sym_list, inter_law)
params_buf = np.random.random((NBS.nparams))
action_grad_grav = NBS.params_to_action_grad(params_buf)
# Parametrized power_law_pot
inter_law_str = "power_law_pot"
inter_law_param_dict = {'n':-1., 'alpha':1.}
NBS = choreo.NBodySyst(geodim, nbody, bodymass, bodycharge, Sym_list, None, inter_law_str, inter_law_param_dict)
action_grad = NBS.params_to_action_grad(params_buf)
print(np.linalg.norm(action_grad_grav-action_grad))
assert np.allclose(action_grad_grav, action_grad, rtol = float64_tols.rtol, atol = float64_tols.atol)
# Python pot_fun
def inter_law(ptr, xsq, res):
a = xsq ** (-2.5)
b = xsq*a
res[0] = -xsq*b
res[1]= 0.5*b
res[2] = (-0.75)*a
NBS = choreo.NBodySyst(geodim, nbody, bodymass, bodycharge, Sym_list, inter_law)
action_grad = NBS.params_to_action_grad(params_buf)
print(np.linalg.norm(action_grad_grav-action_grad))
assert np.allclose(action_grad_grav, action_grad, rtol = float64_tols.rtol, atol = float64_tols.atol)
inter_law_str = """
def inter_law(ptr, xsq, res):
a = xsq ** (-2.5)
b = xsq*a
res[0] = -xsq*b
res[1]= 0.5*b
res[2] = (-0.75)*a
"""
NBS = choreo.NBodySyst(geodim, nbody, bodymass, bodycharge, Sym_list, None, inter_law_str)
action_grad = NBS.params_to_action_grad(params_buf)
print(np.linalg.norm(action_grad_grav-action_grad))
assert np.allclose(action_grad_grav, action_grad, rtol = float64_tols.rtol, atol = float64_tols.atol)
[docs]
@ParametrizeDocstrings
@pytest.mark.parametrize(("NBS", "params_buf"), [pytest.param(NBS, params_buf, id=name) for name, (NBS, params_buf) in Sols_dict.items()])
def test_periodicity_default(float64_tols, NBS, params_buf):
""" Tests that Fourier periodic solutions have zero periodicity default.
"""
NBS.ForceGreaterNStore = True
segmpos = NBS.params_to_segmpos(params_buf)
xo = np.ascontiguousarray(segmpos[:,0 ,:].reshape(-1))
xf = np.ascontiguousarray(segmpos[:,-1,:].reshape(-1))
dx = NBS.Compute_periodicity_default(xo, xf)
ndof = dx.shape[0]
assert np.allclose(dx, np.zeros((ndof), dtype=np.float64), rtol = float64_tols.rtol, atol = float64_tols.atol)
[docs]
@ParametrizeDocstrings
@pytest.mark.parametrize("NoSymIfPossible", [True, False])
@pytest.mark.parametrize("LowLevel", [True, False])
@pytest.mark.parametrize("vector_calls", [True, False])
@pytest.mark.parametrize(("NBS", "params_buf"), [pytest.param(NBS, params_buf, id=name) for name, (NBS, params_buf) in Sols_dict.items()])
def test_ODE_vs_spectral(NBS, params_buf, vector_calls, LowLevel, NoSymIfPossible):
""" Tests that the Fourier periodic spectral solver agrees with the time forward Runge-Kutta solver.
"""
NBS.ForceGreaterNStore = True
segmpos = NBS.params_to_segmpos(params_buf)
action_grad = NBS.segmpos_params_to_action_grad(segmpos, params_buf)
action_grad_norm = np.linalg.norm(action_grad, ord = np.inf)
tol = 100 * action_grad_norm
ODE_Syst = NBS.Get_ODE_def(params_buf, vector_calls = vector_calls, LowLevel = LowLevel, NoSymIfPossible = NoSymIfPossible)
nsteps = 10
keep_freq = 1
nint_ODE = (NBS.segm_store-1) * keep_freq
method = "Gauss"
rk = choreo.scipy_plus.multiprec_tables.ComputeImplicitRKTable_Gauss(nsteps, method=method)
segmpos_ODE, segmvel_ODE = choreo.scipy_plus.ODE.SymplecticIVP(
rk = rk ,
keep_freq = keep_freq ,
nint = nint_ODE ,
keep_init = True ,
**ODE_Syst ,
)
segmpos_ODE = np.ascontiguousarray(segmpos_ODE.reshape((NBS.segm_store, NBS.nsegm, NBS.geodim)).swapaxes(0, 1))
print(np.linalg.norm(segmpos - segmpos_ODE))
assert np.allclose(segmpos, segmpos_ODE, rtol = tol, atol = tol)