Download scripts/common.py from OneScience-Group/NOTS-DarcyFlowRate: direct link, hf CLI and curl.
- Browser
- Download file 6.75 kB
-
https://huggingface.co/OneScience-Group/NOTS-DarcyFlowRate/resolve/main/scripts/common.py
- Command line
-
hf download hf://OneScience-Group/NOTS-DarcyFlowRate/scripts/common.py
-
curl -L -o common.py https://huggingface.co/OneScience-Group/NOTS-DarcyFlowRate/resolve/main/scripts/common.py
6.75 kB
| """Data, functional, provenance and computation boundary shared by entrypoints.""" | |
| import argparse | |
| import csv | |
| import hashlib | |
| import json | |
| import os | |
| import random | |
| import socket | |
| import sys | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parents[1] | |
| if (ROOT / '.deps').exists(): | |
| sys.path.insert(0, str(ROOT / '.deps')) | |
| sys.path.insert(0, str(ROOT)) | |
| import numpy as np | |
| import torch | |
| import yaml | |
| def parse_args(description): | |
| p = argparse.ArgumentParser(description=description) | |
| p.add_argument('--config', type=Path, default=ROOT / 'config/config.yaml') | |
| return p | |
| def load_config(path): | |
| with open(path) as f: | |
| cfg = yaml.safe_load(f) | |
| return cfg | |
| def save_json(path, value): | |
| path = Path(path) | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| temporary = path.with_suffix(path.suffix + '.tmp') | |
| temporary.write_text(json.dumps(value, indent=2, ensure_ascii=False, allow_nan=False)) | |
| temporary.replace(path) | |
| def save_torch(path, value): | |
| path = Path(path) | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| temporary = path.with_suffix(path.suffix + '.tmp') | |
| torch.save(value, temporary) | |
| temporary.replace(path) | |
| def model_state_on_cpu(model): | |
| """Export PyTorch state while handling neuraloperator 1.0.2 metadata. | |
| BaseModel.state_dict inserts an unprefixed dictionary key '_metadata' even | |
| when nested in NOTS. That entry is not a parameter/buffer and must not be | |
| passed to the enclosing nn.Module's strict loader. The model architecture | |
| is saved separately as checkpoint.model_config. OrderedDict._metadata (the | |
| PyTorch version information attribute) is preserved. | |
| """ | |
| state = model.state_dict() | |
| metadata = state.pop('_metadata', None) | |
| if metadata is not None and not isinstance(metadata, dict): | |
| raise TypeError('Unexpected neuraloperator initialization metadata') | |
| for name, value in state.items(): | |
| if not torch.is_tensor(value): | |
| raise TypeError(f'Unexpected non-tensor model state: {name}') | |
| state[name] = value.detach().cpu() | |
| return state | |
| def write_csv(path, rows): | |
| path = Path(path) | |
| if not rows: | |
| return | |
| temporary = path.with_suffix('.csv.tmp') | |
| with temporary.open('w', newline='') as f: | |
| w = csv.DictWriter(f, fieldnames=list(rows[0])) | |
| w.writeheader() | |
| w.writerows(rows) | |
| temporary.replace(path) | |
| def sha256(path): | |
| h = hashlib.sha256() | |
| with open(path, 'rb') as f: | |
| for chunk in iter(lambda: f.read(1024 * 1024), b''): | |
| h.update(chunk) | |
| return h.hexdigest() | |
| def config_hash(cfg): | |
| return hashlib.sha256(json.dumps(cfg, sort_keys=True).encode()).hexdigest() | |
| def case_guard(cfg): | |
| """Refuse login-node execution and attachment to any pre-existing job.""" | |
| if cfg['runtime']['require_case_slurm_job']: | |
| actual = os.environ.get('SLURM_JOB_ID', '') | |
| registry = Path(cfg['paths']['output_dir']) / '.onescience/jobs.json' | |
| if not actual or not registry.is_file(): | |
| raise RuntimeError('Compute requires a newly submitted case-owned SLURM job') | |
| jobs = json.loads(registry.read_text())['jobs'] | |
| if actual not in [str(j['job_id']) for j in jobs]: | |
| raise RuntimeError(f'Refusing unregistered job {actual}') | |
| if not os.environ.get('SLURM_JOB_NODELIST'): | |
| raise RuntimeError('Missing allocated compute node list') | |
| torch.set_num_threads(cfg['runtime']['num_threads']) | |
| device = torch.device(cfg['runtime']['device']) | |
| if device.type == 'cuda' and not torch.cuda.is_available(): | |
| raise RuntimeError('Allocated accelerator is unavailable; CPU fallback is disabled') | |
| return device | |
| def seed_all(seed): | |
| random.seed(seed) | |
| np.random.seed(seed) | |
| torch.manual_seed(seed) | |
| torch.cuda.manual_seed_all(seed) | |
| def rng_state(): | |
| return {'python': random.getstate(), 'numpy': np.random.get_state(), | |
| 'torch': torch.get_rng_state(), 'cuda': torch.cuda.get_rng_state_all()} | |
| def restore_rng(state): | |
| random.setstate(state['python']) | |
| np.random.set_state(state['numpy']) | |
| torch.set_rng_state(state['torch']) | |
| torch.cuda.set_rng_state_all(state['cuda']) | |
| def load_data(cfg, split='train'): | |
| path = Path(cfg['paths']['data_dir']) / cfg['paths'][split + '_file'] | |
| raw = torch.load(path, map_location='cpu', weights_only=True) | |
| out = [] | |
| for key in ('x', 'y'): | |
| a = raw[key].float() | |
| if a.ndim == 3: | |
| a = a.unsqueeze(1) | |
| if a.ndim != 4 or tuple(a.shape[1:]) != (1, 16, 16): | |
| raise ValueError(f'{path}:{key} invalid shape {a.shape}') | |
| if not torch.isfinite(a).all(): | |
| raise ValueError(f'{path}:{key} contains nonfinite data') | |
| out.append(a.contiguous()) | |
| if out[0].shape != out[1].shape: | |
| raise ValueError('Input and pressure pairing mismatch') | |
| if split == 'train' and len(out[0]) != cfg['experiment']['pool_size']: | |
| raise ValueError('The required 1000-candidate official pool is unavailable') | |
| meta = {'path': str(path), 'sha256': sha256(path), 'n': len(out[0]), | |
| 'shape': list(out[0].shape), 'x_range': [out[0].min().item(), out[0].max().item()], | |
| 'y_range': [out[1].min().item(), out[1].max().item()], 'dtype': 'float32'} | |
| return out[0], out[1], meta | |
| def negative_total_flow_rates(u, a): | |
| """Appendix D.1: integral(a grad(u).n), maximize negative physical outflow. | |
| First-order one-sided normal differences and rectangular edge quadrature. | |
| Equal 16x16 spacings cancel analytically; corners count once per edge. | |
| This deliberately uses original permeability values, not a display mask. | |
| """ | |
| if u.shape != a.shape or u.ndim != 4: | |
| raise ValueError('Functional requires matching [B,1,H,W] tensors') | |
| h, w = u.shape[-2:] | |
| left = -(a[..., :, 0] * (u[..., :, 1] - u[..., :, 0])).sum(-1) * (w / h) | |
| right = (a[..., :, -1] * (u[..., :, -1] - u[..., :, -2])).sum(-1) * (w / h) | |
| bottom = -(a[..., 0, :] * (u[..., 1, :] - u[..., 0, :])).sum(-1) * (h / w) | |
| top = (a[..., -1, :] * (u[..., -1, :] - u[..., -2, :])).sum(-1) * (h / w) | |
| return (left + right + bottom + top).mean(-1) | |
| def predict(model, xs, batch_size): | |
| model.eval() | |
| return torch.cat([model(xs[i:i + batch_size]) for i in range(0, len(xs), batch_size)]) | |
| def hardware_record(): | |
| return {'hostname': socket.gethostname(), 'job_id': os.environ.get('SLURM_JOB_ID'), | |
| 'node_list': os.environ.get('SLURM_JOB_NODELIST'), 'torch': torch.__version__, | |
| 'accelerator': torch.cuda.get_device_name(), 'device_count': torch.cuda.device_count(), | |
| 'device_memory_bytes': torch.cuda.get_device_properties(0).total_memory} | |