File size: 5,456 Bytes
09c43eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
"""Render Darcy coefficient/GT/prediction/signed-error panels and diagnostics."""
from __future__ import annotations

import csv
import json
from pathlib import Path

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np

from common import atomic_json, configuration, read_config


def main():
    args=configuration('Visualize the full HNO Darcy experiment').parse_args()
    c=read_config(args.config)
    output=Path(c['output']['results_dir'])
    data=np.load(output/'predictions.npz')
    metrics=json.loads((output/'metrics.json').read_text())
    with (output/'training_history.csv').open() as f:
        history=list(csv.DictReader(f))
    epochs=np.asarray([int(r['epoch']) for r in history])
    if len(history)!=c['training']['epochs'] or list(epochs)!=list(range(1,c['training']['epochs']+1)):
        raise ValueError('Visualization requires the complete formal training history')
    if len(data['prediction'])!=c['data']['n_eval']:
        raise ValueError('Incomplete inference data')
    dpi=c['visualization']['dpi']
    fig,axes=plt.subplots(1,3,figsize=(14,3.8),constrained_layout=True)
    for name,label in [('train_relative_l2','Training relative L2'),('eval_relative_l2','Evaluation relative L2')]:
        axes[0].semilogy(epochs,[float(r[name]) for r in history],label=label,lw=1)
    axes[0].axvline(metrics['best_epoch'],color='gray',ls=':',label='Best checkpoint')
    axes[0].axhline(metrics['paper_relative_l2'],color='black',ls='--',label='Paper: 0.00446')
    axes[0].set(xlabel='Epoch',ylabel='Mean relative L2');axes[0].legend(fontsize=8)
    axes[1].semilogy(epochs,[float(r['train_loss']) for r in history],label='Total loss / sample')
    axes[1].semilogy(epochs,[float(r['train_derivative']) for r in history],label='Derivative relative loss')
    axes[1].set(xlabel='Epoch',ylabel='Loss');axes[1].legend(fontsize=8)
    axes[2].plot(epochs,[float(r['learning_rate']) for r in history]);axes[2].set(xlabel='Epoch',ylabel='Learning rate')
    fig.savefig(output/'training_curves.png',dpi=dpi);fig.savefig(output/'training_curves.pdf');plt.close(fig)
    chosen=c['visualization']['sample_indices']
    fig,axes=plt.subplots(len(chosen),4,figsize=(13,3*len(chosen)),squeeze=False,constrained_layout=True)
    titles=['Coefficient a','Ground truth u','HNO prediction','Signed error (prediction - GT)']
    for row,index in enumerate(chosen):
        a,u,p=(data[k][index] for k in ['coefficient','target','prediction'])
        residual=p-u
        vmin=min(u.min(),p.min());vmax=max(u.max(),p.max())
        error_bound=max(float(np.abs(residual).max()),1e-12)
        specifications=[(a,'viridis',None,None),(u,'viridis',vmin,vmax),(p,'viridis',vmin,vmax),
                        (residual,'RdBu_r',-error_bound,error_bound)]
        for col,(field,cmap,lo,hi) in enumerate(specifications):
            ax=axes[row,col]
            im=ax.imshow(field,origin='lower',extent=(0,1,0,1),cmap=cmap,vmin=lo,vmax=hi)
            ax.set(xlabel='x',ylabel='y',aspect='equal')
            if row==0:ax.set_title(titles[col],fontsize=10)
            fig.colorbar(im,ax=ax,fraction=.046,pad=.04)
        axes[row,0].set_ylabel(f'Sample {index}\ny')
        axes[row,2].text(.02,.98,f'Rel. L2={data["relative_l2"][index]:.5f}',transform=axes[row,2].transAxes,
                         va='top',fontsize=8,bbox=dict(facecolor='white',alpha=.8,edgecolor='none'))
    fig.savefig(output/'darcy_predictions.png',dpi=dpi);fig.savefig(output/'darcy_predictions.pdf');plt.close(fig)
    fig,ax=plt.subplots(figsize=(6,4),constrained_layout=True)
    ax.hist(data['relative_l2'],bins=24,color='#35668b',edgecolor='white')
    ax.axvline(metrics['mean_relative_l2'],label=f'Mean: {metrics["mean_relative_l2"]:.6f}',color='black')
    ax.set(xlabel='Per-sample relative L2',ylabel='Evaluation samples',title='Darcy: 200 best-checkpoint predictions')
    ax.legend();fig.savefig(output/'error_distribution.png',dpi=dpi);plt.close(fig)
    # Appendix J.3: aggregate Fourier energy over the full 200-case evaluation set.
    residual=data['prediction']-data['target']
    error_energy=np.abs(np.fft.fft2(residual,axes=(-2,-1)))**2
    target_energy=np.abs(np.fft.fft2(data['target'],axes=(-2,-1)))**2
    size=residual.shape[-1];freq=np.fft.fftfreq(size)*size
    yy,xx=np.meshgrid(freq,freq,indexing='ij');bins=np.floor(np.sqrt(xx**2+yy**2)).astype(int)
    spectrum=[]
    for b in range(bins.max()+1):
        select=bins==b;den=target_energy[:,select].sum()
        spectrum.append(float(np.sqrt(error_energy[:,select].sum()/max(den,1e-30))))
    fig,ax=plt.subplots(figsize=(6,4),constrained_layout=True)
    ax.semilogy(np.arange(len(spectrum)),spectrum);ax.set(xlabel='Radial frequency |k|',ylabel='Relative error spectrum')
    fig.savefig(output/'error_spectrum.png',dpi=dpi);plt.close(fig)
    atomic_json(output/'visualization_manifest.json',{'status':'completed','n_eval':len(data['prediction']),
                'epochs':len(history),'sample_indices':chosen,'dpi':dpi,'colormaps':['viridis','RdBu_r'],
                'figures':['training_curves.png','training_curves.pdf','darcy_predictions.png','darcy_predictions.pdf',
                           'error_distribution.png','error_spectrum.png'],'radial_error_spectrum':spectrum})
    print(f'VISUALIZATION_COMPLETED results={output} epochs={len(history)} samples={len(data["prediction"])}',flush=True)


if __name__=='__main__':
    main()