Source code for normal_forms.normal_form
import sympy
import numpy as np
import lie_operator
import jet
import bases
import combinatorics
[docs]class normal_form(object):
"""A normal form of an autonomous vector field :math:`f:\\mathbb{R}^n\\rightarrow\\mathbb{R}^m`.
Arguments
---------
f : callable
function that accepts ``n`` arguments and returns tuple of length ``m`` numbers, corresponding to mathematical function :math:`f:\\mathbb{R}^n\\rightarrow\\mathbb{R}^m`
x : number if ``n==1`` or tuple of length ``n`` if ``n>=1``
center about which normal form is computed
k : int
maximum degree of normal form
Attributes
----------
n : int
dimension of domain of :math:`f`
m : int
dimension of codomain of :math:`f`
jet : ``normal_forms.jet.jet``
series representation of normal form
L1 : ``normal_forms.lie_operator.lie_operator``
fundamental operator of the normal form, Lie bracket with the linear term :math:`f_1(x)=f'(x)x`, that is :math:`L_{f_1}(\cdot) = [f_1,\cdot]`, see ``normal_forms.lie_operator.lie_operator``
g : list of ``k-1`` ``sympy.Matrix(m,1)`` objects
generators, i.e. homogenous :math:`j^{th}` degree :math:`m`-dimensional polynomial vector fields :math:`g_j` for :math:`j\geq2` used to carry out sequence of near-identity transformations :math:`e^{L_{g_j}}` of :math:`f`
L : ``normal_forms.lie_operator.lie_operator``
Lie operators :math:`L_{g_j}` of the generators in ``g``, see ``normal_forms.lie_operator.lie_operator``
eqv : list of shape ``(k-1,2,.,.)``
coefficients and ``sympy.Matrix(m,1)`` object representation of normal form equivariant vector fields
fun : sympy.Matrix(m,1) object
symbolic representation of normal form
"""
def __init__(self, f, x, k, f_args=None):
self.f = f
self.x = x
self.k = k
if np.array(x).shape == ():
n, x = 1, [x]
else:
n = len(x)
# call to f
if f_args is None:
f_eval = f(*x)
else:
f_eval = f(*(list(x) + list(f_args)))
if np.array(f_eval).shape == ():
m = 1
else:
# call to f
m = len(f_eval)
self.m = m
self.n = n
# list of symbolic variables
var = sympy.symarray('x', (n, ))
# polynomial basis
pb = bases.poly_basis(var)
# vector basis
vb = bases.vf_basis(pb, m)
# k-jet of f centered at x
# call to f
self.jet = jet.jet(f, x, k, f_args, var, pb)
# fundamental operator of normal form theory, Lie bracket with f'
self.L1 = lie_operator.lie_operator(self.jet.fun_deg[1], var, 1, pb, vb)
# work space of coefficients
n_terms = combinatorics.simplicial_list(n, k)
wrk = [[np.zeros(m * n_terms[i + j + 1]) for j in range(k - i)]
for i in range(k)]
# initialize first row of workspace as k-jet
for j in range(k):
wrk[0][j] = np.concatenate(self.jet.coeff[j + 1])
# generators
g = []
# Lie brackets with generators
L = []
# equivariant vector fields
eqv = []
# list of factorials
fac = combinatorics.factorial_list(k)
# algorithm based on Murdock
for deg in range(2, k + 1):
# update workspace and solve for generator
for j, l in enumerate(L):
wrk[1][deg - 2] += l[deg - 1 - j].dot(wrk[0][deg - 2 - j])
f_coeff = np.zeros(m * n_terms[deg])
for i in range(deg):
f_coeff += wrk[i][deg - 1 - i] / fac[i]
g_coeff = np.linalg.lstsq(self.L1[deg], f_coeff)[0]
# normal form coefficients
h_coeff = f_coeff - self.L1[deg].dot(g_coeff)
# represent normal form term in L1.T nullspace basis
u, s, v = np.linalg.svd(self.L1[deg])
rank = min(self.L1[deg].shape) - np.isclose(s, 0).sum()
perp_basis = u[:, rank:]
e_coeff = perp_basis.T.conj().dot(h_coeff)
e = [
sympy.Matrix(perp_basis[:, i].reshape(
m, perp_basis[:, i].shape[0] / m)) * pb[deg]
for i in range(perp_basis.shape[1])
]
# truncate roundoff error
for coeff in [e_coeff, f_coeff, g_coeff, h_coeff]:
coeff[np.isclose(coeff, 0)] = 0
# store generator
g.append(
sympy.Matrix(np.reshape(g_coeff, (m, len(g_coeff) / m))) *
pb[deg])
# update series coeff
self.jet.coeff[deg] = np.reshape(h_coeff, (m, len(h_coeff) / m))
# store equivariant vector fields
eqv.append((e_coeff, e))
# store Lie operator
L.append(lie_operator.lie_operator(g[-1], var, deg, pb, vb))
# update workspace
wrk[1][deg - 2] += L[-1][1].dot(wrk[0][0])
for i in range(2, k - deg + 2):
for j, l in enumerate(L):
wrk[i][deg -
2] += l[deg -
2 + i - j].dot(wrk[i - 1][deg - 2 - j])
self.L = L
self.g = g
self.eqv = eqv
# update series symbolic and lambdified representation
self.jet.update_fun()
# make jet.fun accessible from this class
self.fun = self.jet.fun
def __call__(self, *args):
"""Evaluate the normal form."""
return self.jet.fun_lambdified(*args)
def __getitem__(self, deg):
"""Return symbolic representation of ``deg``th degree normal form term."""
return self.jet[deg]