zhangrenchao commited on
Commit
956cd31
·
verified ·
1 Parent(s): 68fd2c6

Publish SamudrACE reproduction

Browse files
.gitattributes CHANGED
@@ -1,35 +1,6 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.py text eol=lf
2
+ *.md text eol=lf
3
+ *.json text eol=lf
4
+ *.yaml text eol=lf
5
+ *.pt binary
6
+ *.npz binary
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/config.yaml ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ seed: 42
2
+ data:
3
+ format_version: samudrace_tiles_v1
4
+ path: data/coupled_tiles.npz
5
+ logical_grid: [180, 360]
6
+ tile_size: 8
7
+ tile_origins: [[20, 40], [80, 160], [140, 280], [50, 320]]
8
+ atmosphere_channels: 46
9
+ ocean_channels: 80
10
+ model:
11
+ hidden_dim: 16
12
+ train:
13
+ epochs: 1
14
+ learning_rate: 0.001
15
+ atmosphere_steps_per_ocean: 20
16
+ paths:
17
+ checkpoint: result/checkpoints/samudrace.pt
18
+ training_metrics: result/training/metrics.json
19
+ predictions: result/output/predictions.npz
20
+ evaluation: result/evaluation/metrics.json
21
+ figure: result/evaluation/comparison.png
22
+ paper_model:
23
+ training_years: 155
24
+ validation_years: 5
25
+ test_years: 40
26
+ atmosphere_step_hours: 6
27
+ ocean_step_days: 5
28
+ atmosphere_levels: 8
29
+ ocean_levels: 19
30
+ optimizer: AdamW
31
+ batch_size: 16
config.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"model_name":"SamudrACE","model_type":"samudrace","architectures":["SamudrACE"],"framework":"PyTorch","domain":"coupled-climate","task":"atmosphere-ocean-emulation","implementation":{"entry_point":"model/samudrace.py","scope":"L2 core coupling reproduction"},"architecture":{"logical_grid":[180,360],"atmosphere_channels":46,"atmosphere_levels":8,"ocean_channels":80,"ocean_levels":19,"coupling_ratio":20,"atmosphere_step_hours":6,"ocean_step_days":5},"data":{"source":"GFDL CM4 piControl","years":200,"split_years":[155,5,40],"synthetic_tiles":true,"enumerated_fields":126,"paper_abstract_fields":145,"unresolved_fields":19},"configuration_sources":["conf/config.yaml","model/samudrace.py","scripts/fake_data.py","scripts/train.py","scripts/inference.py","scripts/result.py"]}
model/samudrace.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compact physical-state coupled atmosphere-ocean emulator."""
2
+ import json
3
+ from pathlib import Path
4
+ import numpy as np
5
+ import torch
6
+ from torch import nn
7
+ import yaml
8
+
9
+ ATM3D=("T","qT","U","V");ATM2D=("Ts","ps");DIAG=("RSW","OLR","USWsfc","ULWsfc","DSWsfc","DLWsfc","LHF","SHF","P","dTWP_adv","tau_u","tau_v")
10
+ OCEAN3D=("thetao","so","uo","vo");OCEAN2D=("SIC","HI","SST","ZOS")
11
+ def load_config(root):return yaml.safe_load((Path(root)/"conf/config.yaml").read_text())
12
+ def synthetic_state(origin,size,i,step=0):
13
+ y,x=torch.meshgrid(torch.arange(origin[0],origin[0]+size),torch.arange(origin[1],origin[1]+size),indexing="ij");lat=torch.deg2rad(90-(y+.5));lon=torch.deg2rad(-180+(x+.5));a=torch.arange(46)[:,None,None];o=torch.arange(80)[:,None,None];atm=torch.sin((a%6+1)*lat+.03*a+.05*(i+step))*torch.cos(lon);oce=torch.cos((o%5+1)*lat-.01*o+.02*(i+step))*torch.sin(lon);return atm.float(),oce.float()
14
+ class ResidualEmulator(nn.Module):
15
+ def __init__(self,cin,cout,h):super().__init__();self.net=nn.Sequential(nn.Conv2d(cin,h,1),nn.GELU(),nn.Conv2d(h,h,3,padding=1),nn.GELU(),nn.Conv2d(h,cout,1))
16
+ def forward(self,x):return self.net(x)
17
+ class SamudrACE(nn.Module):
18
+ def __init__(self,hidden_dim=16):super().__init__();self.atmosphere=ResidualEmulator(48,46,hidden_dim);self.ocean=ResidualEmulator(92,80,hidden_dim);self.model_config={"hidden_dim":hidden_dim}
19
+ def atmosphere_step(self,atm,ocean):return atm+self.atmosphere(torch.cat((atm,ocean[:,[76,0]]),1))
20
+ def ocean_step(self,ocean,flux):return ocean+self.ocean(torch.cat((ocean,flux),1))
21
+ def forward(self,atm,ocean,n=20):return self.coupled_step(atm,ocean,n)
22
+ def coupled_step(self,atm,ocean,n=20):
23
+ flux=[]
24
+ for _ in range(n):atm=self.atmosphere_step(atm,ocean);flux.append(atm[:,-12:])
25
+ ocean=self.ocean_step(ocean,torch.stack(flux).mean(0));return atm,ocean
26
+ def write_json(path,obj):path=Path(path);path.parent.mkdir(parents=True,exist_ok=True);path.write_text(json.dumps(obj,indent=2)+"\n")
scripts/fake_data.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import sys,json,numpy as np
3
+ ROOT=Path(__file__).resolve().parents[1];sys.path.insert(0,str(ROOT))
4
+ from model.samudrace import *
5
+ c=load_config(ROOT);aa=[];oo=[];at=[];ot=[]
6
+ for i,p in enumerate(c["data"]["tile_origins"]):a,o=synthetic_state(p,8,i);ta,to=synthetic_state(p,8,i,1);aa.append(a);oo.append(o);at.append(ta);ot.append(to)
7
+ path=ROOT/c["data"]["path"];path.parent.mkdir(parents=True,exist_ok=True);np.savez_compressed(path,atmosphere=np.stack(aa),ocean=np.stack(oo),atmosphere_target=np.stack(at),ocean_target=np.stack(ot),origins=np.array(c["data"]["tile_origins"]),logical_shape=np.array([126,180,360]),is_complete_global=False);print(path)
scripts/inference.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import sys,numpy as np,torch
3
+ ROOT=Path(__file__).resolve().parents[1];sys.path.insert(0,str(ROOT))
4
+ from model.samudrace import *
5
+ c=load_config(ROOT);d=np.load(ROOT/c["data"]["path"]);ck=torch.load(ROOT/c["paths"]["checkpoint"],map_location="cpu",weights_only=True);m=SamudrACE(**ck["model_config"]);m.load_state_dict(ck["model"]);a=torch.tensor(d["atmosphere"]);o=torch.tensor(d["ocean"]);ap=[];op=[]
6
+ with torch.no_grad():
7
+ for _ in range(4):a,o=m.coupled_step(a,o,20);ap.append(a.numpy());op.append(o.numpy())
8
+ path=ROOT/c["paths"]["predictions"];path.parent.mkdir(parents=True,exist_ok=True);np.savez_compressed(path,atmosphere=np.stack(ap,1),ocean=np.stack(op,1),origins=d["origins"],lead_days=np.arange(1,5)*5,logical_shape=d["logical_shape"],is_complete_global=False);print(path)
scripts/result.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import sys,numpy as np
3
+ import matplotlib;matplotlib.use("Agg");import matplotlib.pyplot as plt
4
+ ROOT=Path(__file__).resolve().parents[1];sys.path.insert(0,str(ROOT))
5
+ from model.samudrace import load_config,write_json
6
+ c=load_config(ROOT);d=np.load(ROOT/c["paths"]["predictions"]);a=d["atmosphere"];o=d["ocean"];armse=np.sqrt(np.mean((a-a[:,0:1])**2,axis=(0,2,3,4)));ormse=np.sqrt(np.mean((o-o[:,0:1])**2,axis=(0,2,3,4)));heat=o[:,:,0].mean((0,2,3));salt=o[:,:,19].mean((0,2,3));write_json(ROOT/c["paths"]["evaluation"],{"atmosphere_drift_rmse":armse.tolist(),"ocean_drift_rmse":ormse.tolist(),"ocean_heat_proxy":heat.tolist(),"ocean_salt_proxy":salt.tolist(),"is_complete_global":False,"synthetic":True});fig,ax=plt.subplots(1,2,figsize=(9,3.5));ax[0].plot(d["lead_days"],armse,label="atmosphere");ax[0].plot(d["lead_days"],ormse,label="ocean");ax[0].legend();ax[0].set(xlabel="Lead (days)",ylabel="Drift RMSE");im=ax[1].imshow(o[0,-1,76]-o[0,0,76],cmap="coolwarm");ax[1].set_title("SST drift proxy");fig.colorbar(im,ax=ax[1]);fig.tight_layout();path=ROOT/c["paths"]["figure"];path.parent.mkdir(parents=True,exist_ok=True);fig.savefig(path,dpi=150);print(path)
scripts/train.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import sys,os,numpy as np,torch
3
+ import torch.distributed as dist
4
+ from torch.nn.parallel import DistributedDataParallel as DDP
5
+ ROOT=Path(__file__).resolve().parents[1];sys.path.insert(0,str(ROOT))
6
+ from model.samudrace import *
7
+ c=load_config(ROOT);rank=int(os.getenv("RANK",0));world=int(os.getenv("WORLD_SIZE",1));distributed=world>1
8
+ if distributed:dist.init_process_group("gloo")
9
+ torch.manual_seed(c["seed"]);torch.set_num_threads(2);d=np.load(ROOT/c["data"]["path"]);base=SamudrACE(**c["model"]);m=DDP(base) if distributed else base;opt=torch.optim.AdamW(m.parameters(),lr=c["train"]["learning_rate"]);losses=[]
10
+ for i in range(rank,len(d["atmosphere"]),world):a=torch.tensor(d["atmosphere"][i:i+1]);o=torch.tensor(d["ocean"][i:i+1]);pa,po=m(a,o,20);loss=((pa-torch.tensor(d["atmosphere_target"][i:i+1]))**2).mean()+((po-torch.tensor(d["ocean_target"][i:i+1]))**2).mean();opt.zero_grad();loss.backward();opt.step();losses.append(float(loss))
11
+ v=torch.tensor([sum(losses),len(losses)],dtype=torch.float64)
12
+ if distributed:dist.all_reduce(v)
13
+ path=ROOT/c["paths"]["checkpoint"]
14
+ if rank==0:path.parent.mkdir(parents=True,exist_ok=True);torch.save({"model":base.state_dict(),"model_config":c["model"]},path);write_json(ROOT/c["paths"]["training_metrics"],{"coupled_mse":float(v[0]/v[1]),"world_size":world});print(path)
15
+ if distributed:dist.destroy_process_group()
weight/.gitkeep ADDED
File without changes