Files
lucidia-math/lab/amundson_equations.py
Alexa Louise f5f1551964 Initial extraction from blackroad-prism-console
Lucidia Math - Advanced mathematical engines:

forge/ - Mathematical Foundations:
- consciousness.py (650 lines) - Consciousness modeling
- unified_geometry.py (402 lines) - Geometric transformations
- advanced_tools.py (356 lines) - Advanced utilities
- main.py (209 lines) - CLI orchestration
- numbers.py, proofs.py, fractals.py, dimensions.py

lab/ - Experimental Mathematics:
- unified_geometry_engine.py (492 lines) - Geometry engine
- amundson_equations.py (284 lines) - Custom equations
- iterative_math_build.py (198 lines) - Iterative construction
- trinary_logic.py (111 lines) - Three-valued logic
- prime_explorer.py (108 lines) - Prime exploration
- quantum_finance.py (83 lines) - Quantum finance models

3,664 lines of Python.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 08:43:07 -06:00

285 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Amundson coherence, energy, and learning equations.
This module encodes the first entry in the proposed Amundson
series—*Amundson I: The Coherence Gradient Equation*—and
provides scaffolding for the follow-up energy and learning
laws. The implementation mirrors the conversation blueprint:
phase accelerates with connection and slows with decoherence.
The key constructs follow the original exposition:
* :class:`AmundsonCoherenceModel` captures the differential
dynamics
.. math::
\frac{d\phi}{dt} = \omega_0 + \lambda C(x, y) - \eta E_\phi
where ``C`` measures cosine coherence and ``E`` denotes the
decoherence energy penalty.
* :func:`coherence` isolates the cosine alignment term
:math:`C(x, y)=\cos(\phi_x-\phi_y)`.
* :func:`decoherence_energy` implements the thermal energy
penalty :math:`E_\phi = k_B T \, \lambda \, r_x r_y (1-\cos(\phi_x-\phi_y))`.
* :func:`phase_derivative` exposes the coherence gradient as a
reusable utility.
Placeholders for **Amundson II** and **Amundson III** are
included so future work can extend the energy balance and
learning formulations in situ.
"""
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import Protocol
import numpy as np
from numpy.typing import ArrayLike
def coherence(phi_x: float, phi_y: float) -> float:
"""Return the cosine coherence term :math:`C(x, y)`.
Parameters
----------
phi_x, phi_y:
Phases whose alignment is being measured.
"""
return math.cos(phi_x - phi_y)
def decoherence_energy(
*,
phi_x: float,
phi_y: float,
r_x: float,
r_y: float,
lambda_: float,
k_b_t: float,
) -> float:
"""Compute the decoherence energy :math:`E_\phi`.
Parameters mirror the seed formulation: ``r_x`` and ``r_y``
are participation amplitudes, ``lambda_`` controls coupling,
and ``k_b_t`` is the thermal scale :math:`k_B T`.
"""
return k_b_t * lambda_ * r_x * r_y * (1.0 - coherence(phi_x, phi_y))
def phase_derivative(
*,
omega_0: float,
lambda_: float,
eta: float,
phi_x: float,
phi_y: float,
r_x: float,
r_y: float,
k_b_t: float,
) -> float:
"""Evaluate the Amundson I coherence gradient equation.
This function computes :math:`\frac{d\phi}{dt}` from the
provided state, matching the proposed dynamic:
.. math::
\omega_0 + \lambda\,C(x,y) - \eta\,E_\phi
"""
c_term = coherence(phi_x, phi_y)
energy = decoherence_energy(
phi_x=phi_x,
phi_y=phi_y,
r_x=r_x,
r_y=r_y,
lambda_=lambda_,
k_b_t=k_b_t,
)
return omega_0 + lambda_ * c_term - eta * energy
class SupportsPhaseUpdate(Protocol):
"""Protocol for objects that can consume phase derivatives."""
def update_phase(self, d_phi_dt: float) -> None: # pragma: no cover - protocol signature
"""Apply the derivative to internal state."""
@dataclass(slots=True)
class AmundsonCoherenceModel:
"""Stateful helper implementing Amundson I dynamics."""
omega_0: float
lambda_: float
eta: float
k_b_t: float
def dphi_dt(
self,
*,
phi_x: float,
phi_y: float,
r_x: float,
r_y: float,
) -> float:
"""Return the instantaneous phase derivative."""
return phase_derivative(
omega_0=self.omega_0,
lambda_=self.lambda_,
eta=self.eta,
phi_x=phi_x,
phi_y=phi_y,
r_x=r_x,
r_y=r_y,
k_b_t=self.k_b_t,
)
def evolve(self, system: SupportsPhaseUpdate, **state: float) -> float:
"""Evaluate the derivative and forward it to *system*.
The method is intentionally lightweight: it returns the
derivative so callers can both inspect and apply it.
"""
derivative = self.dphi_dt(**state)
system.update_phase(derivative)
return derivative
def amundson_energy_balance(*, energy: float, dissipation: float) -> float:
"""Return the net resonant energy after accounting for dissipation.
The current Amundson II formulation focuses on conserving the
available coherence energy while subtracting the irreversible loss
channel ``dissipation``. We assume ``dissipation`` is expressed in
the same units as ``energy`` over the integration horizon. The
balance therefore follows the simple conservation law
.. math::
E_{t+1} = \max(0, E_t - D_t),
which clamps the output at zero to avoid non-physical negative
energies.
Parameters
----------
energy:
The current coherence energy :math:`E_t`.
dissipation:
The total dissipative loss :math:`D_t` over the step.
"""
if energy < 0:
raise ValueError("energy must be non-negative to preserve physical meaning")
if dissipation < 0:
raise ValueError("dissipation must be non-negative")
return max(0.0, energy - dissipation)
def _ensure_vector(name: str, value: ArrayLike) -> np.ndarray:
"""Return *value* as a 1-D float vector."""
array = np.asarray(value, dtype=float)
if array.ndim == 0:
return array.reshape(1)
if array.ndim != 1:
raise ValueError(f"{name} must be a 1-D vector, received shape {array.shape}.")
return array
def _ensure_metric(name: str, value: ArrayLike, dimension: int) -> np.ndarray:
"""Validate that *value* is a symmetric positive-definite matrix."""
matrix = np.asarray(value, dtype=float)
if matrix.ndim == 0:
matrix = matrix.reshape(1, 1)
if matrix.ndim != 2 or matrix.shape[0] != matrix.shape[1]:
raise ValueError(f"{name} must be a square matrix, received shape {matrix.shape}.")
if matrix.shape[0] != dimension:
raise ValueError(
f"{name} dimension {matrix.shape[0]} does not match vector length {dimension}."
)
if not np.allclose(matrix, matrix.T, atol=1e-10):
raise ValueError(f"{name} must be symmetric to act as a metric tensor.")
try:
np.linalg.cholesky(matrix)
except np.linalg.LinAlgError as exc: # pragma: no cover - defensive branch
raise ValueError(f"{name} must be positive definite.") from exc
return matrix
def amundson_learning_update(
*,
weights: ArrayLike,
gradient: ArrayLike,
metric: ArrayLike,
learning_rate: float,
k_b_t: float,
noise: ArrayLike | None = None,
rng: np.random.Generator | None = None,
) -> np.ndarray:
r"""Apply the Amundson III Resonant Natural Gradient update.
The update follows the stochastic natural-gradient rule described
for the Resonant Natural Gradient option:
.. math::
\theta_{t+1} = \theta_t - \eta G^{-1} \nabla_\theta \mathcal F
+ \sqrt{2\eta k_B T}\, G^{-1/2} \xi,
where ``weights`` is :math:`\theta_t`, ``gradient`` is the free-energy
gradient, ``metric`` is the Fisher (or GaussNewton) metric
:math:`G`, and ``noise`` supplies the standard-normal samples
:math:`\xi`. Setting ``k_b_t`` to zero removes the stochastic term
and recovers standard gradient descent when the metric is the
identity.
"""
weight_vec = _ensure_vector("weights", weights)
grad_vec = _ensure_vector("gradient", gradient)
if weight_vec.shape != grad_vec.shape:
raise ValueError(
"weights and gradient must share the same shape, "
f"received {weight_vec.shape} and {grad_vec.shape}."
)
metric_tensor = _ensure_metric("metric", metric, weight_vec.size)
chol = np.linalg.cholesky(metric_tensor)
natural_grad = np.linalg.solve(metric_tensor, grad_vec)
deterministic_step = -learning_rate * natural_grad
scale = math.sqrt(max(0.0, 2.0 * learning_rate * k_b_t))
if noise is None:
if scale == 0.0:
stochastic_step = np.zeros_like(weight_vec)
else:
generator = rng if rng is not None else np.random.default_rng()
xi = generator.standard_normal(size=weight_vec.shape)
stochastic_step = scale * np.linalg.solve(chol.T, xi)
else:
xi = _ensure_vector("noise", noise)
if xi.shape != weight_vec.shape:
raise ValueError(
"noise must match the dimensionality of the weights, "
f"received {xi.shape} and {weight_vec.shape}."
)
if scale == 0.0:
stochastic_step = np.zeros_like(weight_vec)
else:
stochastic_step = scale * np.linalg.solve(chol.T, xi)
return weight_vec + deterministic_step + stochastic_step