"""Thermodynamic quantities for free energy calculation."""
from typing import Optional
import numpy as np
[docs]
class InternalEnergy:
"""
Computes internal energy E(P) of a pattern.
E = constraint_violation + kinetic_energy + potential_energy
"""
[docs]
@staticmethod
def constraint_energy(constraint_violation: float, stiffness: float = 1.0) -> float:
"""E_constraint = κ * ||C(v)||²."""
return stiffness * constraint_violation**2
[docs]
@staticmethod
def kinetic_energy(state_velocities: np.ndarray, mass: float = 1.0) -> float:
"""E_kin = ½ m Σ v_i²."""
return 0.5 * mass * np.sum(state_velocities**2)
[docs]
@staticmethod
def potential_energy(state_positions: np.ndarray, spring_constant: float = 0.1) -> float:
"""E_pot = ½ k Σ (x_i - x₀)² (harmonic approximation)."""
mean = np.mean(state_positions, axis=0)
return 0.5 * spring_constant * np.sum((state_positions - mean) ** 2)
[docs]
@classmethod
def total_energy(cls, pattern, state_velocities: Optional[np.ndarray] = None) -> float:
"""E_total = E_constraint + E_kin + E_pot."""
E_constraint = cls.constraint_energy(pattern.C.violation_norm(pattern.V))
E_pot = cls.potential_energy(pattern.V)
if state_velocities is not None:
E_kin = cls.kinetic_energy(state_velocities)
else:
E_kin = 0.0
return E_constraint + E_kin + E_pot
[docs]
class EffectiveTemperature:
"""
Dynamic effective temperature T_eff.
Evolves according to: dT/dt = -α (T - T_env) + β dE/dt
"""
def __init__(
self,
initial_temp: float = 1.0,
env_temp: float = 1.0,
cooling_rate: float = 0.01,
heating_coeff: float = 0.1,
):
self.T = initial_temp
self.T_env = env_temp
self.alpha = cooling_rate
self.beta = heating_coeff
[docs]
def update(self, dt: float, dE_dt: float) -> float:
"""Update temperature based on energy change."""
dT_dt = -self.alpha * (self.T - self.T_env) + self.beta * abs(dE_dt)
self.T += dT_dt * dt
# Prevent negative temperature
self.T = max(self.T, 0.01)
return self.T
[docs]
class FullFreeEnergy:
"""
Implements F = E - T_eff * S_gen.
"""
[docs]
def __init__(
self,
temperature: Optional[EffectiveTemperature] = None,
entropy_weights: tuple = (1.0, 1.0, 1.0),
):
"""
Parameters
----------
temperature : EffectiveTemperature, optional
If None, uses constant T_eff = 1.0.
entropy_weights : tuple
(β_info, β_therm, β_ph) for generalized entropy.
"""
self.temperature = temperature
self.beta_info, self.beta_therm, self.beta_ph = entropy_weights
self.energy_calc = InternalEnergy()
[docs]
def generalized_entropy(self, pattern, state_velocities: Optional[np.ndarray] = None) -> float:
"""S_gen = β_info·I + β_therm·S_therm + β_ph·(1 - coherence)."""
from mercurial.core.entropy import GeneralizedEntropy
entropy_calc = GeneralizedEntropy(self.beta_info, self.beta_therm, self.beta_ph)
info_entropy = pattern.information_content()
# Approximate thermodynamic entropy from energy dispersion
therm_entropy = np.log(np.var(pattern.V) + 1e-10) if np.var(pattern.V) > 0 else 0.0
ph_disorder = 1.0 - pattern.coherence()
return entropy_calc.compute(info_entropy, therm_entropy, ph_disorder)
[docs]
def compute(
self,
pattern,
state_velocities: Optional[np.ndarray] = None,
regularization_penalty: float = 0.0,
) -> float:
"""F = E - T_eff * S_gen + λ * ||θ||²."""
E = self.energy_calc.total_energy(pattern, state_velocities)
S_gen = self.generalized_entropy(pattern, state_velocities)
if self.temperature is not None:
T_eff = self.temperature.T
else:
T_eff = 1.0
return E - T_eff * S_gen + regularization_penalty