Merge branch 'fdm': output timestampato, heatmap animata, menu semplificato
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
Write all git commit messages in Italian.
|
||||
|
||||
## Scope of Work
|
||||
|
||||
**Modifica solo il modulo `fdm/`.** Ignora qualsiasi richiesta riguardante PINN (`model.py`, `engine.py`, `visualizer.py`, `app.py` radice). Se una richiesta coinvolge file PINN, avvisa l'utente e non apportare modifiche.
|
||||
|
||||
## Project Overview
|
||||
|
||||
**Heat Equation PINN** — A Physics-Informed Neural Network that solves the 1D time-varying heat equation with an internal point heat source:
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
RESULTS_DIR="$(dirname "$0")/results/fdm"
|
||||
|
||||
if [[ ! -d "$RESULTS_DIR" ]]; then
|
||||
echo "Nessuna cartella results/fdm trovata."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mapfile -t RUNS < <(find "$RESULTS_DIR" -mindepth 1 -maxdepth 1 -type d | sort)
|
||||
|
||||
if [[ ${#RUNS[@]} -eq 0 ]]; then
|
||||
echo "Nessun risultato da cancellare."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Risultati disponibili:"
|
||||
for i in "${!RUNS[@]}"; do
|
||||
printf " %2d. %s\n" "$((i+1))" "$(basename "${RUNS[$i]}")"
|
||||
done
|
||||
echo ""
|
||||
echo "a. Cancella tutti"
|
||||
echo "s. Selezione manuale"
|
||||
echo "0. Annulla"
|
||||
echo ""
|
||||
read -rp "Scelta: " MODE
|
||||
|
||||
case "$MODE" in
|
||||
a|A)
|
||||
read -rp "Confermi la cancellazione di ${#RUNS[@]} cartelle? [s/N] " CONFIRM
|
||||
if [[ "${CONFIRM,,}" == "s" ]]; then
|
||||
rm -rf "${RUNS[@]}"
|
||||
echo "Cancellati ${#RUNS[@]} risultati."
|
||||
else
|
||||
echo "Annullato."
|
||||
fi
|
||||
;;
|
||||
s|S)
|
||||
read -rp "Numeri da cancellare (es. 1 3 5): " -a CHOICES
|
||||
TO_DELETE=()
|
||||
for N in "${CHOICES[@]}"; do
|
||||
if [[ "$N" =~ ^[0-9]+$ ]] && (( N >= 1 && N <= ${#RUNS[@]} )); then
|
||||
TO_DELETE+=("${RUNS[$((N-1))]}")
|
||||
else
|
||||
echo "Indice non valido: $N (ignorato)"
|
||||
fi
|
||||
done
|
||||
if [[ ${#TO_DELETE[@]} -eq 0 ]]; then
|
||||
echo "Nessuna selezione valida."
|
||||
exit 0
|
||||
fi
|
||||
echo "Verranno cancellati:"
|
||||
for D in "${TO_DELETE[@]}"; do
|
||||
echo " - $(basename "$D")"
|
||||
done
|
||||
read -rp "Confermi? [s/N] " CONFIRM
|
||||
if [[ "${CONFIRM,,}" == "s" ]]; then
|
||||
rm -rf "${TO_DELETE[@]}"
|
||||
echo "Cancellati ${#TO_DELETE[@]} risultati."
|
||||
else
|
||||
echo "Annullato."
|
||||
fi
|
||||
;;
|
||||
0|"")
|
||||
echo "Annullato."
|
||||
;;
|
||||
*)
|
||||
echo "Scelta non valida."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
+4
-7
@@ -24,26 +24,23 @@ def main_menu():
|
||||
print("\n" + "-" * 30)
|
||||
print(" MAIN MENU")
|
||||
print("-" * 30)
|
||||
print("1. Risolvi e salva risultati")
|
||||
print("2. Heatmap T(x,t)")
|
||||
print("3. Animazione T(x) nel tempo")
|
||||
print("4. Grafico T(t) in punti fissi")
|
||||
print("1. Risolvi")
|
||||
print("2. Visualizza")
|
||||
print("0. Esci")
|
||||
print("-" * 30)
|
||||
|
||||
choice = input("Select an option (0-4): ").strip()
|
||||
choice = input("Select an option (0-2): ").strip()
|
||||
|
||||
if choice == "1":
|
||||
T, x_vals, t_vals = solve()
|
||||
print(f"Soluzione completata. Shape T: {T.shape}")
|
||||
print(f"T range: [{T.min():.2f}, {T.max():.2f}] °C")
|
||||
|
||||
elif choice in ("2", "3", "4"):
|
||||
elif choice == "2":
|
||||
if T is None:
|
||||
print("Eseguire prima l'opzione 1.")
|
||||
else:
|
||||
visualize_fdm(T, x_vals, t_vals)
|
||||
print("Grafici salvati in animations/fdm/")
|
||||
|
||||
elif choice == "0":
|
||||
print("Uscita.")
|
||||
|
||||
+102
-39
@@ -1,5 +1,6 @@
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime
|
||||
import numpy as np
|
||||
import plotly.graph_objects as go
|
||||
|
||||
@@ -7,16 +8,6 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import config
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
FDM_ANIM_DIR = os.path.join(BASE_DIR, 'animations', 'fdm')
|
||||
|
||||
|
||||
def _next_path(base_dir, prefix, ext):
|
||||
i = 1
|
||||
while True:
|
||||
path = os.path.join(base_dir, f'{prefix}_{i:03d}{ext}')
|
||||
if not os.path.exists(path):
|
||||
return path
|
||||
i += 1
|
||||
|
||||
|
||||
def visualize_fdm(T_matrix, x_vals, t_vals):
|
||||
@@ -31,55 +22,127 @@ def visualize_fdm(T_matrix, x_vals, t_vals):
|
||||
t_vals : np.ndarray, shape (NT,)
|
||||
Time values.
|
||||
"""
|
||||
os.makedirs(FDM_ANIM_DIR, exist_ok=True)
|
||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
out_dir = os.path.join(BASE_DIR, 'results', 'fdm', timestamp)
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 1. Heatmap
|
||||
# 1. heatmap.html: plot statico T(x,t) + striscia 1D animata
|
||||
# Due figure Plotly indipendenti nello stesso file HTML per
|
||||
# evitare conflitti tra animazioni e assi multipli.
|
||||
# ------------------------------------------------------------------
|
||||
zmax = float(np.max(np.abs(T_matrix - config.T0)))
|
||||
# Use symmetric range centred on T0 if there is any variation,
|
||||
# otherwise fall back to the raw range.
|
||||
z_data = T_matrix.T # shape (NT, NX) — rows=time, cols=space
|
||||
zmin_val = float(np.min(T_matrix))
|
||||
zmax_val = float(np.max(T_matrix))
|
||||
|
||||
if zmax > 0:
|
||||
z_center = float(np.mean(T_matrix))
|
||||
z_abs = float(np.max(np.abs(T_matrix - z_center)))
|
||||
zmin_sym = z_center - z_abs
|
||||
zmax_sym = z_center + z_abs
|
||||
else:
|
||||
zmin_sym = float(np.min(T_matrix))
|
||||
zmax_sym = float(np.max(T_matrix))
|
||||
# Subsample frames (shared with animation below)
|
||||
n_frames = len(t_vals)
|
||||
max_frames = 200
|
||||
step = max(1, n_frames // max_frames)
|
||||
frame_indices = list(range(0, n_frames, step))
|
||||
|
||||
fig_heatmap = go.Figure(go.Heatmap(
|
||||
# --- Figura A: heatmap 2D statica (identica all'originale) ---
|
||||
z_data = T_matrix.T # (NT, NX)
|
||||
z_center = float(np.mean(T_matrix))
|
||||
z_abs = float(np.max(np.abs(T_matrix - z_center))) or 1.0
|
||||
fig_static = go.Figure(go.Heatmap(
|
||||
z=z_data,
|
||||
x=x_vals,
|
||||
y=t_vals,
|
||||
colorscale='RdBu_r',
|
||||
zmin=zmin_sym,
|
||||
zmax=zmax_sym,
|
||||
zmin=z_center - z_abs,
|
||||
zmax=z_center + z_abs,
|
||||
colorbar=dict(title='T [°C]'),
|
||||
))
|
||||
fig_heatmap.update_layout(
|
||||
fig_static.update_layout(
|
||||
title='FDM — Heat Equation T(x,t)',
|
||||
xaxis_title='x [m]',
|
||||
yaxis_title='t [s]',
|
||||
height=500,
|
||||
height=480,
|
||||
margin=dict(t=50, b=50, l=70, r=90),
|
||||
)
|
||||
|
||||
heatmap_path = _next_path(FDM_ANIM_DIR, 'heatmap', '.html')
|
||||
fig_heatmap.write_html(heatmap_path)
|
||||
# --- Figura B: striscia 1D animata ---
|
||||
strip_frames = [
|
||||
go.Frame(
|
||||
data=[go.Heatmap(
|
||||
z=T_matrix[:, idx].reshape(1, -1),
|
||||
x=x_vals,
|
||||
y=[0],
|
||||
colorscale='Jet',
|
||||
zmin=zmin_val,
|
||||
zmax=zmax_val,
|
||||
showscale=True,
|
||||
colorbar=dict(title='T [°C]', thickness=18),
|
||||
)],
|
||||
name=str(idx),
|
||||
layout=go.Layout(title_text=f't = {t_vals[idx]:.3f} s'),
|
||||
)
|
||||
for idx in frame_indices
|
||||
]
|
||||
|
||||
fig_strip = go.Figure(
|
||||
data=[go.Heatmap(
|
||||
z=T_matrix[:, frame_indices[0]].reshape(1, -1),
|
||||
x=x_vals,
|
||||
y=[0],
|
||||
colorscale='Jet',
|
||||
zmin=zmin_val,
|
||||
zmax=zmax_val,
|
||||
showscale=True,
|
||||
colorbar=dict(title='T [°C]', thickness=18),
|
||||
)],
|
||||
frames=strip_frames,
|
||||
layout=go.Layout(
|
||||
title=f't = {t_vals[frame_indices[0]]:.3f} s',
|
||||
xaxis=dict(title='x [m]'),
|
||||
yaxis=dict(showticklabels=False, showgrid=False,
|
||||
zeroline=False, fixedrange=True),
|
||||
height=300,
|
||||
margin=dict(t=80, b=130, l=70, r=110),
|
||||
updatemenus=[dict(
|
||||
type='buttons',
|
||||
showactive=False,
|
||||
y=1.22, x=0.5, xanchor='center',
|
||||
buttons=[
|
||||
dict(label='▶ Play', method='animate',
|
||||
args=[None, dict(frame=dict(duration=40, redraw=True),
|
||||
fromcurrent=True, mode='immediate')]),
|
||||
dict(label='⏸ Pause', method='animate',
|
||||
args=[[None], dict(frame=dict(duration=0, redraw=False),
|
||||
mode='immediate')]),
|
||||
],
|
||||
)],
|
||||
sliders=[dict(
|
||||
steps=[
|
||||
dict(method='animate',
|
||||
args=[[str(idx)], dict(mode='immediate',
|
||||
frame=dict(duration=0, redraw=True))],
|
||||
label=f'{t_vals[idx]:.2f}')
|
||||
for idx in frame_indices
|
||||
],
|
||||
transition=dict(duration=0),
|
||||
x=0.05, y=0, len=0.9,
|
||||
currentvalue=dict(prefix='t = ', font=dict(size=14)),
|
||||
)],
|
||||
),
|
||||
)
|
||||
|
||||
# Scrivi entrambe le figure in un unico file HTML
|
||||
html_static = fig_static.to_html(full_html=False, include_plotlyjs='cdn')
|
||||
html_strip = fig_strip.to_html(full_html=False, include_plotlyjs=False)
|
||||
heatmap_path = os.path.join(out_dir, 'heatmap.html')
|
||||
with open(heatmap_path, 'w', encoding='utf-8') as _f:
|
||||
_f.write('<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>\n')
|
||||
_f.write(html_static)
|
||||
_f.write('\n')
|
||||
_f.write(html_strip)
|
||||
_f.write('\n</body></html>')
|
||||
print(f"Heatmap saved → {heatmap_path}")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 2. Animated profile T(x) evolving in time
|
||||
# ------------------------------------------------------------------
|
||||
L = config.L
|
||||
n_frames = len(t_vals)
|
||||
|
||||
# Subsample frames for a manageable animation (max ~200 frames)
|
||||
max_frames = 200
|
||||
step = max(1, n_frames // max_frames)
|
||||
frame_indices = list(range(0, n_frames, step))
|
||||
|
||||
y_min = float(np.min(T_matrix)) - 1.0
|
||||
y_max = float(np.max(T_matrix)) + 1.0
|
||||
@@ -168,7 +231,7 @@ def visualize_fdm(T_matrix, x_vals, t_vals):
|
||||
frames=frames,
|
||||
)
|
||||
|
||||
anim_path = _next_path(FDM_ANIM_DIR, 'animation', '.html')
|
||||
anim_path = os.path.join(out_dir, 'animation.html')
|
||||
fig_anim.write_html(anim_path)
|
||||
print(f"Animation saved → {anim_path}")
|
||||
|
||||
@@ -222,6 +285,6 @@ def visualize_fdm(T_matrix, x_vals, t_vals):
|
||||
height=480,
|
||||
)
|
||||
|
||||
ts_path = _next_path(FDM_ANIM_DIR, 'time_series', '.html')
|
||||
ts_path = os.path.join(out_dir, 'time_series.html')
|
||||
fig_ts.write_html(ts_path)
|
||||
print(f"Time-series saved → {ts_path}")
|
||||
|
||||
Reference in New Issue
Block a user