diff --git a/examples/test_cheb_mo_1p_1d_twostep.py b/examples/test_cheb_mo_1p_1d_twostep.py new file mode 100644 index 0000000000000000000000000000000000000000..18129790e9375bbfab62e03e9ab9c6a085866e46 --- /dev/null +++ b/examples/test_cheb_mo_1p_1d_twostep.py @@ -0,0 +1,86 @@ +""" One particle in Morse potential - Chebyshev and two-step Chebyshev """ +import numpy as np +import matplotlib.pyplot as plt +from rotagaporp.chebyshev import Chebyshev, TwoStepChebyshev +from rotagaporp.symplectic import Symplectic +from rotagaporp.systems import Morse + +params = {'tstart': 0., 'tend': 500., 'tstep': 0.05, 'nterms': 4, 'tqdm': True} +syspar0 = {'q0': 3., 'p0': 0.} +system = Morse(**syspar0) + +# find the initial values for the two step propagator +ini_params = params.copy() +ini_params['tend'] = params['tstart'] + params['tstep'] +ini_params['tstep'] = 0.001 +ini_params['nterms'] = 5 +ini_prop = Chebyshev(syst=system, **ini_params) +ini_prop.propagate() +ini_prop.analyse() +print('energy drift of initializer', np.max(np.fabs(ini_prop.er))) + +assert np.isclose(ini_prop.time[-1], params['tstart'] + params['tstep']) +assert len(ini_prop.sq) == len(ini_prop.time) +syspar = {'q0': ini_prop.sq[-1], 'p0': ini_prop.sp[-1]} +print(syspar) +system = Morse(**syspar) + +# velocity Verlet +params_verlet = params.copy() +del params_verlet['nterms'] +vv_prop = Symplectic(syst=system, **params_verlet) +vv_prop.propagate() +vv_prop.analyse() +edrift_vv = vv_prop.er + +# one-step propagator +ch_prop = Chebyshev(syst=system, **params) +ch_prop.propagate() +time_ch, q_ch, p_ch = ch_prop.get_trajectory() +ch_prop.analyse() +edrift_ch = ch_prop.er + +# two-step propagator +params['q_1'] = syspar0['q0'] +params['p_1'] = syspar0['p0'] +params['parity'] = 0 +# params['signed'] = False +ts_prop = TwoStepChebyshev(syst=system, **params) +ts_prop.propagate() +time_ts, q_ts, p_ts = ts_prop.get_trajectory() +ts_prop.analyse() +edrift_ts = ts_prop.er + +assert len(time_ts) == len(time_ch) +assert np.allclose(time_ts, time_ch) +q_an, p_an = system.solution(times=time_ts) + +fig = plt.figure() +plot1 = fig.add_subplot(311) +plot1.plot(time_ch, np.maximum.accumulate(edrift_ch), label='Chebyshev') +plot1.plot(time_ts, np.maximum.accumulate(edrift_ts), label='Two-step Chebyshev') +plot1.plot(time_ts, np.maximum.accumulate(edrift_vv), label='Velocity Verlet') +plot1.set_ylabel('maximum energy drift') +plot1.set_yscale('log') +plot1.set_xscale('log') +plot1.legend() + +plot2 = fig.add_subplot(312) +plot2.plot(time_ts, q_ch, label='Chebyshev') +plot2.plot(time_ts, q_ts, label='Two-step Chebyshev') +plot2.plot(time_ts, q_an, label='Analytic solution') +plot2.set_xlabel(r'$t$') +plot2.set_ylabel(r'$q(t)$') +plot2.set_xscale('log') +plot2.legend() + +plot3 = fig.add_subplot(313) +plot3.plot(time_ts, p_ch, label='Chebyshev') +plot3.plot(time_ts, p_ts, label='Two-step Chebyshev') +plot3.plot(time_ts, p_an, label='Analytic solution') +plot3.set_xlabel(r'$t$') +plot3.set_ylabel(r'$p(t)$') +plot3.set_xscale('log') +plot3.legend() + +plt.show() diff --git a/rotagaporp/chebyshev.py b/rotagaporp/chebyshev.py index f226ddb2d2b2c1c5b8362c69b9f8a97ab2c9f38e..eea2d63f41d725f69597c2542a51f43434fdde32 100644 --- a/rotagaporp/chebyshev.py +++ b/rotagaporp/chebyshev.py @@ -4,6 +4,7 @@ import numpy as np import sympy as sp from scipy.special import jn, iv from propagators import PolynomialPropagator +from propagators import TwoStepPolynomialPropagator def cheb_coef(bf, nterms, alpha): @@ -87,3 +88,35 @@ class Chebyshev(PolynomialPropagator): self.set_polynomial(system=self.syst, scale=self.scale, shift=self.shift, recursion='chebyshev', sign=sign) + + +class TwoStepChebyshev(TwoStepPolynomialPropagator): + """ Two-step Chebyshev propagator for classical particle dynamics """ + name = 'two-step chebyshev' + def __init__(self, delta=1., nterms=None, signed=False, **kwargs): + super().__init__(**kwargs) + + self.delta = delta + alpha = self.delta*self.tstep/2. + self.nterms = int(np.ceil(alpha)) if nterms is None else nterms + self.efficiency = alpha/self.nterms + bf = besself(self.fpprec, signed, prec=self.prec) + sign = -1 if signed else 1 + assert self.substep is None, 'substep not implemented' + self.coeffs = 2.*cheb_coef(bf, self.nterms, alpha)[self.parity::2] + self.set_polynomial(system=self.syst, scale=2./self.delta, + recursion='chebyshev', sign=sign) + + def step_nopbc(self, time, q, p): + """ perform a time step with initial coordinate and momentum """ + # the get_val_float() method has to be adapted to evaluate only + # either the odd or the even powers + qvecaux, pvecaux = self.iln.get_val_float(q, p) + qvec = qvecaux[self.parity::2] + pvec = pvecaux[self.parity::2] + # + qt = np.dot(self.coeffs, qvec) + self.prefact*self.q_1 + pt = np.dot(self.coeffs, pvec) + self.prefact*self.p_1 + self.q_1 = q + self.p_1 = p + return (qt, pt) diff --git a/rotagaporp/propagators.py b/rotagaporp/propagators.py index d3925489db5988e3a4ee475e860bd09d6f748576..b8c2e8b50ccc8a4b7af802333cf6e24982651dda 100644 --- a/rotagaporp/propagators.py +++ b/rotagaporp/propagators.py @@ -148,3 +148,17 @@ class PolynomialPropagator(Propagator): import cloudpickle as pkl with open(filename, 'wb') as fh: pkl.dump(self, fh) + + +class TwoStepPolynomialPropagator(PolynomialPropagator): + """ Base class for two-step polynomial classical propagators """ + + name = 'two-step polynomial' + + def __init__(self, q_1, p_1, parity=0, **kwargs): + assert parity in (0, 1) + super().__init__(**kwargs) + self.parity = parity + self.prefact = 2*parity-1 + self.q_1 = q_1 + self.p_1 = p_1 diff --git a/tests/sp_tests.py b/tests/sp_tests.py index 199fbff991b05614d7efb36979fb9e37048ab5b9..9f31b5d0c4d034b9f35eecf854306c147b1d5110 100644 --- a/tests/sp_tests.py +++ b/tests/sp_tests.py @@ -5,7 +5,7 @@ import sympy as sp from rotagaporp.liouville import q, p from rotagaporp.liouville import LiouvillePowers, LiouvillePowersQP, LiouvillePowersNR from rotagaporp.symplectic import Symplectic -from rotagaporp.chebyshev import Chebyshev +from rotagaporp.chebyshev import Chebyshev, TwoStepChebyshev from rotagaporp.newtonian import Newtonian from rotagaporp.newtonian import leja_points_ga, leja_points_jb from rotagaporp.newtonian import chebyshev_points @@ -24,6 +24,8 @@ from rotagaporp.combinatorics import cnk_chebyshev_T3 from rotagaporp.combinatorics import cnk_chebyshev_U1, cnk_chebyshev_U2 from rotagaporp.combinatorics import cnk_chebyshev_U3 +atol32 = np.finfo(np.float32).resolution + class LiouvillePowersTest(unittest.TestCase): """ Test of the one-particle Liouville powers class """ @@ -283,7 +285,6 @@ class ChebyshevPropagatorTest(unittest.TestCase): (ti, qt, pt) = prop.get_trajectory() (qr, pr) = syst.solution(self.syspar['q0'], self.syspar['p0'], ti) self.assertTrue(np.allclose(qt, qr, rtol=1e-7)) - atol32 = np.finfo(np.float32).resolution self.assertTrue(np.allclose(pt, pr, atol=atol32)) def chebyshev_propagator_signed_test(self): @@ -322,6 +323,75 @@ class ChebyshevPropagatorTest(unittest.TestCase): self.assertTrue(np.allclose(pt, pr, rtol=1e-7)) +class TwoStepChebyshevPropagatorTest(unittest.TestCase): + """ Tests for the two-step Chebyshev polynomial propagator """ + + params = {'tstart': 0.0, 'tend': 5.0, 'tstep': 0.2, 'nterms': 8} + syspar = {'q0': 1.1, 'p0': 0.0} + + def chebyshev_propagator_ballistic_and_harmonic_test(self): + """ test with ballistic motion and harmonic oscillator """ + for potential in (Harmonic, Ballistic): + sys = potential(**self.syspar) + t_1 = self.params['tstart'] - self.params['tstep'] + q_1, p_1 = sys.solution(self.syspar['q0'], self.syspar['p0'], t_1) + prop = TwoStepChebyshev(syst=sys, q_1=q_1, p_1=p_1, **self.params) + prop.propagate() + prop.analyse() + self.assertLess(np.max(np.fabs(prop.er)), 1e-8) + (ti, qt, pt) = prop.get_trajectory() + (qr, pr) = sys.solution(self.syspar['q0'], self.syspar['p0'], ti) + self.assertTrue(np.allclose(qt, qr, rtol=1e-8)) + self.assertTrue(np.allclose(pt, pr, rtol=1e-8)) + + def chebyshev_propagator_morse_test(self): + """ test with Morse oscillator """ + syst = Morse(**self.syspar) + init_params = self.params.copy() + init_params['tend'] = self.params['tstart'] + self.params['tstep'] + init_times = [init_params['tstart'], init_params['tend']] + qinit, pinit = syst.solution(times=init_times) + syst = Morse(q0=qinit[1], p0=pinit[1]) + params = self.params.copy() + params['q_1'] = qinit[0] + params['p_1'] = pinit[0] + for parity in (0, 1): + for signed in (True, False): + prop = TwoStepChebyshev(syst=syst, parity=parity, + signed=signed, **params) + prop.propagate() + prop.analyse() + self.assertLess(np.max(np.fabs(prop.er)), 1e-8) + ti, qt, pt = prop.get_trajectory() + qr, pr = syst.solution(times=ti) + self.assertTrue(np.allclose(qt, qr, rtol=1e-8)) + self.assertTrue(np.allclose(pt, pr, atol=atol32)) + + def chebyshev_propagator_lj_test(self): + """ test with a particle in Lennard-Jones potential """ + syspar = {'p0': -1.0, 'q0': 2} + params = {'tstart': 0.0, 'tend': 1.8, 'tstep': 0.02, 'nterms': 6} + syst = LennardJones(**syspar) + ref_prop = Chebyshev(syst=syst, **params) + ref_prop.propagate() + time_ref, q_ref, p_ref = ref_prop.get_trajectory() + syst = LennardJones(q0=q_ref[1], p0=p_ref[1]) + params['q_1'] = q_ref[0] + params['p_1'] = p_ref[0] + params['tstart'] = params['tstart'] + params['tstep'] + for parity in (0, 1): + for signed in (False, True): + prop = TwoStepChebyshev(syst=syst, parity=parity, + signed=signed, **params) + prop.propagate() + prop.analyse() + self.assertLess(np.max(np.fabs(prop.er)), 1e-3) + time, qt, pt = prop.get_trajectory() + assert len(time) == len(time_ref[1:]) + self.assertTrue(np.allclose(qt, q_ref[1:], rtol=1e-3)) + self.assertTrue(np.allclose(pt, p_ref[1:], rtol=1e-2)) + + class PropagatorRestartTest(unittest.TestCase): """ Tests with restarting polynomial propagators """ @@ -500,7 +570,6 @@ class NewtonianPropagatorTest(unittest.TestCase): (ti, qt, pt) = prop.get_trajectory() qr, pr = syst.solution(self.syspar['q0'], self.syspar['p0'], ti) self.assertTrue(np.allclose(qt, qr, rtol=1e-7)) - atol32 = np.finfo(np.float32).resolution self.assertTrue(np.allclose(pt, pr, atol=atol32)) @@ -547,7 +616,6 @@ class SymplecticPropagatorTest(unittest.TestCase): (ti, qt, pt) = prop.get_trajectory() qr, pr = syst.solution(self.syspar['q0'], self.syspar['p0'], ti) self.assertTrue(np.allclose(qt, qr, rtol=1e-2)) - atol32 = np.finfo(np.float32).resolution self.assertTrue(np.allclose(pt, pr, atol=atol32)) @@ -661,7 +729,7 @@ class ChebyshevAssessmentTest(unittest.TestCase): prop.analyse() t, q, p = prop.get_trajectory() qr, pr = syst.solution(times=t) - self.assertTrue(np.allclose(q, qr)) + self.assertTrue(np.allclose(q, qr)) self.assertLess(np.max(np.fabs(prop.er)), 1e-6) def chebyshev_symplectic_2p_lj_test(self):