228 lines
7.2 KiB
Python
228 lines
7.2 KiB
Python
import sys
|
|
import os
|
|
import numpy as np
|
|
import plotly.graph_objects as go
|
|
|
|
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):
|
|
"""Produce three HTML visualisations for the FDM solver output.
|
|
|
|
Parameters
|
|
----------
|
|
T_matrix : np.ndarray, shape (NX, NT)
|
|
Temperature field: T_matrix[i, n] = T(x_i, t_n).
|
|
x_vals : np.ndarray, shape (NX,)
|
|
Spatial node positions.
|
|
t_vals : np.ndarray, shape (NT,)
|
|
Time values.
|
|
"""
|
|
os.makedirs(FDM_ANIM_DIR, exist_ok=True)
|
|
|
|
# ------------------------------------------------------------------
|
|
# 1. Heatmap
|
|
# ------------------------------------------------------------------
|
|
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
|
|
|
|
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))
|
|
|
|
fig_heatmap = go.Figure(go.Heatmap(
|
|
z=z_data,
|
|
x=x_vals,
|
|
y=t_vals,
|
|
colorscale='RdBu_r',
|
|
zmin=zmin_sym,
|
|
zmax=zmax_sym,
|
|
colorbar=dict(title='T [°C]'),
|
|
))
|
|
fig_heatmap.update_layout(
|
|
title='FDM — Heat Equation T(x,t)',
|
|
xaxis_title='x [m]',
|
|
yaxis_title='t [s]',
|
|
height=500,
|
|
)
|
|
|
|
heatmap_path = _next_path(FDM_ANIM_DIR, 'heatmap', '.html')
|
|
fig_heatmap.write_html(heatmap_path)
|
|
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
|
|
|
|
vline_shapes = [
|
|
dict(type='line', x0=0, x1=0, y0=y_min, y1=y_max,
|
|
line=dict(color='grey', dash='dash', width=1)),
|
|
dict(type='line', x0=L, x1=L, y0=y_min, y1=y_max,
|
|
line=dict(color='grey', dash='dash', width=1)),
|
|
]
|
|
|
|
frames = []
|
|
for idx in frame_indices:
|
|
frames.append(go.Frame(
|
|
data=[go.Scatter(
|
|
x=x_vals,
|
|
y=T_matrix[:, idx],
|
|
mode='lines',
|
|
line=dict(color='royalblue', width=2),
|
|
name='FDM',
|
|
)],
|
|
name=str(idx),
|
|
layout=go.Layout(
|
|
title_text=f'FDM | t = {t_vals[idx]:.3f} s',
|
|
),
|
|
))
|
|
|
|
fig_anim = go.Figure(
|
|
data=[go.Scatter(
|
|
x=x_vals,
|
|
y=T_matrix[:, frame_indices[0]],
|
|
mode='lines',
|
|
line=dict(color='royalblue', width=2),
|
|
name='FDM',
|
|
)],
|
|
layout=go.Layout(
|
|
title=f'FDM | t = {t_vals[frame_indices[0]]:.3f} s',
|
|
xaxis=dict(title='x [m]', range=[-0.02 * L, 1.02 * L]),
|
|
yaxis=dict(title='T [°C]', range=[y_min, y_max]),
|
|
shapes=vline_shapes,
|
|
updatemenus=[dict(
|
|
type='buttons',
|
|
showactive=False,
|
|
y=1.15,
|
|
x=0.5,
|
|
xanchor='center',
|
|
buttons=[
|
|
dict(
|
|
label='▶ Play',
|
|
method='animate',
|
|
args=[None, dict(
|
|
frame=dict(duration=40, redraw=False),
|
|
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=False),
|
|
)],
|
|
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)),
|
|
)],
|
|
),
|
|
frames=frames,
|
|
)
|
|
|
|
anim_path = _next_path(FDM_ANIM_DIR, 'animation', '.html')
|
|
fig_anim.write_html(anim_path)
|
|
print(f"Animation saved → {anim_path}")
|
|
|
|
# ------------------------------------------------------------------
|
|
# 3. Time evolution at fixed spatial points
|
|
# ------------------------------------------------------------------
|
|
fixed_fractions = [0.0, 0.25, 0.5, 0.75, 1.0]
|
|
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd']
|
|
|
|
fig_ts = go.Figure()
|
|
|
|
for frac, color in zip(fixed_fractions, colors):
|
|
x_target = frac * L
|
|
idx_x = int(np.argmin(np.abs(x_vals - x_target)))
|
|
label = f'x = {x_vals[idx_x]:.2f} m'
|
|
fig_ts.add_trace(go.Scatter(
|
|
x=t_vals,
|
|
y=T_matrix[idx_x, :],
|
|
mode='lines',
|
|
name=label,
|
|
line=dict(color=color, width=2),
|
|
))
|
|
|
|
# "Heat ON" vertical dashed line at t = T_STEP
|
|
t_step = config.T_STEP
|
|
t_min_val = float(np.min(t_vals))
|
|
t_max_val = float(np.max(t_vals))
|
|
T_min_val = float(np.min(T_matrix)) - 1.0
|
|
T_max_val = float(np.max(T_matrix)) + 1.0
|
|
|
|
fig_ts.add_shape(
|
|
type='line',
|
|
x0=t_step, x1=t_step,
|
|
y0=T_min_val, y1=T_max_val,
|
|
line=dict(color='red', dash='dash', width=1.5),
|
|
)
|
|
fig_ts.add_annotation(
|
|
x=t_step,
|
|
y=T_max_val,
|
|
text='Heat ON',
|
|
showarrow=False,
|
|
yanchor='top',
|
|
font=dict(color='red', size=11),
|
|
)
|
|
|
|
fig_ts.update_layout(
|
|
title='FDM — Temperature evolution at fixed points',
|
|
xaxis=dict(title='t [s]', range=[t_min_val, t_max_val]),
|
|
yaxis=dict(title='T [°C]', range=[T_min_val, T_max_val]),
|
|
legend=dict(x=0.01, y=0.99),
|
|
height=480,
|
|
)
|
|
|
|
ts_path = _next_path(FDM_ANIM_DIR, 'time_series', '.html')
|
|
fig_ts.write_html(ts_path)
|
|
print(f"Time-series saved → {ts_path}")
|