Random energy
The random energy system.
- Class:
RandomEnergySystem - Import:
from skcomplex.systems.physics.statistical_mechanics import RandomEnergySystem - Field:
physics/statistical_mechanics - Topics:
disorder,glass transition,static - References: Derrida 1981; Bertin 2021
Use this system
from skcomplex.core.engine.runtime import simulate
from skcomplex.systems.physics.statistical_mechanics import RandomEnergySystem
trajectory = simulate(RandomEnergySystem())
Parameters
| Parameter | Default |
|---|---|
n_spins |
10 |
temperature |
1.0 |
random_state |
0 |
Tags
| Tag | Values |
|---|---|
| Time | static |
| Space | none |
| Laws | equation |
| Randomness | deterministic |
| Domains | real |
| Structure | fixed |
Class
class RandomEnergySystem(BaseSystem):
"""The random energy system.
It has `2^N` quenched Gaussian energies of variance `N / 2` at a temperature.
The entropy per spin follows `ln 2 - 1 / (4 T^2)` above the glass temperature `T_g = 1 / (2 sqrt(ln 2))` and freezes
below it, where the Boltzmann weight falls on the few lowest levels (Bertin 2021 §1.5.3). The energies are drawn
from `random_state`, so a disorder average sweeps it with `sweep`. The system has the states
`ensemble.log_partition`, `ensemble.energy`, and `ensemble.entropy`.
Args:
n_spins:
The number of spins `N`.
temperature:
The temperature.
random_state:
The seed or generator of the energies.
label:
The label of the system, which names it when it is nested in another one.
Examples:
>>> from skcomplex.systems.physics.statistical_mechanics import RandomEnergySystem
>>> from skcomplex.core.engine.runtime import simulate
>>> system = RandomEnergySystem(12, temperature=2.0).prepare().system_
>>> trajectory = simulate(system, until=1.0)
>>> bool(abs(trajectory.values('ensemble.entropy')[0] - (np.log(2) - 1 / 16)) < 0.05)
True
"""
field = 'physics/statistical_mechanics'
topics = ('disorder', 'glass transition', 'static')
references = ('Derrida 1981', 'Bertin 2021')
def __init__(
self,
n_spins: int = 10,
*,
temperature: float = 1.0,
random_state: int | np.random.Generator | None = 0,
label: str = 'system',
) -> None:
super().__init__(label)
self.n_spins = n_spins
self.temperature = temperature
self.random_state = random_state
def _build_system(self) -> System:
"""Return the random energy system.
It has `2^N` quenched Gaussian energies of variance `N / 2` at a temperature.
"""
size = 2**self.n_spins
energies = np.random.default_rng(self.random_state).normal(0.0, np.sqrt(self.n_spins / 2), size)
visual = VisualSpec('histograms', {'color_by': 'energy'})
levels = GroupElement(
'levels',
element=EnergyLevelElement,
size=size,
values={'energy': energies},
visual=visual,
)
sums = BoltzmannSumInteraction(
'sums',
target='ensemble',
levels='levels',
temperature=self.temperature,
n_spins=float(self.n_spins),
)
return System(
self.label,
elements=[levels, BoltzmannAverageElement('ensemble')],
interactions=[sums],
time=StaticTime(),
)
Components
Elements:
EnergyLevelElement, fromElementBoltzmannAverageElement, fromElement
Interactions:
BoltzmannSumInteraction, fromInteraction
class EnergyLevelElement(Element):
"""Energy level of a configuration, held fixed.
Args:
label:
The label of the level or the group.
energy:
The energy of the level.
Examples:
>>> from skcomplex.systems.physics.statistical_mechanics import EnergyLevelElement
>>> EnergyLevelElement('level', energy=-1.5).energy
-1.5
"""
def __init__(self, label: str, energy: Annotated[float, StateSpec()] = 0.0) -> None:
super().__init__(label)
self.energy = energy
class BoltzmannAverageElement(Element):
"""Log partition function, mean energy, and entropy per spin of a group of levels.
Args:
label:
The label of the element.
log_partition:
The initial guess of the log partition function, which the equations replace.
energy:
The initial guess of the mean energy, which the equations replace.
entropy:
The initial guess of the entropy per spin, which the equations replace.
Examples:
>>> from skcomplex.systems.physics.statistical_mechanics import BoltzmannAverageElement
>>> BoltzmannAverageElement('ensemble').entropy
0.0
"""
def __init__(
self,
label: str,
log_partition: Annotated[float, StateSpec()] = 0.0,
energy: Annotated[float, StateSpec()] = 0.0,
entropy: Annotated[float, StateSpec()] = 0.0,
) -> None:
super().__init__(label)
self.log_partition = log_partition
self.energy = energy
self.entropy = entropy
class BoltzmannSumInteraction(Interaction):
"""Boltzmann sums over a group of levels.
The sums give the log partition function, mean energy, and entropy per spin.
The log partition function is `ln Z = log sum_j exp(-E_j / T)`, the mean energy is
`U = sum_j E_j exp(-E_j / T - ln Z)`, and the entropy per spin is `(ln Z + U / T) / N`. The sums reduce in log
space, so they do not overflow at any temperature.
Args:
label:
The label of the interaction.
target:
The path of the element that holds the averages.
levels:
The path of the group of levels.
temperature:
The temperature.
n_spins:
The number of spins the entropy is divided by.
Examples:
>>> from skcomplex.systems.physics.statistical_mechanics import BoltzmannSumInteraction
>>> BoltzmannSumInteraction('sums', target='ensemble', levels='levels', temperature=0.5).temperature
0.5
"""
law_classes = (EquationLaw,)
def __init__(
self,
label: str,
*,
target: Annotated[str, RoleSpec()],
levels: Annotated[str, RoleSpec()],
temperature: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
n_spins: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
) -> None:
super().__init__(label)
self.target = target
self.levels = levels
self.temperature = temperature
self.n_spins = n_spins
def _get_laws(self, s: SimpleNamespace) -> Sequence[BasePart]:
"""Return the log partition function, mean energy, and entropy per spin equations.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The three equations.
"""
weight = -s.levels.energy / s.temperature
log_partition = log_sum_exp(weight, over=s.levels)
energy = total(s.levels.energy * exp(weight - log_partition), over=s.levels)
entropy = (s.target.log_partition + s.target.energy / s.temperature) / s.n_spins
return [
EquationLaw(s.target.log_partition == log_partition, target=s.target.log_partition),
EquationLaw(s.target.energy == energy, target=s.target.energy),
EquationLaw(s.target.entropy == entropy, target=s.target.entropy),
]