diff --git a/examples/test_cheb_mo_1p_1d_symmetric.py b/examples/test_cheb_mo_1p_1d_symmetric.py index 17254c926982f765da7e3d945fcd306714a92e44..66538973ccc9a6033be01f2aaa579ec52f8523ed 100644 --- a/examples/test_cheb_mo_1p_1d_symmetric.py +++ b/examples/test_cheb_mo_1p_1d_symmetric.py @@ -14,7 +14,7 @@ numeric = {'tool': 'lambdify', 'modules': 'mpmath'} params = {'tstart': spfloat(0., prec), 'tend': spfloat(100., prec), 'tstep': spfloat(0.2, prec), 'nterms': 6, 'tqdm': True} -# params = {'tstart': 0., 'tend': 100., 'tstep': 0.4, 'nterms': 4, 'tqdm': True} +# params = {'tstart': 0., 'tend': 100., 'tstep': 0.2, 'nterms': 6, 'tqdm': True} syspar = {'q0': spfloat(3., prec), 'p0': spfloat(0., prec), 'fpprec': 'multi', 'prec': prec} diff --git a/examples/test_cheb_mo_2p_3d_symmetric.py b/examples/test_cheb_mo_2p_3d_symmetric.py new file mode 100644 index 0000000000000000000000000000000000000000..08f6df47ff49e3281768f2a258d6f80fff9dd34e --- /dev/null +++ b/examples/test_cheb_mo_2p_3d_symmetric.py @@ -0,0 +1,105 @@ +""" Three dimensional Lennard-Jones gas with two particles """ +import numpy as np +import pickle as pkl +import matplotlib.pyplot as plt +from rotagaporp.chebyshev import SymmetricChebyshev +from rotagaporp.chebyshev import Chebyshev +from rotagaporp.symplectic import Symplectic +from rotagaporp.mpliouville import MPLPQP +from rotagaporp.systems import MPPS3D, Morse + +M = [2.0, 2.0] +numeric = {'tool': 'lambdify', 'modules': 'numpy'} +params = {'tstart': 0.0, 'tend': 1000., 'tstep': 0.1, 'tqdm': False} +syspar = {'symbols': ['0x:z', '1x:z'], 'q0': [[1, 2, 3], [4, 2, 3]], + 'p0': [[0, 0, 0], [0, 0, 0]], 'masses': M, 'numeric': numeric} +ffpars = [{'class': 'FFMorse', 'pair': ((0, 1, 2), (3, 4, 5)), + 'params': {'distance': 1.}}] +system = MPPS3D(ffpars, **syspar) + +# velocity Verlet +vv_prop = Symplectic(syst=system, **params) +vv_prop.propagate() +time_vv, q_vv, p_vv = vv_prop.get_trajectory_3d() +r_vv = np.abs(q_vv[:, 1]-q_vv[:, 0]) +r_vv_1p = np.linalg.norm(r_vv, axis=1) +pr_vv = (p_vv[:, 1]*M[0]-p_vv[:, 0]*M[1])/np.sum(M) +pr_vv_1p = np.einsum('ij,ij->i', pr_vv, r_vv/r_vv_1p[:, np.newaxis]) +vv_prop.analyse() +print('energy drift of velocity verlet prop', np.max(np.fabs(vv_prop.er))) + +system_1d = Morse(q0=3., p0=0.) +q_an, p_an = system_1d.solution(times=time_vv) +en0 = system_1d.energy(system_1d.q0, system_1d.p0) +evecf = np.vectorize(system_1d.energy) +err_an = np.array((evecf(q_an, p_an)-en0)/en0, dtype=float) +print('energy drift of analytic solution', np.max(np.fabs(err_an))) + +# one-step propagator +params['liouville'] = MPLPQP +params['nterms'] = 4 +ch_prop = Chebyshev(syst=system, **params) +ch_prop.propagate() +time_ch, q_ch, p_ch = ch_prop.get_trajectory_3d() +r_ch = np.abs(q_ch[:, 1]-q_ch[:, 0]) +r_ch_1p = np.linalg.norm(r_ch, axis=1) +pr_ch = (p_ch[:, 1]*M[0]-p_ch[:, 0]*M[1])/np.sum(M) +pr_ch_1p = np.einsum('ij,ij->i', pr_ch, r_ch/r_ch_1p[:, np.newaxis]) +ch_prop.analyse() +print('energy drift of one-step prop', np.max(np.fabs(ch_prop.er))) + +# symmetrized propagator +params['numeric'] = numeric +ts_prop = SymmetricChebyshev(syst=system, **params) +ts_prop.propagate() +time_ts, q_ts, p_ts = ts_prop.get_trajectory_3d() +r_ts = np.abs(q_ts[:, 1]-q_ts[:, 0]) +r_ts_1p = np.linalg.norm(r_ts, axis=1) +pr_ts = (p_ts[:, 1]*M[0]-p_ts[:, 0]*M[1])/np.sum(M) +pr_ts_1p = np.einsum('ij,ij->i', pr_ts, r_ts/r_ts_1p[:, np.newaxis]) +ts_prop.analyse() +print('energy drift of symmetric prop', np.max(np.fabs(ts_prop.er))) + +assert len(time_ts) == len(time_ch) == len(time_vv) +assert np.allclose(time_ts, time_ch) +assert np.allclose(time_ts, time_vv) + +fig = plt.figure() +plot1 = fig.add_subplot(311) +plot1.plot(time_ch, np.maximum.accumulate(abs(ch_prop.er)), + label='Chebyshev') +plot1.plot(time_ts, np.maximum.accumulate(abs(ts_prop.er)), + label='Symmetric Chebyshev') +plot1.plot(time_ts, np.maximum.accumulate(abs(vv_prop.er)), + 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, r_ch_1p, label='Chebyshev') +plot2.plot(time_ts, r_ts_1p, label='Symmetric Chebyshev') +plot2.plot(time_ts, r_vv_1p, label='Velocity Verlet') +plot2.plot(time_ts, q_an, label='Analytic solution') +plot2.set_xlabel(r'$t$') +plot2.set_ylabel(r'$r(t)$') +plot2.set_xscale('log') +plot2.legend() + +plot3 = fig.add_subplot(313) +plot3.plot(time_ts, pr_ch_1p, label='Chebyshev') +plot3.plot(time_ts, pr_ts_1p, label='Symmetric Chebyshev') +plot3.plot(time_ts, pr_vv_1p, label='Velocity Verlet') +plot3.plot(time_ts, p_an, label='Analytic solution') +plot3.set_xlabel(r'$t$') +plot3.set_ylabel(r'$p_r(t)$') +plot3.set_xscale('log') +plot3.legend() + +with open('pltobj.pkl', 'wb') as fh: + pkl.dump(fig, fh) + +fig.savefig('pltfig.png') + +# plt.show() diff --git a/rotagaporp/liouville.py b/rotagaporp/liouville.py index 316a23cc645ca8c94e69fd42192210de14c0cf4c..05cf443ca6f6397049134617afaa468df5c24f48 100644 --- a/rotagaporp/liouville.py +++ b/rotagaporp/liouville.py @@ -218,6 +218,8 @@ class SPLPQP: function=system.q, **kwargs) self.pobj = SPLP(nterms, system.hamiltonian, system.q, system.p, function=system.p, **kwargs) + self.ilnqcart = self.qobj.Lf + self.ilnpcart = self.pobj.Lf def get_val_float(self, qval, pval): """ evaluate the symbolic expressions for given values of q and p """ diff --git a/rotagaporp/propagators.py b/rotagaporp/propagators.py index 47184261a1c9a9a9f1fbda7fc65e7b560ad09388..95820e3d9948b55ce7cb06605c737348fc9346c2 100644 --- a/rotagaporp/propagators.py +++ b/rotagaporp/propagators.py @@ -3,6 +3,7 @@ import mpmath as mp import numpy as np import sympy as sp +from collections.abc import Iterable from liouville import SPLPQP class Propagator: @@ -206,12 +207,17 @@ class SymmetricPolynomialPropagator(PolynomialPropagator): multi mp.Float subs None sympy step_sympy multi mp.Float lambdify mpmath mpmath step_mpmath ------------------------------------------------------------------- + NOTABENE: for numeric tool lambdify modules 'tensorflow' and 'math' + are currently not supported. """ from generic import validate_numeric if numeric is not None: self.numeric = numeric + elif hasattr(self.syst, 'numeric'): + self.numeric = self.syst.numeric + tool = self.numeric['tool'] assert tool in ['subs', 'lambdify', 'theano'] if tool == 'subs': @@ -247,72 +253,81 @@ class SymmetricPolynomialPropagator(PolynomialPropagator): self.matinv = np.linalg.inv self.normf = np.linalg.norm + if isinstance(self.q, Iterable): + assert isinstance(self.p, Iterable) + assert isinstance(self.syst.q, Iterable) + assert isinstance(self.syst.p, Iterable) + assert len(self.q) == len(self.p) + assert len(self.q) == len(self.syst.q) + assert len(self.syst.q) == len(self.syst.p) + self.concat = lambda a, b: (*a, *b) + self.split = lambda x: (x[:len(x)//2], x[len(x)//2:]) + else: + self.concat = lambda a, b: (a, b) + self.split = lambda x: (x[0], x[1]) + def set_gvar_jacobian(self): """ initialize the G_var vector function and the Jacobian matrix """ if self.fpprec == 'multi': self.coeffs = sp.Matrix(self.coeffs) self.reverse_coeffs = sp.Matrix(self.reverse_coeffs) - - gq = self.syst.q + self.reverse_coeffs.dot(self.iln.qobj.Lf) - gp = self.syst.p + self.reverse_coeffs.dot(self.iln.pobj.Lf) - self.z = sp.Matrix((self.syst.q, self.syst.p)) - jacobian = sp.Matrix([gq, gp]).jacobian(self.z) + ugq =self.reverse_coeffs.dot(self.iln.ilnqcart) + ugp = self.reverse_coeffs.dot(self.iln.ilnpcart) + if isinstance(self.syst.q, Iterable): + gq = sp.Matrix(self.syst.q) + sp.Matrix(ugq) + gp = sp.Matrix(self.syst.p) + sp.Matrix(ugp) + else: + gq = self.syst.q + ugq + gp = self.syst.p + ugp + self.z = sp.Matrix(self.concat(self.syst.q, self.syst.p)) + jacobian = sp.Matrix(self.concat(gq, gp)).jacobian(self.z) self.matrix_shape = jacobian.shape + self.vector_shape = (self.z.shape[0],) if self.numeric['tool'] == 'lambdify': modules = self.numeric['modules'] if self.numeric['modules'] == 'mpmath': - gvar = sp.Matrix([gq, gp]) + gvar = sp.Matrix(self.concat(gq, gp)) else: - gvar = sp.Array([gq, gp]) + gvar = sp.Array(self.concat(gq, gp)) self.jacobian = sp.lambdify(self.z, jacobian, modules) self.gvar = sp.lambdify(self.z, gvar, modules) elif self.numeric['tool'] == 'theano': from sympy.printing.theanocode import theano_function as tf - gvar = sp.Matrix([gq, gp]) + gvar = sp.Matrix(self.concat(gq, gp)) self.jacobian = tf(self.z, jacobian, on_unused_input='ignore') self.gvar = tf(self.z, gvar, on_unused_input='ignore') else: self.jacobian = jacobian - self.gvar = sp.Matrix([gq, gp]) + self.gvar = sp.Matrix(self.concat(gq, gp)) - def matrix_eval(self, matrix, zval): + def array_eval(self, array, shape, zval): + """ evaluate the elements of a matrix that are sympy expressions """ if self.numeric['tool'] == 'subs': - ret = matrix.xreplace(dict(zip(self.z, zval))).evalf() + retval = array.xreplace(dict(zip(self.z, zval))).evalf() else: + retval = array(*zval) if self.linalg == 'numpy': - ret = np.array(matrix(*zval)) - ret.shape = self.matrix_shape - else: - ret = matrix(*zval) - return ret - - def vector_eval(self, vector, zval): - if self.numeric['tool'] == 'subs': - ret = vector.xreplace(dict(zip(self.z, zval))).evalf() - else: - if self.linalg == 'numpy': - ret = np.array(vector(*zval)) - else: - ret = vector(*zval) - return ret + retval = np.array(retval) + retval.shape = shape + return retval def step_nopbc(self, time, qval, pval): - """ implicit stepping procedure using Newton iteration """ - zval = self.matrix([qval, pval]) + """ implicit time stepping procedure using Newton iteration """ qvec, pvec = self.iln.get_val_float(qval, pval) - zval0 = self.matrix([self.coeffs.dot(qvec), self.coeffs.dot(pvec)]) - gcon = zval + zval0 - zval1 = zval0.copy() + qt = self.coeffs.dot(qvec) + pt = self.coeffs.dot(pvec) + zval = self.matrix(self.concat(qt, pt)) + gcon = self.matrix(self.concat(qval, pval)) + zval residue = 1. numiter = 0 while residue > 10**(-self.prec/2): if numiter > self.maxiter: raise RuntimeError('maximum number of iterations exceeded') - jacval = self.matrix_eval(self.jacobian, zval1) - gvar = self.vector_eval(self.gvar, zval1) + jacval = self.array_eval(self.jacobian, self.matrix_shape, zval) + gvar = self.array_eval(self.gvar, self.vector_shape, zval) correction = self.matmul(self.matinv(jacval), gvar-gcon) - zval1 = zval1 - correction + zval = zval - correction residue = self.normf(correction) numiter += 1 - return (zval1[0], zval1[1]) + return self.split(zval) diff --git a/tests/sy_tests.py b/tests/sy_tests.py index cac8387831d698204fdcbe65c5b76f0a28cf524e..9c09777c4d9786b7a2c253429c9626b8534ecaef 100644 --- a/tests/sy_tests.py +++ b/tests/sy_tests.py @@ -4,7 +4,8 @@ import numpy as np from rotagaporp.generic import spfloat from rotagaporp.chebyshev import SymmetricChebyshev from rotagaporp.newtonian import SymmetricNewtonian -from rotagaporp.systems import Morse +from rotagaporp.systems import Morse, MPPSMD +from rotagaporp.mpliouville import MPLPQP class TestSymmetricPolynomialPropagator(unittest.TestCase): """ Test the self-starting symmetric polynomial propagator """ @@ -18,7 +19,6 @@ class TestSymmetricPolynomialPropagator(unittest.TestCase): qan, pan = syst.solution(times=tsy) qanfl = np.array(qan, dtype=float) panfl = np.array(pan, dtype=float) - print(qsy, qanfl) self.assertTrue(np.allclose(qsy, qanfl, atol=atol)) self.assertTrue(np.allclose(psy, panfl, atol=atol)) self.assertTrue(np.allclose(sy.er, 0, atol=atol)) @@ -148,3 +148,38 @@ class TestSymmetricPolynomialPropagator(unittest.TestCase): 'fpprec': 'multi', 'prec': prec} system = Morse(**syspar) self.compare(SymmetricNewtonian, params, system, 1e-2) + + +class TestSymmetricPolynomialPropagatorMP(unittest.TestCase): + """ Self-starting symmetric polynomial propagator - many particles """ + + def test_cheb_morse_2p_2d_lambdify(self): + """ Chebyshev for two-particle 2D Morse oscillator with lambdify """ + + M = [2.0, 2.0] + numeric = {'tool': 'lambdify', 'modules': 'numpy'} + params = {'tstart': 0.0, 'tend': 5., 'tstep': 0.1, 'nterms': 4, + 'liouville': MPLPQP, 'numeric': numeric} + syspar = {'symbols': ['0x:y', '1x:y'], 'q0': [[1, 2], [4, 2]], + 'p0': [[0, 0], [0, 0]], 'masses': M, 'numeric': numeric} + ffpars = [{'class': 'FFMorse', 'pair': ((0, 1), (2, 3)), + 'params': {'distance': 1.}}] + + system = MPPSMD(ffpars, dim=2, **syspar) + ts_prop = SymmetricChebyshev(syst=system, **params) + ts_prop.propagate() + time_ts, q_ts, p_ts = ts_prop.get_trajectory() + q_ts.shape = (len(q_ts), -1, 2) + p_ts.shape = (len(p_ts), -1, 2) + r_ts = np.abs(q_ts[:, 1]-q_ts[:, 0]) + r_ts_1p = np.linalg.norm(r_ts, axis=1) + pr_ts = (p_ts[:, 1]*M[0]-p_ts[:, 0]*M[1])/np.sum(M) + pr_ts_1p = np.einsum('ij,ij->i', pr_ts, r_ts/r_ts_1p[:, np.newaxis]) + ts_prop.analyse() + + system_1d = Morse(q0=3., p0=0.) + q_an, p_an = system_1d.solution(times=time_ts) + + self.assertTrue(np.allclose(r_ts_1p, q_an, atol=1e-2)) + self.assertTrue(np.allclose(pr_ts_1p, p_an, atol=1e-2)) + self.assertTrue(np.allclose(ts_prop.er, 0, atol=1e-2))