Skip to content

Instantly share code, notes, and snippets.

@pukpr
Created June 30, 2026 19:59
Show Gist options
  • Select an option

  • Save pukpr/98427ca6bf5b220a31fa919955f50422 to your computer and use it in GitHub Desktop.

Select an option

Save pukpr/98427ca6bf5b220a31fa919955f50422 to your computer and use it in GitHub Desktop.
Comparing manifold between models
#!/usr/bin/env python3
import sys
import json
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
def read_lpap(subdir):
"""
Read lpap list from lt.exe.p in the given subdirectory.
Returns a list of tuples (period_days, amplitude, phase_rad).
"""
json_path = Path(subdir) / "lt.exe.p"
if not json_path.is_file():
print(f"Warning: {json_path} not found, returning empty list.")
return []
with open(json_path, 'r') as f:
data = json.load(f)
triplets = data.get("lpap", [])
components = []
for t in triplets:
if len(t) >= 3:
try:
period = float(t[0]) # first field: period in days
amp = float(t[1]) # second field: amplitude
phase = float(t[2]) # third field: phase in radians
components.append((period, amp, phase))
except ValueError:
# skip if conversion fails
continue
return components
def compute_composite(components, t_days):
"""
Given a list of (period, amplitude, phase) and a time array (days),
return the sum of all sine waves: Σ A_i * sin(2π * t / P_i + φ_i)
"""
composite = np.zeros_like(t_days, dtype=float)
for period, amp, phase in components:
if period > 0:
composite += amp * np.sin(2.0 * np.pi * t_days / period + phase)
return composite
def main():
if len(sys.argv) != 3:
print("Usage: python plot_composite.py <subdir1> <subdir2>")
sys.exit(1)
subdir1, subdir2 = sys.argv[1], sys.argv[2]
# Read component data from both directories
comps1 = read_lpap(subdir1)
comps2 = read_lpap(subdir2)
if not comps1 and not comps2:
print("No data found in either file.")
return
# Time axis: from 1962‑01‑01 to 1972‑01‑01 (10 years)
start_year = 1962.0
duration_years = 10.0
days_per_year = 365.2422
num_points = 20000
t_days = np.linspace(0.0, duration_years * days_per_year, num_points)
t_years = start_year + t_days / days_per_year
# Compute composite signals
if comps1:
signal1 = compute_composite(comps1, t_days)
else:
signal1 = np.zeros_like(t_days)
if comps2:
signal2 = compute_composite(comps2, t_days)
else:
signal2 = np.zeros_like(t_days)
# Compute correlation coefficient (Pearson)
if np.any(signal1) and np.any(signal2):
corr = np.corrcoef(signal1, signal2)[0, 1]
else:
corr = float('nan')
# Plot
fig, ax = plt.subplots(figsize=(12, 6))
if comps1:
ax.plot(t_years, signal1, linestyle='-', color='blue', label=f'Composite from {subdir1}', linewidth=1.5)
if comps2:
ax.plot(t_years, signal2, linestyle='--', color='red', label=f'Composite from {subdir2}', linewidth=1.5)
ax.set_xlabel('Year')
ax.set_ylabel('Amplitude')
ax.set_title(f'Composite sine waves (10‑year span starting 1962) - Correlation: {corr:.4f}')
ax.legend()
ax.grid(True, linestyle='--', alpha=0.6)
plt.tight_layout()
plt.show()
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Plot column 1 vs column 4 from lte_results.csv files in two subdirs.
Each series is normalized to unit variance.
Pearson's r over the overlapping time segment is computed and shown in the title.
"""
import sys
import os
import glob
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
def main():
if len(sys.argv) < 3:
print("Usage: python lte_results_manifold.py <dir1> <dir2> [output_png]")
sys.exit(1)
output_png = sys.argv[3] if len(sys.argv) > 3 else None
COLUMN=3
TITLE='Manifold'
# COLUMN=2
# TITLE='Index'
# Collect one CSV per directory (take the first found if multiple)
csv_files = []
for d in sys.argv[1:3]:
found = glob.glob(os.path.join(d, "lte_results.csv"))
if found:
csv_files.append(found[0]) # pick first
if len(csv_files) != 2:
print("Need exactly one lte_results.csv in each of the two directories.")
sys.exit(1)
# Read both files
df1 = pd.read_csv(csv_files[0], header=None)
df2 = pd.read_csv(csv_files[1], header=None)
x1, y1 = df1[0].values, df1[COLUMN].values
x2, y2 = df2[0].values, df2[COLUMN].values
# Normalize each y to unit variance (one‑liner)
y1 = (y1 - y1.mean()) / y1.std()
y2 = (y2 - y2.mean()) / y2.std()
# ---- ONE‑LINER for Pearson's r over the shared time segment ----
# Defines common x as the union of points inside the overlap, interpolates both,
# computes correlation, and stores it in 'r'
x_min, x_max = max(x1.min(), x2.min()), min(x1.max(), x2.max())
common_x = np.unique(np.concatenate([x1[(x1 >= x_min) & (x1 <= x_max)], x2[(x2 >= x_min) & (x2 <= x_max)]]))
r = np.corrcoef(np.interp(common_x, x1, y1), np.interp(common_x, x2, y2))[0, 1] if len(common_x) > 1 else np.nan
# -----------------------------------------------------------------
# Plot both series
fig, ax = plt.subplots(figsize=(10, 6))
# Add vertical gray dotted grid lines every 3.81 years
spacing = 3.81
grid_years = np.arange(1880, 2021, spacing)
for g in grid_years:
ax.axvline(g, color='green', linestyle=':', lw=0.8)
ax.plot(x1, y1, label=sys.argv[1], color='red', linestyle='-', linewidth=1)
ax.plot(x2, y2, label=sys.argv[2], color='blue', linestyle='--', linewidth=1)
# Place r in the title (with 3 decimals)
title = f"{TITLE} — Pearson r = {r:.3f}" if not np.isnan(r) else "No overlapping time segment"
ax.set_title(title)
ax.set_xlabel("Year")
ax.set_ylabel(TITLE)
ax.legend()
# ax.grid(True, linestyle='--', alpha=0.7)
plt.tight_layout()
if output_png:
plt.savefig(output_png, dpi=100, bbox_inches='tight')
print(f"Saved: {output_png}")
else:
plt.show()
if __name__ == "__main__":
main()
@pukpr

pukpr commented Jun 30, 2026

Copy link
Copy Markdown
Author

Ratan MSL #88

image image image

NINO4

image image image

NAO

image image image

AMO

image image image

IODE

image image image

PDO

image image image

EMI

image image image

TNA

image image image

Baltic

image image image

NINO34

image image image

Honolulu MSL #155

image image image

Los Angeles MSL #245

image image image

Fremantle MSL #111

image image image

@pukpr

pukpr commented Jul 1, 2026

Copy link
Copy Markdown
Author

Ratan MSL #88

image image image

NINO4

image image image

NAO

image image image

AMO

image image image

IODE

image image image

PDO

image image image

EMI

image image image

TNA

image image image

Baltic

image image image

NINO34

image image image

Honolulu MSL #155

image image image

Los Angeles MSL #245

image image image

Fremantle MSL #111

image image image

@pukpr

pukpr commented Jul 2, 2026

Copy link
Copy Markdown
Author
 The analysis of the tidal and LTE parameters across the requested models reveals distinct patterns correlated with
  their geographic ocean basins and longitudes. Below is a detailed breakdown of the differences in tidal parameters
  (from lpap) and LTE parameters (from lt.exe.p) relative to their geolocations defined in ID.yml.

  1. LTE Parameter Analysis

  The LTE parameters show a clear "oceanic basin signature," particularly in the impulse response parameters (impA,
  impB) and the asymmetry (asym).

  ┌────────┬──────────┬──────────┬───────────┬────────┬────────┬────────┬────────┬───────┐
  │ Model  │ Basin    │ Latitude │ Longitude │ impA   │ impB   │ asym   │ ltep   │ offs  │
  ├────────┼──────────┼──────────┼───────────┼────────┼────────┼────────┼────────┼───────┤
  │ nino4  │ Pacific  │ 0.0      │ 175.0W    │ -0.335 │ -0.002 │ -0.875 │ -1.113 │ 0.037 │
  │ pdo    │ Pacific  │ 45.0N    │ 160.0W    │ -0.410 │ -0.002 │ -0.909 │ -1.179 │ 0.037 │
  │ emi    │ Pacific  │ 0.5N     │ 145.0W    │ -0.311 │ -0.002 │ -0.864 │ -1.157 │ 0.037 │
  │ nino34 │ Pacific  │ 0.0      │ 142.0W    │ -0.212 │ -0.033 │ -0.893 │ -1.156 │ 0.038 │
  │ 155    │ Pacific  │ 21.3N    │ 157.9W    │ -0.324 │ -0.002 │ -0.878 │ -1.209 │ 0.037 │
  │ 245    │ Pacific  │ 33.7N    │ 118.3W    │ -0.472 │ -0.000 │ -0.891 │ -1.167 │ 0.037 │
  │ amo    │ Atlantic │ 35.0N    │ 41.0W     │ -0.089 │ -1.691 │ -0.861 │ -1.107 │ 0.042 │
  │ tna    │ Atlantic │ 40.0N    │ 35.0W     │ -0.050 │ -1.397 │ -0.871 │ -1.204 │ 0.042 │
  │ nao    │ Atlantic │ 55.0N    │ 25.0W     │ 0.000  │ -1.060 │ -0.837 │ -1.120 │ 0.038 │
  │ iode   │ Indian   │ 5.0S     │ 100.0E    │ -0.179 │ -0.001 │ -0.931 │ -1.179 │ 0.040 │
  │ 111    │ Indian   │ 32.1S    │ 115.7E    │ -0.379 │ -0.002 │ -0.883 │ -1.115 │ 0.038 │
  │ 88     │ Baltic   │ 64.0N    │ 20.9E     │ -0.096 │ -0.110 │ -0.859 │ -1.093 │ 0.036 │
  │ baltic │ Baltic   │ 58.0N    │ 18.0E     │ 0.034  │ -0.273 │ -0.905 │ -1.114 │ 0.034 │
  └────────┴──────────┴──────────┴───────────┴────────┴────────┴────────┴────────┴───────┘

  Key Findings:
   * Impulse Response Basis Shift: There is a fundamental shift in the impulse response components between basins.
     Pacific and Indian Ocean models are primarily driven by the impA component (with impB near zero), while Atlantic
     models are dominated by impB (with impA near zero). This likely reflects the orthogonal orientation and different
     resonance characteristics of the Atlantic vs. Pacific basins.
   * Asymmetry (asym): All models show strong negative asymmetry (ranging from -0.83 to -0.93), which is a
     characteristic of the non-linear response in the LTE (Length of Day / Tidal) solver. The Indian Ocean (iode) and
     high-latitude Pacific (pdo) show the strongest asymmetry.
   * Global Parameters: delB is identical across all models (0.7917), suggesting it is a fixed physical constant in the
     model configuration. ltep values are also remarkably stable, generally clustering between -1.09 and -1.21,
     regardless of the geographic location.
   * Offset (offs): The Atlantic and Indian Ocean basins show slightly higher offsets (0.040 - 0.042) compared to the
     Pacific and Baltic models (0.034 - 0.037).

  2. Tidal Parameter Analysis (lpap)

  The tidal parameters for the ~27.32 day cycle (Component 0) show high consistency in amplitude but significant
  variation in phase, which correlates with longitude.

  ┌────────┬───────────┬─────────────┬───────────────────────────┐
  │ Model  │ Longitude │ Phase (rad) │ Normalized Phase (mod 2π) │
  ├────────┼───────────┼─────────────┼───────────────────────────┤
  │ iode   │ 100.0E    │ 6.475       │ 0.192                     │
  │ 111    │ 115.7E    │ 12.535      │ 6.252                     │
  │ nino4  │ 185.0E    │ 6.288       │ 6.288                     │
  │ pdo    │ 200.0E    │ 6.389       │ 6.389                     │
  │ 155    │ 202.1E    │ 6.448       │ 6.448                     │
  │ emi    │ 215.0E    │ 6.443       │ 6.443                     │
  │ nino34 │ 218.0E    │ 12.262      │ 5.979                     │
  │ 245    │ 241.7E    │ 6.917       │ 0.634                     │
  │ amo    │ 319.0E    │ 10.554      │ 4.271                     │
  │ tna    │ 325.0E    │ 10.397      │ 4.114                     │
  │ nao    │ 335.0E    │ 11.208      │ 4.925                     │
  │ 88     │ 20.9E     │ -1.277      │ 5.006                     │
  │ baltic │ 18.0E     │ 10.776      │ 4.493                     │
  └────────┴───────────┴─────────────┴───────────────────────────┘

  Key Findings:
   * Phase Migration: The phases of the lunar tidal components migrate systematically with longitude. The phase shift
     between the Western Pacific (nino4) and the American West Coast (245) reflects the physical propagation of the
     tidal potential.
   * Amplitudes: For most models, the tidal amplitudes are kept at a "reference" level (e.g., 0.1047 for Component 0),
     with the exception of the baltic composite model, which uses a significantly higher amplitude (0.1700), likely due
     to the enclosed and resonant nature of the Baltic Sea affecting the composite average.
   * Consistency: The periods are identical across all models, confirming they are locked to the same astronomical
     forcing frequencies. The "very good fits" mentioned are achieved by fine-tuning the phases to the local geographic
     response while maintaining global LTE physics.

The tidal factor amplitudes appear to cluster according to Atlantic and Pacific location, but that may not be as strict, as these were all seeded from a fit close by.
image

@pukpr

pukpr commented Jul 2, 2026

Copy link
Copy Markdown
Author

The latent hidden manifold is not that different between the ocean basins (the vertical lines are 3..8 years apart corresponding to the Mf tidal factor)

NAO vs NINO4
image

but does align much more within the basin

PDO vs NINO4
image

NAO vs 88 Ratan MSL
image

The ongoing premise is that these are all very slight or subtle amplitude and phase variations that give rise to variations in the climate and MSL oscillations. This is in keeping with conventional tidal analysis where the rank ordering is largely the same around the world, but where phase plays a significant role.

The ordering here corresponds to that of LOD, but the ~7-day harmonics of the Mf and Mf' are amplified, so the calibration to LOD is only a starting point here. The non-linearity of the LTE modulation is possibly missing some of this amplification. It's also possible that this is on a continuum with the much stronger diurnal tides, i.e. 7 days is clearly closer to 1-day than 13.66 days, so that stronger acceleration of shorter tidal factor periods play a role.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment