Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commit Messages
Write all git commit messages in Italian.
Commands
source .venv/bin/activate # always first
python app.py # forward PINN: train / evaluate / visualize
python fdm/app.py # FDM reference solver menu
python -m inverse.app # inverse PINN menu (to be implemented)
pytest # all tests
pytest -m "not slow" # skip full-training tests
pytest tests/test_model.py # single file
Delete models/best_heat_pinn_model.pth to retrain from scratch.
Architecture
config.py ← all physical + numerical parameters
model.py ← HeatPINN (5-layer FC) + heat_pinn_loss()
engine.py ← prepare_data(), train_model(), evaluate_model()
app.py ← forward PINN CLI
visualizer.py ← PINN vs FDM plots (Plotly HTML)
fdm/solver.py ← FTCS explicit scheme, returns T_matrix[NX, NT]
inverse/ ← inverse PINN (to implement — see plan below)
tests/ ← pytest suite (42 tests); conftest.py has device, small_data, pinn_model fixtures
Key design decisions
Output scaling (model.py:forward):
T = T_AMB + (Q_VAL · L / K) · net(x_norm, t_norm)
This keeps net outputs in [0,1] and ∂T/∂x at O(1). Do not remove.
Loss normalization (model.py:heat_pinn_loss): all four terms are scaled to O(1) via _T_char = Q_VAL·L/K and _bc_scale. Changing physical params in config.py does not require retuning weights.
Collocation clustering (engine.py:prepare_data): 25% extra points near X_SRC (source gradient) and T_STEP (flux discontinuity). First lever to pull if accuracy is poor: increase N_F.
Training sequence: Adam (early stopping + ReduceLROnPlateau) → L-BFGS fine-tuning. L-BFGS uses a _last closure dict to capture loss components without double-calling the loss outside a grad context.
FDM Robin BCs (fdm/solver.py): implicit-like update T[0] = (T[1] + robin_coeff·T_amb) / (1 + robin_coeff). Point source added after BCs: T[i_src] += Q·α·dt/(k·dx).
Inverse PINN — implementation plan
Goal: identify unknown physical parameters (ALPHA, K, H_CONV) from sparse noisy temperature measurements. The network learns T(x,t) and the physics parameters simultaneously.
Files to create (in order)
inverse/config_inverse.py
N_SENSORS,SENSOR_POSITIONS(list of x positions)NOISE_STD— Gaussian noise std on measurements [°C]IDENTIFY = ['alpha', 'k', 'h_conv']ALPHA_INIT,K_INIT,H_CONV_INIT— initial guesses (2–5× off from true values)EPOCHS_INV,LR_ADAM_INV,W_DATA = 10.0MODELS_DIR,DATA_PATH
inverse/data.py
generate_measurements(noise_std, sensor_positions): callfdm.solver.solve()with true params fromconfig.py, sample at nearest FDM nodes, add noise, save toinverse/data/measurements.csv(columns:x, t, T)load_measurements(device): load CSV → tensors(x_s, t_s, T_meas)on device
inverse/model.py — InverseHeatPINN(nn.Module)
- Same 5-layer architecture as
HeatPINN - Unknown params as log-space
nn.Parameter(guarantees positivity without constraints):self.log_alpha = nn.Parameter(torch.log(torch.tensor(ALPHA_INIT))) self.log_k = nn.Parameter(torch.log(torch.tensor(K_INIT))) self.log_h_conv = nn.Parameter(torch.log(torch.tensor(H_CONV_INIT))) - Properties
alpha,k,h_convthat returnexp(log_*) forward()uses same output scaling asHeatPINNbut withself.kandself.alpha- Never
.detach()the learned params inside the loss — gradients must flow through them
inverse/loss.py — inverse_heat_pinn_loss(..., x_s, t_s, T_meas)
- Same PDE/IC/BC structure as
heat_pinn_loss()but usesmodel.alpha,model.k,model.h_conv - Normalization scales must be computed from the current learned params (not config constants), otherwise there is no gradient signal toward the physics params
- Adds data fit term:
L_data = mean((T_pred(x_s, t_s) − T_meas)²) / T_char² - Total:
w_pde·L_pde + w_ic·L_ic + w_bc·L_bc + w_data·L_data
inverse/engine.py
prepare_data_inverse(): same clustering strategy asengine.prepare_data()train_inverse(data, measurements): Adam only (no L-BFGS — unstable when physics params are learnable because loss curvature differs by orders of magnitude between network weights and physics params); print identified param values every 100 epochsevaluate_inverse(model): print table of true vs identified params with relative error %; also compute L2 error of T field vs FDM
inverse/app.py — CLI menu: (1) Generate measurements, (2) Train, (3) Evaluate, (0) Exit
inverse/__init__.py — empty
Pitfalls
- If
W_DATAis too high, BC/IC are ignored and the net overfits measurements (physics collapses) - Sensors far from x=0 and x=L → poor identification of
H_CONV(weak boundary signal) - Do not resample sensor points each epoch —
(x_s, t_s, T_meas)are fixed throughout training