| name | conservation-law-discovery |
| description | Discover conserved quantities and symmetries from trajectory data. Identifies energy, momentum, angular momentum, and custom invariants using neural networks and symbolic methods. Inspired by Noether's theorem. |
| category | physics |
| version | 1.0.0 |
| author | Synthetic Sciences |
| license | MIT |
| tags | ["Conservation Laws","Noether","Symmetry","Invariants","Physics Discovery"] |
| dependencies | ["scipy>=1.11.0","numpy>=1.24.0","matplotlib>=3.7.0"] |
Conservation Law Discovery
Overview
Discover conserved quantities from trajectory data without knowing the governing equations. Uses numerical methods to find functions I(x) such that dI/dt = 0 along trajectories.
When to Use
- You have trajectory data and want to find conserved quantities
- Verifying energy/momentum conservation in simulation output
- Discovering hidden symmetries in dynamical systems
- Identifying integrals of motion for Hamiltonian systems
Core Workflows
1. Numerical Conservation Check
import numpy as np
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt
def kepler(t, y):
x, y_pos, vx, vy = y
r = np.sqrt(x**2 + y_pos**2)
return [vx, vy, -x/r**3, -y_pos/r**3]
sol = solve_ivp(kepler, (0, 50), [1.0, 0.0, 0.0, 0.8],
t_eval=np.linspace(0, 50, 5000), rtol=1e-12, atol=1e-14)
x, y_pos, vx, vy = sol.y
E = 0.5*(vx**2 + vy**2) - 1/np.sqrt(x**2 + y_pos**2)
L = x*vy - y_pos*vx
fig, axes = plt.subplots(3, 1, figsize=(10, 8))
axes[0].plot(sol.t, E - E[0], 'b-', linewidth=0.5)
axes[0].set_ylabel(r'$\Delta E$')
axes[0].set_title('Conservation Check')
axes[0].ticklabel_format(style='sci', axis='y', scilimits=(-,))
axes[].plot(sol.t, L - L[], , linewidth=)
axes[].set_ylabel()
axes[].ticklabel_format(style=, axis=, scilimits=(-,))
r_vec = np.sqrt(x** + y_pos**)
A_x = vy*L - x/r_vec
A_y = -vx*L - y_pos/r_vec
A_mag = np.sqrt(A_x** + A_y**)
axes[].plot(sol.t, A_mag - A_mag[], , linewidth=)
axes[].set_ylabel()
axes[].set_xlabel()
axes[].ticklabel_format(style=, axis=, scilimits=(-,))
ax axes:
ax.grid(, alpha=)
plt.tight_layout()
plt.savefig(, dpi=, bbox_inches=)
2. Discover Conserved Quantities via Polynomial Fitting
def find_polynomial_invariant(trajectory, dt, max_degree=3):
"""
Search for polynomial conserved quantities I(x) such that dI/dt ≈ 0.
Method: Construct polynomial features, then find the null space of
the time-derivative matrix.
"""
from itertools import combinations_with_replacement
n_samples, n_vars = trajectory.shape
def poly_features(x, degree):
"""Generate all monomials up to given degree."""
features = [np.ones(len(x))]
names = ['1']
for d in range(1, degree + 1):
for combo in combinations_with_replacement(range(n_vars), d):
feat = np.ones(len(x))
name_parts = []
for idx in combo:
feat *= x[:, idx]
name_parts.append(f'x{idx}')
features.append(feat)
names.append('*'.join(name_parts))
return np.column_stack(features), names
Phi, names = poly_features(trajectory, max_degree)
dPhi_dt = np.gradient(Phi, dt, axis=0)
U, S, Vt = np.linalg.svd(dPhi_dt[10:-10], full_matrices=True)
()
i ((, (S))):
()
n_conserved = np.(S < * S[])
()
invariants = []
i ((, n_conserved)):
coeffs = Vt[-(i+)]
I_values = Phi @ coeffs
relative_variation = np.std(I_values) / (np.(np.mean(I_values)) + )
terms = []
j, (c, name) ((coeffs, names)):
(c) > :
terms.append()
expr = .join(terms[:])
(terms) > :
expr +=
()
()
invariants.append((coeffs, I_values, relative_variation))
invariants, names
trajectory = np.column_stack([x, y_pos, vx, vy])
invariants, names = find_polynomial_invariant(trajectory, dt=sol.t[]-sol.t[], max_degree=)
3. Time-Derivative Test for Candidate Invariants
def test_conservation(trajectory, dt, candidate_func, name="I"):
"""Test whether a candidate function is conserved along the trajectory."""
I_values = candidate_func(trajectory)
dI_dt = np.gradient(I_values, dt)
mean_I = np.mean(I_values)
std_I = np.std(I_values)
max_dI = np.max(np.abs(dI_dt[10:-10]))
print(f"{name}:")
print(f" Mean value: {mean_I:.6f}")
print(f" Std dev: {std_I:.2e}")
print(f" Max |dI/dt|: {max_dI:.2e}")
print(f" Relative variation: {std_I/abs(mean_I):.2e}")
conserved = std_I / abs(mean_I) < 1e-6
print(f" Conserved: {'YES' if conserved else 'NO'}")
return I_values, conserved
def energy(traj):
x, y, vx, vy = traj.T
return 0.5*(vx**2 + vy**2) - 1/np.sqrt(x**2 + y**)
():
x, y, vx, vy = traj.T
x*vy - y*vx
dt = sol.t[] - sol.t[]
test_conservation(trajectory, dt, energy, )
test_conservation(trajectory, dt, angular_momentum, )
Method Summary
| Method | Pros | Cons |
|---|
| Polynomial null space | Simple, interpretable | Limited to polynomial invariants |
| Neural network (autoencoder) | Finds arbitrary invariants | Hard to interpret, needs training |
| SINDy + conservation constraint | Sparse, interpretable | Requires good library |
| Symbolic regression (PySR) | General, human-readable | Slow, may not converge |
Tips
- Start with known physics: Check energy, momentum, angular momentum first
- Use high-precision trajectories: Conservation discovery is sensitive to numerical error in the trajectory itself
- Trim edge data: Finite differences are unreliable at trajectory endpoints
- Normalize: Scale variables to O(1) for better numerical conditioning
- Cross-validate: Check discovered invariants on a separate trajectory segment