PHASE V: PARAMETER MAPPING PLAYGROUND
"""
================================================================================
PHASE V: PARAMETER MAPPING PLAYGROUND — FINAL RUN-READY
================================================================================
Purpose: Explore the consequences of the constitutive model under selectable
mapping assumptions. This notebook is an exploratory parameter-analysis
environment. It is not, by itself, a validation of the constitutive
model or of any physical interpretation.
VERSION INFORMATION
===================
SCRIPT_VERSION = "2.2 (Run-Ready)"
CODATA_VERSION = "2018"
DATE = "2026-07-16"
DEPENDENCIES
============
NumPy >= 1.20.0
Matplotlib >= 3.3.0
ipywidgets >= 7.6.0
LIVE OUTPUT FEATURES
====================
- Real-time console printing for every slider change
- JSON and CSV data snapshots via download buttons
- Full parameter logging to chat window
================================================================================
"""
import numpy as np
import matplotlib.pyplot as plt
import warnings
import sys
import json
import csv
import datetime
from IPython.display import display, clear_output
# Catch missing dependencies gracefully
try:
from google.colab import files # Auto-download capability
except ImportError:
print("Warning: google.colab module not found. Auto-download features will be disabled.")
try:
import ipywidgets as widgets
except ImportError:
raise ImportError("ipywidgets is missing. Please run '!pip install ipywidgets' in a Colab cell first.")
warnings.filterwarnings('ignore')
# ============================================================================
# VERSION AND METADATA
# ============================================================================
SCRIPT_VERSION = "2.2"
CODATA_VERSION = "2018"
DATE = "2026-07-16"
print("="*80)
print(" PHASE V: FRCMΠD PARAMETER MAPPING PLAYGROUND")
print("="*80)
print(f"Version: {SCRIPT_VERSION} | CODATA: {CODATA_VERSION} | Date: {DATE}")
print(f"Python {sys.version.split()[0]} | NumPy {np.__version__} | Matplotlib {plt.matplotlib.__version__}")
print("="*80)
print("\n LIVE OUTPUT: Every parameter change will print data to this console.")
print(" DATA SNAPSHOTS: Use the JSON and CSV buttons to save data.")
print("="*80 + "\n")
# ============================================================================
# CONSTANTS
# ============================================================================
MIN_RADIUS = 1e-40
R_MIN_FRACTION = 0.1
R_MAX_FRACTION = 10.0
DEFAULT_N_POINTS = 500
DEFAULT_ALPHA = 0.5
DEFAULT_KAPPA = 0.1
DEFAULT_MASS = 3.0
# ============================================================================
# PHYSICAL CONSTANTS (CODATA 2018)
# ============================================================================
G = 6.67430e-11
c = 2.99792458e8
M_Planck = 2.176434e-8
L_Planck = 1.616255e-35
M_solar = 1.98847e30
R_solar = 6.957e8
# NOTE: M_universe and R_universe are order-of-magnitude approximations.
M_universe = 1.5e53
R_universe = 4.4e26
# ============================================================================
# FRCMΠD CONSTANTS (from Benchmark 3)
# ============================================================================
r_c = 1.2
I_1_peak = 98.76
mu = 1.0
lam = 1.0
# ============================================================================
# DATA STORAGE
# ============================================================================
current_data = {}
# ============================================================================
# CORE PHYSICS FUNCTIONS
# ============================================================================
def schwarzschild_radius(M):
"""Compute Schwarzschild radius with correct factor of 2."""
return 2.0 * G * M / (c**2)
def saturation_I1(kappa):
"""Compute saturation I1 from the condition 6*kappa*I1^2 = mu + 2*lam."""
return np.sqrt((mu + 2*lam) / (6 * kappa))
def rho_powerlaw_r2_profile(r_vals, M, R_sat):
"""
Normalized r^-2 profile: rho(r) = rho0 * (r / R_sat)^-2
Mass conservation: M = 4*pi*rho0*R_sat^3
"""
r_safe = np.maximum(r_vals, 1e-40)
rho0 = M / (4.0 * np.pi * R_sat**3)
rho_vals = rho0 * (r_safe / R_sat)**(-2)
rho_vals[r_vals > R_sat] = 0.0
rho_vals = np.minimum(rho_vals, 1e100)
return rho_vals, rho0
def compute_configuration(scale_type, kappa_input, mass_input, density_model='uniform', alpha=DEFAULT_ALPHA):
"""
Computes the full configuration state for the given parameters.
"""
global current_data
if mass_input <= 0:
raise ValueError("Mass must be positive")
if kappa_input <= 0:
raise ValueError("kappa must be positive")
# Scale selection
if scale_type == 'Planck':
M_ref, L_ref, mass_label, scale_name = M_Planck, L_Planck, "Planck Masses", "Planck Scale"
elif scale_type == 'Astrophysical':
M_ref, L_ref, mass_label, scale_name = M_solar, R_solar, "Solar Masses", "Astrophysical Scale"
elif scale_type == 'Cosmological':
M_ref, L_ref, mass_label, scale_name = M_universe, R_universe, "Universe Masses", "Cosmological Scale"
else:
raise ValueError(f"Unknown scale_type: {scale_type}")
# Physical quantities
M_physical = mass_input * M_ref
R_schwarzschild = schwarzschild_radius(M_physical)
if R_schwarzschild <= 0:
raise ValueError("Schwarzschild radius is non-positive; check mass input.")
# Saturation and mapping
I_1_sat_current = saturation_I1(kappa_input)
constitutive_scale_factor = (I_1_peak / I_1_sat_current)**alpha
R_sat = r_c * R_schwarzschild * constitutive_scale_factor
R_sat = max(R_sat, MIN_RADIUS)
# Density models
if density_model == 'uniform':
Volume = (4/3) * np.pi * (R_sat**3)
rho_max = M_physical / Volume
density_description = "Uniform density within R_sat"
elif density_model == 'power_law_r2':
rho0 = M_physical / (4.0 * np.pi * R_sat**3)
rho_max = rho0
density_description = "r^-2 power-law profile (mass-conserving)"
elif density_model == 'schwarzschild':
Volume_s = (4/3) * np.pi * (R_schwarzschild**3)
rho_max = M_physical / Volume_s
density_description = "Schwarzschild baseline (R_s reference)"
else:
raise ValueError(f"Unknown density_model: {density_model}")
# Validation
assert rho_max > 0, "rho_max must be positive"
assert R_sat > 0, "R_sat must be positive"
assert np.isfinite(R_sat), "R_sat must be finite"
assert np.isfinite(rho_max), "rho_max must be finite"
current_data = {
'timestamp': datetime.datetime.now().isoformat(),
'script_version': SCRIPT_VERSION,
'scale_type': scale_type,
'scale_name': scale_name,
'mass_label': mass_label,
'mass_input': float(mass_input),
'M_physical': float(M_physical),
'M_ref': float(M_ref),
'L_ref': float(L_ref),
'kappa': float(kappa_input),
'alpha': float(alpha),
'density_model': density_model,
'density_description': density_description,
'R_schwarzschild': float(R_schwarzschild),
'R_sat': float(R_sat),
'rho_max': float(rho_max),
'I_1_sat': float(I_1_sat_current),
'constitutive_scale_factor': float(constitutive_scale_factor),
'r_c': float(r_c),
'I_1_peak': float(I_1_peak),
'mu': float(mu),
'lam': float(lam)
}
return current_data
def generate_density_profile(params, n_points=DEFAULT_N_POINTS):
"""Generates density profiles for plotting."""
R_sat = params['R_sat']
M = params['M_physical']
rho_max = params['rho_max']
density_model = params['density_model']
r_min = max(R_sat * R_MIN_FRACTION, MIN_RADIUS)
r_max = max(R_sat * R_MAX_FRACTION, r_min * 10.0)
try:
r_vals = np.logspace(np.log10(r_min), np.log10(r_max), n_points)
except:
r_vals = np.linspace(r_min, r_max, n_points)
rho_classical = M / ((4/3) * np.pi * (r_vals**3))
if density_model == 'power_law_r2':
rho_frcmpd, rho0 = rho_powerlaw_r2_profile(r_vals, M, R_sat)
rho_max_actual = rho0
else:
rho_frcmpd = np.minimum(rho_classical, rho_max)
rho_max_actual = rho_max
return r_vals, rho_classical, rho_frcmpd, rho_max_actual
# ============================================================================
# LIVE OUTPUT FUNCTIONS
# ============================================================================
def print_live_data(params):
"""Prints live data to console for copying."""
print("\n" + "="*60)
print(" LIVE DATA SNAPSHOT")
print("="*60)
print(f"Timestamp: {params['timestamp']}")
print(f"Scale: {params['scale_name']} ({params['scale_type']})")
print(f"Mass: {params['mass_input']:.2f} {params['mass_label']}")
print(f"kappa Parameter: {params['kappa']:.4f}")
print(f"Alpha (mapping): {params['alpha']:.4f}")
print(f"Density Model: {params['density_model']}")
print("-"*60)
print(f"M_physical: {params['M_physical']:.4e} kg")
print(f"R_schwarzschild: {params['R_schwarzschild']:.4e} m")
print(f"R_sat: {params['R_sat']:.4e} m")
print(f"rho_max: {params['rho_max']:.4e} kg/m^3")
print(f"I_1_sat: {params['I_1_sat']:.4f}")
print(f"Scale Factor: {params['constitutive_scale_factor']:.4f}")
print(f"R_sat/R_s ratio: {params['R_sat']/params['R_schwarzschild']:.4f}")
print("="*60)
print(" Copy this data directly from the console.\n")
def download_json(params):
"""Downloads JSON data snapshot to browser."""
try:
json_str = json.dumps(params, indent=4)
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"FRCMpD_PhaseV_Snapshot_{timestamp}.json"
with open(filename, 'w') as f:
f.write(json_str)
if 'google.colab' in sys.modules:
files.download(filename)
print(f" JSON downloaded: {filename}")
else:
print(f" JSON saved locally: {filename}")
return True
except Exception as e:
print(f" JSON download failed: {e}")
return False
def download_csv(params):
"""Downloads CSV data snapshot to browser."""
try:
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"FRCMpD_PhaseV_Snapshot_{timestamp}.csv"
flat_params = {}
for key, value in params.items():
if isinstance(value, (int, float, str, bool)):
flat_params[key] = value
else:
flat_params[key] = str(value)
with open(filename, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['Parameter', 'Value'])
for key, value in flat_params.items():
writer.writerow([key, value])
if 'google.colab' in sys.modules:
files.download(filename)
print(f" CSV downloaded: {filename}")
else:
print(f" CSV saved locally: {filename}")
return True
except Exception as e:
print(f" CSV download failed: {e}")
return False
# ============================================================================
# PLOTTING FUNCTIONS
# ============================================================================
def create_plot(params, r_vals, rho_classical, rho_frcmpd, rho_max_actual, show_legend=True):
"""Creates the density vs radius plot."""
plt.close("all")
fig, ax = plt.subplots(figsize=(12, 8))
ax.loglog(r_vals, rho_classical, 'r--', linewidth=2, label='Classical Singularity (Newtonian)')
ax.loglog(r_vals, rho_frcmpd, 'b-', linewidth=3, label='FRCMΠD Saturation (Arrested Collapse)')
R_sat = params['R_sat']
ax.axvline(x=R_sat, color='g', linestyle=':', linewidth=2, label=f'Saturation Radius\nR_sat = {R_sat:.3e} m')
ax.axhline(y=rho_max_actual, color='orange', linestyle=':', linewidth=2, label=f'Max Density\nrho_max = {rho_max_actual:.3e} kg/m^3')
R_schwarzschild = params['R_schwarzschild']
if R_schwarzschild > 0:
ax.axvline(x=R_schwarzschild, color='purple', linestyle='--', alpha=0.5, label=f'Schwarzschild Radius\nR_s = {R_schwarzschild:.3e} m')
ax.set_title(f"Phase V: Core Density vs. Radius\n"
f"{params['scale_name']} | {params['mass_label']} = {params['mass_input']:.2f} | "
f"kappa = {params['kappa']:.3f} | alpha = {params['alpha']:.2f}\n"
f"Model: {params['density_model']} ({params['density_description']})",
fontsize=14)
ax.set_xlabel("Physical Radius (meters)", fontsize=12)
ax.set_ylabel("Core Density (kg/m^3)", fontsize=12)
ax.grid(True, which="both", alpha=0.3)
if show_legend:
ax.legend(loc='best', fontsize=10)
plt.tight_layout()
plt.show()
def update_plot(scale_type, kappa_input, mass_input, density_model, alpha, show_legend=True):
"""Updates the interactive plot with live output."""
try:
params = compute_configuration(scale_type, kappa_input, mass_input, density_model, alpha)
r_vals, rho_classical, rho_frcmpd, rho_max_actual = generate_density_profile(params)
create_plot(params, r_vals, rho_classical, rho_frcmpd, rho_max_actual, show_legend)
print_live_data(params)
global current_data
current_data = params
except Exception as e:
print(f" ERROR: {e}")
print("Please adjust parameters and try again.")
# ============================================================================
# UI FUNCTIONS
# ============================================================================
def create_playground():
"""Creates the interactive Phase V playground."""
print("\n" + "="*80)
print(" PHASE V: FRCMΠD PARAMETER MAPPING PLAYGROUND")
print("="*80)
print(f"Version: {SCRIPT_VERSION} | CODATA: {CODATA_VERSION} | Date: {DATE}")
print("\n LIVE OUTPUT: Every parameter change prints data to the console.")
print(" Use the 'Download JSON' or 'Download CSV' buttons to save data.")
print("\nControls:")
print(" Scale Regime: Planck / Astrophysical / Cosmological")
print(" kappa Parameter: Nonlinear coefficient (dimensionless, 0.01-1.0)")
print(" Mass Input: Mass in multiples of the reference mass")
print(" Density Model: uniform / power_law_r2 / schwarzschild")
print(" Alpha: Mapping exponent (constitutive assumption)")
print("="*80 + "\n")
scale_dropdown = widgets.Dropdown(
options=['Planck', 'Astrophysical', 'Cosmological'],
value='Astrophysical',
description='Scale:',
style={'description_width': 'initial'}
)
kappa_slider = widgets.FloatSlider(
value=DEFAULT_KAPPA,
min=0.01,
max=1.0,
step=0.01,
description='kappa:',
style={'description_width': 'initial'},
continuous_update=False
)
mass_slider = widgets.FloatSlider(
value=DEFAULT_MASS,
min=0.1,
max=50.0,
step=0.1,
description='Mass (x ref):',
style={'description_width': 'initial'},
continuous_update=False
)
density_dropdown = widgets.Dropdown(
options=['uniform', 'power_law_r2', 'schwarzschild'],
value='uniform',
description='Density Model:',
style={'description_width': 'initial'}
)
alpha_slider = widgets.FloatSlider(
value=DEFAULT_ALPHA,
min=0.1,
max=1.0,
step=0.05,
description='Alpha:',
style={'description_width': 'initial'},
continuous_update=False
)
legend_checkbox = widgets.Checkbox(
value=True,
description='Show Legend',
style={'description_width': 'initial'}
)
download_json_button = widgets.Button(
description='Download JSON',
button_style='primary',
layout=widgets.Layout(width='auto')
)
download_csv_button = widgets.Button(
description='Download CSV',
button_style='primary',
layout=widgets.Layout(width='auto')
)
def on_json_click(b):
if current_data:
download_json(current_data)
else:
print(" No data to download. Adjust a slider first.")
def on_csv_click(b):
if current_data:
download_csv(current_data)
else:
print(" No data to download. Adjust a slider first.")
download_json_button.on_click(on_json_click)
download_csv_button.on_click(on_csv_click)
controls_row1 = widgets.HBox([scale_dropdown, kappa_slider])
controls_row2 = widgets.HBox([mass_slider, density_dropdown])
controls_row3 = widgets.HBox([alpha_slider, legend_checkbox])
controls_row4 = widgets.HBox([download_json_button, download_csv_button])
ui = widgets.VBox([
controls_row1,
controls_row2,
controls_row3,
controls_row4
])
out = widgets.interactive_output(
update_plot,
{
'scale_type': scale_dropdown,
'kappa_input': kappa_slider,
'mass_input': mass_slider,
'density_model': density_dropdown,
'alpha': alpha_slider,
'show_legend': legend_checkbox
}
)
display(ui, out)
# ============================================================================
# MAIN EXECUTION
# ============================================================================
if __name__ == "__main__":
create_playground()