mirror of
https://github.com/blackboxprogramming/lucidia.git
synced 2026-03-17 05:57:21 -05:00
Synced from BlackRoad-OS-Inc/blackroad-operator/orgs/personal/lucidia BlackRoad OS — Pave Tomorrow. RoadChain-SHA2048: fe729062952871e7 RoadChain-Identity: alexa@sovereign RoadChain-Full: fe729062952871e77147cf6d938b799096e87d9024d7005a14c9e209e12e8ad0c825b624c7bc649fc7eeb4c284fdcab8231af77980065cc04d9f36fca479ffc2346ed3c1b73de6f240d8f9485f47c995ad5b81142f7179b84932c67914dff1c08db039349ba28fca36cb57688093bf0199268dd1c2f3448c9383000bc77cc9663066ff57b834370afc8838b18466ea9029908018b961555cccaabf2ce21649cf3cabc7f64bdcc4abdf2da259b210c342835a2cecf92bdd3b4e109b4d6e622f6934e13b2b123607bd61ce3d0f20454c9ab594f9284cffe18716619c52db57ce5f4ee2856cb96e1fa3748fe1fe65435bec297c5ab3ab58d570ec1064aea29931dd
222 lines
7.0 KiB
Python
222 lines
7.0 KiB
Python
"""
|
|
thermodynamic_entropy_mirror.py
|
|
|
|
Implementation of a thermodynamic/entropy mirror for Lucidia's mirror mechanics.
|
|
|
|
This module provides functions to split a probability distribution into reversible and irreversible components, update the distribution using a 'breath' operator that preserves total energy while allowing entropy to increase, apply perturbations (delta-kicks), and run a demonstration simulation of a simple thermodynamic system.
|
|
"""
|
|
|
|
import numpy as np
|
|
import os
|
|
import csv
|
|
import json
|
|
|
|
|
|
def normalize(dist):
|
|
total = np.sum(dist)
|
|
return dist / total if total != 0 else dist
|
|
|
|
|
|
def mirror_split_distribution(dist, kernel_sigma=1.0):
|
|
"""
|
|
Split a probability distribution into reversible and irreversible parts.
|
|
|
|
The irreversible part is obtained by diffusing the distribution with a Gaussian kernel.
|
|
The reversible part is the portion of the original distribution that remains after removing
|
|
the irreversible contribution.
|
|
|
|
Parameters:
|
|
- dist: array-like, the current probability distribution.
|
|
- kernel_sigma: standard deviation of the Gaussian kernel for diffusion.
|
|
|
|
Returns:
|
|
- reversible component of the distribution.
|
|
- irreversible component of the distribution (non-negative diffused part minus original).
|
|
"""
|
|
n = len(dist)
|
|
positions = np.arange(n)
|
|
# construct Gaussian kernel
|
|
kernel = np.exp(- (positions[:, None] - positions[None, :]) ** 2 / (2.0 * kernel_sigma ** 2))
|
|
kernel = kernel / kernel.sum(axis=1, keepdims=True)
|
|
diffused = dist @ kernel
|
|
irreversible = np.maximum(diffused - dist, 0)
|
|
reversible = dist - irreversible
|
|
return reversible, irreversible
|
|
|
|
|
|
def reversible_update(dist, shift=1):
|
|
"""
|
|
Apply a reversible update by shifting the distribution periodically.
|
|
|
|
Parameters:
|
|
- dist: array-like, the current probability distribution.
|
|
- shift: integer shift applied to the distribution (periodic boundary).
|
|
|
|
Returns:
|
|
- shifted distribution.
|
|
"""
|
|
return np.roll(dist, shift)
|
|
|
|
|
|
def irreversible_update(dist, kernel_sigma=1.0):
|
|
"""
|
|
Apply an irreversible update by diffusing the distribution with a Gaussian kernel.
|
|
|
|
Parameters:
|
|
- dist: array-like, the current probability distribution.
|
|
- kernel_sigma: standard deviation of the Gaussian kernel for diffusion.
|
|
|
|
Returns:
|
|
- diffused distribution.
|
|
"""
|
|
n = len(dist)
|
|
positions = np.arange(n)
|
|
kernel = np.exp(- (positions[:, None] - positions[None, :]) ** 2 / (2.0 * kernel_sigma ** 2))
|
|
kernel = kernel / kernel.sum(axis=1, keepdims=True)
|
|
return dist @ kernel
|
|
|
|
|
|
def breath_update(dist, shift=1, kernel_sigma=1.0):
|
|
"""
|
|
Combine reversible and irreversible updates to produce the next distribution.
|
|
|
|
Parameters:
|
|
- dist: array-like, the current probability distribution.
|
|
- shift: integer shift applied for the reversible update.
|
|
- kernel_sigma: standard deviation of the Gaussian kernel for the irreversible update.
|
|
|
|
Returns:
|
|
- normalized distribution after applying both updates.
|
|
"""
|
|
rev_part = reversible_update(dist, shift)
|
|
irr_part = irreversible_update(dist, kernel_sigma)
|
|
new_dist = 0.5 * (rev_part + irr_part)
|
|
return normalize(new_dist)
|
|
|
|
|
|
def delta_kick(dist, strength=0.1):
|
|
"""
|
|
Apply a perturbation (delta-kick) by adding mass to a random position.
|
|
|
|
Parameters:
|
|
- dist: array-like, the current probability distribution.
|
|
- strength: amount of probability mass to add.
|
|
|
|
Returns:
|
|
- normalized distribution after the kick.
|
|
"""
|
|
n = len(dist)
|
|
pos = np.random.randint(n)
|
|
dist_new = dist.copy()
|
|
dist_new[pos] += strength
|
|
return normalize(dist_new)
|
|
|
|
|
|
def energy_of_distribution(dist, energy_levels):
|
|
"""
|
|
Compute the expected energy of a distribution given energy levels.
|
|
|
|
Parameters:
|
|
- dist: array-like, the current probability distribution.
|
|
- energy_levels: array-like, energy associated with each state.
|
|
|
|
Returns:
|
|
- expected energy (float).
|
|
"""
|
|
return float(np.dot(dist, energy_levels))
|
|
|
|
|
|
def entropy_of_distribution(dist):
|
|
"""
|
|
Compute the Shannon entropy of a probability distribution.
|
|
|
|
Parameters:
|
|
- dist: array-like, the current probability distribution.
|
|
|
|
Returns:
|
|
- Shannon entropy (float, base e).
|
|
"""
|
|
eps = 1e-12
|
|
return float(-np.sum(dist * np.log(dist + eps)))
|
|
|
|
|
|
def run_thermo_demo(
|
|
n_states=50,
|
|
steps=50,
|
|
shift=1,
|
|
kernel_sigma=1.0,
|
|
kick_step=25,
|
|
kick_strength=0.5,
|
|
out_dir="out_thermo",
|
|
):
|
|
"""
|
|
Run a demonstration of the thermodynamic/entropy mirror.
|
|
|
|
This simulates a one-dimensional probability distribution evolving under alternating reversible
|
|
(advective) and irreversible (diffusive) updates. At a specified time step, a delta-kick
|
|
introduces a perturbation, and the simulation continues. Energy (expected value of a linear
|
|
energy spectrum) and Shannon entropy are recorded at each step.
|
|
|
|
Parameters:
|
|
- n_states: number of discrete states in the system.
|
|
- steps: total number of time steps.
|
|
- shift: integer shift for the reversible update.
|
|
- kernel_sigma: standard deviation for the Gaussian diffusion.
|
|
- kick_step: time step at which to apply the delta-kick (if negative, no kick is applied).
|
|
- kick_strength: amount of probability mass to add during the delta-kick.
|
|
- out_dir: directory to save output files (CSV and JSON).
|
|
|
|
Returns:
|
|
A dictionary with lists of energies, entropies, and distributions at each recorded step.
|
|
"""
|
|
np.random.seed(0)
|
|
# initialize distribution with a peak at the center
|
|
dist = np.zeros(n_states)
|
|
dist[n_states // 2] = 1.0
|
|
dist = normalize(dist)
|
|
|
|
# linear energy spectrum from 0 to 1
|
|
energy_levels = np.linspace(0, 1, n_states)
|
|
|
|
energies = []
|
|
entropies = []
|
|
distributions = []
|
|
|
|
for t in range(steps):
|
|
# record current state
|
|
energies.append(energy_of_distribution(dist, energy_levels))
|
|
entropies.append(entropy_of_distribution(dist))
|
|
distributions.append(dist.tolist())
|
|
|
|
# apply perturbation if scheduled
|
|
if kick_step >= 0 and t == kick_step:
|
|
dist = delta_kick(dist, kick_strength)
|
|
|
|
# update distribution
|
|
dist = breath_update(dist, shift, kernel_sigma)
|
|
|
|
# record final state
|
|
energies.append(energy_of_distribution(dist, energy_levels))
|
|
entropies.append(entropy_of_distribution(dist))
|
|
distributions.append(dist.tolist())
|
|
|
|
# ensure output directory exists
|
|
os.makedirs(out_dir, exist_ok=True)
|
|
|
|
# write energy and entropy data
|
|
with open(os.path.join(out_dir, "energy_entropy.csv"), "w", newline="") as f:
|
|
writer = csv.writer(f)
|
|
writer.writerow(["step", "energy", "entropy"])
|
|
for i, (e, s) in enumerate(zip(energies, entropies)):
|
|
writer.writerow([i, e, s])
|
|
|
|
# write distributions to JSON
|
|
with open(os.path.join(out_dir, "distributions.json"), "w") as f:
|
|
json.dump({"distributions": distributions}, f, indent=2)
|
|
|
|
return {
|
|
"energies": energies,
|
|
"entropies": entropies,
|
|
"distributions": distributions,
|
|
}
|