CLM5-Emulator / model /clm5_emulator.py
zhangrenchao's picture
Publish CLM5-Emulator reproduction
835fa51 verified
Raw History Blame Contribute Delete
1.51 kB
"""CLM5 GPP/LHF EOF emulators and bounded parameter search."""
import json
from pathlib import Path
import numpy as np
import torch
from torch import nn
import yaml
PARAMETERS=("medlynslope","dleaf","kmax","fff","dint","baseflow_scalar")
def load_config(root):return yaml.safe_load((Path(root)/"conf/config.yaml").read_text())
class TargetEmulator(nn.Module):
def __init__(self,hidden_dim=12):super().__init__();self.net=nn.Sequential(nn.Linear(6,hidden_dim),nn.ReLU(),nn.Linear(hidden_dim,hidden_dim),nn.Tanh(),nn.Linear(hidden_dim,3))
def forward(self,x):return self.net(x)
class CLM5Emulator(nn.Module):
def __init__(self,hidden_dim=12):super().__init__();self.gpp=TargetEmulator(hidden_dim);self.lhf=TargetEmulator(hidden_dim);self.model_config={"hidden_dim":hidden_dim}
def forward(self,x):
if x.ndim!=2 or x.shape[1]!=6:raise ValueError("expected [B,6] normalized parameters")
return torch.stack((self.gpp(x),self.lhf(x)),1)
def reconstruct(pc,eof,mean):return np.einsum("btm,tmg->btg",pc,eof)+mean[None]
def bounded_search(model,observed_pc,scales,weights,candidates=4096,seed=0):
rng=np.random.default_rng(seed);p=torch.from_numpy(rng.random((candidates,6)).astype("float32"));
with torch.no_grad():pred=model(p).numpy()
cost=(((pred-observed_pc[None])/scales[None])**2*weights[None]).sum((1,2));i=int(cost.argmin());return p[i].numpy(),float(cost[i])
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")