"""

Scientific ML Studio -- reference code (written by hand, not generated by the Studio)
 
Licence : Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)
          https://creativecommons.org/licenses/by-nc/4.0/
(c) 2026 Scientific ML Studio. All rights are reserved to the Scientific ML Studio platform
except the permissions granted by the licence above.
 
You may share and adapt this code for NON-COMMERCIAL purposes, provided you credit
"Scientific ML Studio" with a link to https://scimlstudio.com/, and indicate any changes you make.
Commercial use, paid client work and production deployment need prior written permission,
which you can ask for through the site's Contact page.
-------------------------------------------------------------------------------



Compare a Studio "Heat in a plate" run with the finite element reference.

    python heat_2d_compare.py results.csv

results.csv is the table the Studio script writes. It holds the network's value u1 at
10,000 new random points (x, y, t). We evaluate the reference at the same points and
report the error. Needs numpy and scipy, and heat_2d_reference.py in the same folder.
Licence: CC BY-NC 4.0, (c) 2026 Scientific ML Studio.
"""
import csv, sys
import numpy as np
import heat_2d_reference as ref

rows = []
with open(sys.argv[1]) as f:
    for row in csv.reader(f):
        if not row or row[0].startswith("#") or row[0] == "x":
            continue                                   # blank line, summary block, header
        rows.append([float(v) for v in row])
x, y, t, u_pinn = np.array(rows)[:, :4].T

sol = ref.fem_solve(n_el=64, n_steps=500)              # a few seconds; 96 and 1000 changes nothing printed here
u_fem = ref.fem_eval(sol, x, y, t)
err = u_pinn - u_fem

print(f"points                   {len(x)}")
print(f"relative L2 error        {np.linalg.norm(err) / np.linalg.norm(u_fem):.2e}")
print(f"RMS error                {np.sqrt(np.mean(err**2)):.2e}")
print(f"mean absolute error      {np.mean(np.abs(err)):.2e}")
i = np.abs(err).argmax()
print(f"largest error            {abs(err[i]):.2e}  at x={x[i]:.2f}, y={y[i]:.2f}, t={t[i]:.3f}")
print(f"mean signed error        {err.mean():+.2e}")
print(f"smallest PINN value      {u_pinn.min():+.2e}   (reference is never below 0)")
print(f"points with u < 0        {100 * np.mean(u_pinn < 0):.1f} %")
print("\n t range       relative L2 error")
for a in np.arange(0, 1, 0.1):
    s = (t >= a) & (t < a + 0.1)
    print(f" {a:.1f} to {a + 0.1:.1f}    {100 * np.linalg.norm(err[s]) / np.linalg.norm(u_fem[s]):5.2f} %")
