Source code for mercurial.core.thermodynamics

"""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