Upload folder using huggingface_hub
Browse files- README.md +123 -0
- conf/config.yaml +40 -0
- configuration.json +1 -0
- model/__init__.py +4 -0
- model/gpplus_model.py +778 -0
- model/gpregression.py +159 -0
- model/horseshoe.py +63 -0
- model/mollified_uniform.py +77 -0
- scripts/__init__.py +1 -0
- scripts/common.py +155 -0
- scripts/fake_data.py +65 -0
- scripts/inference.py +94 -0
- scripts/result.py +68 -0
- scripts/topology_optimization.py +427 -0
- scripts/train.py +116 -0
- weight/.gitkeep +1 -0
README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: unknown
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
tags:
|
| 6 |
+
- OneScience
|
| 7 |
+
- fluid dynamics
|
| 8 |
+
- topology optimization
|
| 9 |
+
- Gaussian processes
|
| 10 |
+
- physics-informed learning
|
| 11 |
+
frameworks: PyTorch
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
<p align="center">
|
| 15 |
+
<strong>
|
| 16 |
+
<span style="font-size: 30px;">GP_for_TO</span>
|
| 17 |
+
</strong>
|
| 18 |
+
</p>
|
| 19 |
+
|
| 20 |
+
# Model Overview
|
| 21 |
+
|
| 22 |
+
GP_for_TO (Physics-informed GP-TO) is a physics-informed Gaussian-process framework for topology optimization developed by researchers at Northwestern University. It jointly optimizes material distributions and physical state variables in complex design domains without requiring an explicit mesh discretization.
|
| 23 |
+
|
| 24 |
+
Paper: Simultaneous and Meshfree Topology Optimization with Physics-informed Gaussian Processes
|
| 25 |
+
https://arxiv.org/abs/2408.03490
|
| 26 |
+
|
| 27 |
+
# Model Description
|
| 28 |
+
|
| 29 |
+
GP_for_TO uses a Gaussian-process architecture whose mean function is parameterized by a deep neural network. It provides a mesh-free approach to fluid topology optimization problems such as minimizing dissipated power in Stokes flow.
|
| 30 |
+
|
| 31 |
+
# Usage
|
| 32 |
+
|
| 33 |
+
## 1. OneCode
|
| 34 |
+
|
| 35 |
+
Use the online OneCode environment for an intelligent, one-click AI for Science (AI4S) programming experience:
|
| 36 |
+
|
| 37 |
+
[Launch OneCode for one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 38 |
+
|
| 39 |
+
## 2. Manual Setup
|
| 40 |
+
|
| 41 |
+
**Hardware Requirements**
|
| 42 |
+
|
| 43 |
+
- A GPU or DCU is recommended.
|
| 44 |
+
- A CPU can be used for import checks and small-scale pipeline validation, but full training and inference will be slow.
|
| 45 |
+
- DCU users must install DTK in advance. DTK 25.04.2 or later, or the OneScience-recommended version for the target cluster, is recommended.
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
### Download the Model Package
|
| 49 |
+
|
| 50 |
+
```bash
|
| 51 |
+
modelscope download --model OneScience/GP_for_TO --local_dir ./GP_for_TO
|
| 52 |
+
cd GP_for_TO
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
### Set Up the Runtime Environment
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
**DCU Environment**
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
# Activate DTK and Conda first
|
| 62 |
+
conda create -n onescience311 python=3.11 -y
|
| 63 |
+
conda activate onescience311
|
| 64 |
+
# Installation with uv is also supported
|
| 65 |
+
pip install onescience[cfd-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
**GPU Environment**
|
| 69 |
+
```bash
|
| 70 |
+
# Activate Conda first
|
| 71 |
+
conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
|
| 72 |
+
conda activate onescience311
|
| 73 |
+
# Installation with uv is also supported
|
| 74 |
+
pip install onescience[cfd-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
### Training Data
|
| 79 |
+
The method does not rely on pregenerated topology structures or labeled physical fields. Instead, it samples spatial points within the design domain and constructs physics-informed training signals from the governing Stokes-flow equations, boundary conditions, dissipated-power objective, and material volume-fraction constraint. The paper evaluates four fluid topology optimization cases—Rugby, Pipe Bend, Diffuser, and Double Pipe—and compares the results against COMSOL solutions obtained with the SIMP method.
|
| 80 |
+
|
| 81 |
+
### Training
|
| 82 |
+
|
| 83 |
+
The default training configuration retains the original settings: `N_col_domain=10000`, `N_train_per_BC=25`, `num_iter=50000`, and `diff_method=Numerical`.
|
| 84 |
+
|
| 85 |
+
```bash
|
| 86 |
+
python scripts/train.py
|
| 87 |
+
```
|
| 88 |
+
### Model Weights
|
| 89 |
+
This repository will provide GP_for_TO model weights in the `weights/` directory. The weights will be uploaded soon.
|
| 90 |
+
|
| 91 |
+
## Inference
|
| 92 |
+
|
| 93 |
+
```bash
|
| 94 |
+
python scripts/inference.py
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
By default, inference loads `weight/gp_for_to.pt` and produces:
|
| 98 |
+
|
| 99 |
+
```text
|
| 100 |
+
result/inference/predictions.npz
|
| 101 |
+
result/inference/inference_summary.json
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
`predictions.npz` contains five arrays: `x`, `u`, `v`, `p`, and `ro`.
|
| 105 |
+
|
| 106 |
+
## Result Visualization
|
| 107 |
+
|
| 108 |
+
```bash
|
| 109 |
+
python scripts/result.py
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
# Official OneScience Resources
|
| 114 |
+
|
| 115 |
+
| Platform | OneScience Repository | Skills Repository |
|
| 116 |
+
| --- | --- | --- |
|
| 117 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 118 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 119 |
+
|
| 120 |
+
# Citations and License
|
| 121 |
+
|
| 122 |
+
- Original GP_for_TO paper: [Simultaneous and Meshfree Topology Optimization with Physics-informed Gaussian Processes](https://arxiv.org/abs/2408.03490).
|
| 123 |
+
- This repository retains the relevant source and attribution notices. Follow all applicable license requirements when using, modifying, or distributing its contents.
|
conf/config.yaml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
root:
|
| 2 |
+
problem: "doublepipe"
|
| 3 |
+
seed: 11
|
| 4 |
+
output_names: ["u", "v", "p", "ro"]
|
| 5 |
+
|
| 6 |
+
runtime:
|
| 7 |
+
onescience_src: null
|
| 8 |
+
|
| 9 |
+
data:
|
| 10 |
+
n_train_per_bc: 25
|
| 11 |
+
n_col_domain: 10000
|
| 12 |
+
|
| 13 |
+
model:
|
| 14 |
+
mean_function: "neural_network"
|
| 15 |
+
nn_layers_base: [64, 64, 64, 64, 64]
|
| 16 |
+
dtype: "float32"
|
| 17 |
+
|
| 18 |
+
training:
|
| 19 |
+
device: "auto"
|
| 20 |
+
gpu: 0
|
| 21 |
+
num_iter: 50000
|
| 22 |
+
lr_default: 0.001
|
| 23 |
+
diff_method: "Numerical"
|
| 24 |
+
checkpoint_path: "./weight/gp_for_to.pt"
|
| 25 |
+
output_dir: "./result/training"
|
| 26 |
+
plot_outputs: true
|
| 27 |
+
checkpoints: [1000, 10000, 20000, 30000, 40000, 50000]
|
| 28 |
+
|
| 29 |
+
inference:
|
| 30 |
+
device: "auto"
|
| 31 |
+
gpu: 0
|
| 32 |
+
checkpoint_path: "./weight/gp_for_to.pt"
|
| 33 |
+
output_dir: "./result/inference"
|
| 34 |
+
n_col_domain: 10000
|
| 35 |
+
|
| 36 |
+
fake_data:
|
| 37 |
+
problem: "doublepipe"
|
| 38 |
+
n_train_per_bc: 25
|
| 39 |
+
n_col_domain: 10000
|
| 40 |
+
output_dir: "./data/fake"
|
configuration.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"framework":"Pytorch","task":"physics-informed-topology-optimization"}
|
model/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .gpregression import GPR
|
| 2 |
+
from .gpplus_model import GPPLUS
|
| 3 |
+
from .horseshoe import LogHalfHorseshoePrior
|
| 4 |
+
from .mollified_uniform import MollifiedUniformPrior
|
model/gpplus_model.py
ADDED
|
@@ -0,0 +1,778 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import torch
|
| 3 |
+
import math
|
| 4 |
+
import gpytorch
|
| 5 |
+
from gpytorch.constraints import Positive
|
| 6 |
+
from gpytorch.priors import NormalPrior
|
| 7 |
+
from gpytorch.distributions import MultivariateNormal
|
| 8 |
+
from onescience.utils.GP_TO.plot_latenth import plot_sep
|
| 9 |
+
from .gpregression import GPR
|
| 10 |
+
import gpytorch.kernels as kernels
|
| 11 |
+
from .mollified_uniform import MollifiedUniformPrior
|
| 12 |
+
from pandas import DataFrame
|
| 13 |
+
import matplotlib.pyplot as plt
|
| 14 |
+
from torch.nn.parameter import Parameter
|
| 15 |
+
import torch.nn.functional as F
|
| 16 |
+
|
| 17 |
+
def setlevels(X, qual_index = None, return_label = False):
|
| 18 |
+
labels = []
|
| 19 |
+
if qual_index == []:
|
| 20 |
+
return X
|
| 21 |
+
if qual_index is None:
|
| 22 |
+
qual_index = list(range(X.shape[-1]))
|
| 23 |
+
# if type(X) == np.ndarray:
|
| 24 |
+
# temp = torch.from_numpy(X).detach().clone()
|
| 25 |
+
temp = np.copy(X)
|
| 26 |
+
if type(X) == torch.Tensor:
|
| 27 |
+
temp = X.clone()
|
| 28 |
+
if temp.ndim > 1:
|
| 29 |
+
for j in qual_index:
|
| 30 |
+
l = np.sort(np.unique(temp[..., j])).tolist()
|
| 31 |
+
labels.append(l)
|
| 32 |
+
#l = torch.unique(temp[..., j], sorted = True).tolist()
|
| 33 |
+
temp[..., j] = torch.tensor([*map(lambda m: l.index(m),temp[..., j])])
|
| 34 |
+
else:
|
| 35 |
+
l = torch.unique(temp, sorted = True)
|
| 36 |
+
temp = torch.tensor([*map(lambda m: l.tolist().index(m), temp)])
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
if temp.dtype == object:
|
| 40 |
+
temp = temp.astype(float)
|
| 41 |
+
if type(X) == np.ndarray:
|
| 42 |
+
temp = torch.from_numpy(temp)
|
| 43 |
+
|
| 44 |
+
if return_label:
|
| 45 |
+
return temp, labels
|
| 46 |
+
else:
|
| 47 |
+
return temp
|
| 48 |
+
else:
|
| 49 |
+
if type(X) == np.ndarray:
|
| 50 |
+
temp = torch.from_numpy(temp)
|
| 51 |
+
if return_label:
|
| 52 |
+
return temp, labels
|
| 53 |
+
else:
|
| 54 |
+
return temp
|
| 55 |
+
|
| 56 |
+
class GPPLUS(GPR):
|
| 57 |
+
"""The latent Map GP regression model (LMGP) which extends GPs to handle categorical inputs.
|
| 58 |
+
|
| 59 |
+
:note: Binary categorical variables should not be treated as qualitative inputs. There is no
|
| 60 |
+
benefit from applying a latent variable treatment for such variables. Instead, treat them
|
| 61 |
+
as numerical inputs.
|
| 62 |
+
|
| 63 |
+
:param train_x: The training inputs (size N x d). Qualitative inputs needed to be encoded as
|
| 64 |
+
integers 0,...,L-1 where L is the number of levels. For best performance, scale the
|
| 65 |
+
numerical variables to the unit hypercube.
|
| 66 |
+
"""
|
| 67 |
+
def __init__(
|
| 68 |
+
self,
|
| 69 |
+
#transformation_of_A_parameters:str,
|
| 70 |
+
train_x:torch.Tensor,
|
| 71 |
+
train_y:torch.Tensor,
|
| 72 |
+
collocation_x:torch.Tensor,
|
| 73 |
+
qual_ind_lev = {},
|
| 74 |
+
multiple_noise = False,
|
| 75 |
+
lv_dim:int=2,
|
| 76 |
+
quant_correlation_class:str='Rough_RBF',
|
| 77 |
+
noise:float=5e-8,
|
| 78 |
+
fix_noise:bool=True,
|
| 79 |
+
lb_noise:float=1e-8,
|
| 80 |
+
NN_layers:list = [],
|
| 81 |
+
name_output:str='u',
|
| 82 |
+
encoding_type = 'one-hot',
|
| 83 |
+
uniform_encoding_columns = 2,
|
| 84 |
+
lv_columns = [] ,
|
| 85 |
+
basis='neural_network',
|
| 86 |
+
NN_layers_base=[4,4],
|
| 87 |
+
basis_function_size=None,
|
| 88 |
+
device="cpu",
|
| 89 |
+
dtype= torch.float32
|
| 90 |
+
) -> None:
|
| 91 |
+
|
| 92 |
+
tkwargs = {} # or dict()
|
| 93 |
+
tkwargs['dtype'] = dtype
|
| 94 |
+
tkwargs['device'] =device
|
| 95 |
+
|
| 96 |
+
qual_index = list(qual_ind_lev.keys())
|
| 97 |
+
all_index = set(range(train_x.shape[-1]))
|
| 98 |
+
quant_index = list(all_index.difference(qual_index))
|
| 99 |
+
num_levels_per_var = list(qual_ind_lev.values())
|
| 100 |
+
#------------------- lm columns --------------------------
|
| 101 |
+
lm_columns = list(set(qual_index).difference(lv_columns))
|
| 102 |
+
if len(lm_columns) > 0:
|
| 103 |
+
qual_kernel_columns = [*lv_columns, lm_columns]
|
| 104 |
+
else:
|
| 105 |
+
qual_kernel_columns = lv_columns
|
| 106 |
+
|
| 107 |
+
#########################
|
| 108 |
+
if len(qual_index) > 0:
|
| 109 |
+
train_x = setlevels(train_x, qual_index=qual_index)
|
| 110 |
+
#
|
| 111 |
+
if multiple_noise:
|
| 112 |
+
noise_indices = list(range(0,num_levels_per_var[0]))
|
| 113 |
+
else:
|
| 114 |
+
noise_indices = []
|
| 115 |
+
|
| 116 |
+
if len(qual_index) == 1 and num_levels_per_var[0] < 2:
|
| 117 |
+
temp = quant_index.copy()
|
| 118 |
+
temp.append(qual_index[0])
|
| 119 |
+
quant_index = temp.copy()
|
| 120 |
+
qual_index = []
|
| 121 |
+
lv_dim = 0
|
| 122 |
+
elif len(qual_index) == 0:
|
| 123 |
+
lv_dim = 0
|
| 124 |
+
|
| 125 |
+
quant_correlation_class_name = quant_correlation_class
|
| 126 |
+
|
| 127 |
+
if len(qual_index) == 0:
|
| 128 |
+
lv_dim = 0
|
| 129 |
+
|
| 130 |
+
if quant_correlation_class_name == 'Rough_RBF':
|
| 131 |
+
quant_correlation_class = 'RBFKernel'
|
| 132 |
+
|
| 133 |
+
if quant_correlation_class_name == 'Matern32Kernel':
|
| 134 |
+
quant_correlation_class = 'Matern32Kernel'
|
| 135 |
+
|
| 136 |
+
if quant_correlation_class_name == 'Matern52Kernel':
|
| 137 |
+
quant_correlation_class = 'Matern52Kernel'
|
| 138 |
+
|
| 139 |
+
if quant_correlation_class_name == 'Matern12Kernel':
|
| 140 |
+
quant_correlation_class = 'Matern12Kernel'
|
| 141 |
+
|
| 142 |
+
if len(qual_index) > 0:
|
| 143 |
+
####################### Defined multiple kernels for seperate variables ###################
|
| 144 |
+
qual_kernels = []
|
| 145 |
+
for i in range(len(qual_kernel_columns)):
|
| 146 |
+
qual_kernels.append(kernels.RBFKernel(
|
| 147 |
+
active_dims=torch.arange(lv_dim) + lv_dim * i) )
|
| 148 |
+
qual_kernels[i].initialize(**{'lengthscale':1.0})
|
| 149 |
+
qual_kernels[i].raw_lengthscale.requires_grad_(False)
|
| 150 |
+
|
| 151 |
+
if len(quant_index) == 0:
|
| 152 |
+
correlation_kernel = qual_kernels[0]
|
| 153 |
+
for i in range(1, len(qual_kernels)):
|
| 154 |
+
correlation_kernel *= qual_kernels[i]
|
| 155 |
+
else:
|
| 156 |
+
try:
|
| 157 |
+
quant_correlation_class = getattr(kernels,quant_correlation_class)
|
| 158 |
+
except:
|
| 159 |
+
raise RuntimeError(
|
| 160 |
+
"%s not an allowed kernel" % quant_correlation_class
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
if quant_correlation_class_name == 'RBFKernel':
|
| 164 |
+
quant_kernel = quant_correlation_class(
|
| 165 |
+
ard_num_dims=len(quant_index),
|
| 166 |
+
active_dims=len(qual_kernel_columns) * lv_dim+torch.arange(len(quant_index)),
|
| 167 |
+
lengthscale_constraint= Positive(transform= torch.exp,inv_transform= torch.log)
|
| 168 |
+
)
|
| 169 |
+
elif quant_correlation_class_name == 'Rough_RBF':
|
| 170 |
+
quant_kernel = quant_correlation_class(
|
| 171 |
+
ard_num_dims=len(quant_index),
|
| 172 |
+
active_dims=len(qual_kernel_columns)*lv_dim+torch.arange(len(quant_index)),
|
| 173 |
+
lengthscale_constraint= Positive(transform = lambda x: 2.0**(-0.5) * torch.pow(10,-x/2),inv_transform= lambda x: -2.0*torch.log10(x/2.0))
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
elif quant_correlation_class_name == 'Matern12Kernel':
|
| 178 |
+
quant_kernel = quant_correlation_class(
|
| 179 |
+
ard_num_dims=len(quant_index),
|
| 180 |
+
active_dims=len(qual_kernel_columns)*lv_dim+torch.arange(len(quant_index)),
|
| 181 |
+
lengthscale_constraint= Positive(transform= lambda x: 2.0**(-0.5) * torch.pow(10,-x/2),inv_transform= lambda x: -2.0*torch.log10(x/2.0))
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
elif quant_correlation_class_name == 'Matern32Kernel':
|
| 185 |
+
quant_kernel = quant_correlation_class(
|
| 186 |
+
ard_num_dims=len(quant_index),
|
| 187 |
+
active_dims=len(qual_kernel_columns)*lv_dim+torch.arange(len(quant_index)),
|
| 188 |
+
#lengthscale_constraint= Positive(transform= torch.exp,inv_transform= torch.log)
|
| 189 |
+
lengthscale_constraint= Positive(transform= lambda x: 2.0**(-0.5) * torch.pow(10,-x/2),inv_transform= lambda x: -2.0*torch.log10(x/2.0))
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
elif quant_correlation_class_name == 'Matern52Kernel':
|
| 193 |
+
quant_kernel = quant_correlation_class(
|
| 194 |
+
ard_num_dims=len(quant_index),
|
| 195 |
+
active_dims=len(qual_kernel_columns)*lv_dim+torch.arange(len(quant_index)),
|
| 196 |
+
#lengthscale_constraint= Positive(transform= torch.exp,inv_transform= torch.log)
|
| 197 |
+
lengthscale_constraint= Positive(transform= lambda x: 2.0**(-0.5) * torch.pow(10,-x/2),inv_transform= lambda x: -2.0*torch.log10(x/2.0))
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
if quant_correlation_class_name == 'RBFKernel':
|
| 201 |
+
|
| 202 |
+
quant_kernel.register_prior(
|
| 203 |
+
'lengthscale_prior', MollifiedUniformPrior(math.log(0.1),math.log(10)),'raw_lengthscale'
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
elif quant_correlation_class_name == 'Rough_RBF':
|
| 207 |
+
quant_kernel.register_prior(
|
| 208 |
+
'lengthscale_prior',NormalPrior(-3.0,3.0),'raw_lengthscale'
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
elif quant_correlation_class_name == 'Matern12Kernel':
|
| 212 |
+
quant_kernel.register_prior(
|
| 213 |
+
#'lengthscale_prior', MollifiedUniformPrior(math.log(0.1),math.log(10)),'raw_lengthscale'
|
| 214 |
+
'lengthscale_prior',NormalPrior(-3.0,3.0),'raw_lengthscale'
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
elif quant_correlation_class_name == 'Matern32Kernel':
|
| 218 |
+
quant_kernel.register_prior(
|
| 219 |
+
#'lengthscale_prior', MollifiedUniformPrior(math.log(0.1),math.log(10)),'raw_lengthscale'
|
| 220 |
+
'lengthscale_prior',NormalPrior(-3.0,3.0),'raw_lengthscale'
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
elif quant_correlation_class_name == 'Matern52Kernel':
|
| 224 |
+
quant_kernel.register_prior(
|
| 225 |
+
#'lengthscale_prior', MollifiedUniformPrior(math.log(0.1),math.log(10)),'raw_lengthscale'
|
| 226 |
+
'lengthscale_prior',NormalPrior(-3.0,3.0),'raw_lengthscale'
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
if len(qual_index) > 0:
|
| 230 |
+
temp = qual_kernels[0]
|
| 231 |
+
for i in range(1, len(qual_kernels)):
|
| 232 |
+
temp *= qual_kernels[i]
|
| 233 |
+
correlation_kernel = temp*quant_kernel #+ qual_kernel + quant_kernel
|
| 234 |
+
else:
|
| 235 |
+
correlation_kernel = quant_kernel
|
| 236 |
+
|
| 237 |
+
super(GPPLUS,self).__init__(
|
| 238 |
+
train_x=train_x,train_y=train_y,noise_indices=noise_indices,
|
| 239 |
+
correlation_kernel=correlation_kernel,
|
| 240 |
+
noise=noise,fix_noise=fix_noise,lb_noise=lb_noise
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
# register index and transforms
|
| 244 |
+
self.register_buffer('quant_index',torch.tensor(quant_index))
|
| 245 |
+
self.register_buffer('qual_index',torch.tensor(qual_index))
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
self.num_levels_per_var = num_levels_per_var
|
| 249 |
+
self.lv_dim = lv_dim
|
| 250 |
+
self.uniform_encoding_columns = uniform_encoding_columns
|
| 251 |
+
self.encoding_type = encoding_type
|
| 252 |
+
self.perm =[]
|
| 253 |
+
self.zeta = []
|
| 254 |
+
self.perm_dict = []
|
| 255 |
+
self.A_matrix = []
|
| 256 |
+
self.collocation_x = collocation_x ####### ADDED
|
| 257 |
+
self.alpha = 1.0
|
| 258 |
+
self.beta = 20.0
|
| 259 |
+
self.covar_inv = None
|
| 260 |
+
self.omega = 3 #3.2
|
| 261 |
+
self.name_output = name_output
|
| 262 |
+
self.chol_decomp = None
|
| 263 |
+
self.g_uvp = None
|
| 264 |
+
self.k_xX = None
|
| 265 |
+
|
| 266 |
+
if len(qual_kernel_columns) > 0:
|
| 267 |
+
for i in range(len(qual_kernel_columns)):
|
| 268 |
+
if type(qual_kernel_columns[i]) == int:
|
| 269 |
+
num = self.num_levels_per_var[qual_index.index(qual_kernel_columns[i])]
|
| 270 |
+
cat = [num]
|
| 271 |
+
else:
|
| 272 |
+
cat = [self.num_levels_per_var[qual_index.index(k)] for k in qual_kernel_columns[i]]
|
| 273 |
+
num = sum(cat)
|
| 274 |
+
|
| 275 |
+
zeta, perm, perm_dict = self.zeta_matrix(num_levels=cat, lv_dim = self.lv_dim)
|
| 276 |
+
self.zeta.append(zeta.to(**tkwargs))
|
| 277 |
+
self.perm.append(perm.to(**tkwargs))
|
| 278 |
+
self.perm_dict.append(perm_dict)
|
| 279 |
+
|
| 280 |
+
model_temp = FFNN(self, input_size= num, num_classes=lv_dim,
|
| 281 |
+
layers = NN_layers, name = str(qual_kernel_columns[i])).to(**tkwargs)
|
| 282 |
+
self.A_matrix.append(model_temp.to(**tkwargs))
|
| 283 |
+
|
| 284 |
+
self.basis=basis
|
| 285 |
+
i=0
|
| 286 |
+
if self.basis=='single':
|
| 287 |
+
self.mean_module = gpytorch.means.ConstantMean(prior=NormalPrior(0.,1.))
|
| 288 |
+
self.mean_module.constant.data = torch.tensor([0.0]) # Set the desired value
|
| 289 |
+
self.mean_module.constant.requires_grad = False # Fix the hyperparameter
|
| 290 |
+
elif self.basis=='multiple_constant':
|
| 291 |
+
if basis_function_size is None:
|
| 292 |
+
basis_function_size=train_x.shape[1]-1
|
| 293 |
+
self.num_sources=int(torch.max(train_x[:,-1]))
|
| 294 |
+
for i in range(self.num_sources +1):
|
| 295 |
+
if i==0:
|
| 296 |
+
setattr(self,'mean_module_'+str(i), gpytorch.means.ZeroMean())
|
| 297 |
+
|
| 298 |
+
else:
|
| 299 |
+
#Constant
|
| 300 |
+
setattr(self,'mean_module_'+str(i), gpytorch.means.ConstantMean(prior=NormalPrior(0.,.3)))
|
| 301 |
+
|
| 302 |
+
elif self.basis=='multiple_polynomial':
|
| 303 |
+
if basis_function_size is None:
|
| 304 |
+
basis_function_size=train_x.shape[1]-1
|
| 305 |
+
self.num_sources=int(torch.max(train_x[:,-1]))
|
| 306 |
+
for i in range(self.num_sources +1):
|
| 307 |
+
if i==0:
|
| 308 |
+
setattr(self,'mean_module_'+str(i), gpytorch.means.ZeroMean())
|
| 309 |
+
else:
|
| 310 |
+
setattr(self,'mean_module_'+str(i), LinearMean_with_prior(input_size=basis_function_size, batch_shape=torch.Size([]), bias=True))
|
| 311 |
+
elif self.basis=='neural_network':
|
| 312 |
+
############################################### One NN for ALL
|
| 313 |
+
if len(qual_index) == 0:
|
| 314 |
+
setattr(self,'mean_module_NN_All', FFNN_for_Mean(self, input_size= train_x.shape[1], num_classes=4,layers =NN_layers_base, name = str('mean_module_'+str(i)+'_')))
|
| 315 |
+
else:
|
| 316 |
+
setattr(self,'mean_module_NN_All', FFNN_for_Mean(self, input_size= train_x.shape[1]-len(qual_index)+2, num_classes=1, layers =NN_layers_base, name = str('mean_module_'+str(i)+'_')))
|
| 317 |
+
elif self.basis=='M3':
|
| 318 |
+
setattr(self,'mean_module_NN_All', NetworkM4(input_dim = train_x.shape[1], output_dim=3, layers = NN_layers_base))
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
# Fix the hyperparameter value
|
| 322 |
+
self.covar_module.base_kernel.raw_lengthscale.data = torch.tensor([self.omega, self.omega], dtype=torch.float32) # Set the desired value
|
| 323 |
+
self.covar_module.base_kernel.raw_lengthscale.requires_grad = False # Fix the hyperparameter
|
| 324 |
+
|
| 325 |
+
self.covar_module.raw_outputscale.data = torch.tensor(0.541) # Set the desired value
|
| 326 |
+
self.covar_module.raw_outputscale.requires_grad = False # Fix the hyperparameter
|
| 327 |
+
|
| 328 |
+
def forward(self,x:torch.Tensor) -> MultivariateNormal:
|
| 329 |
+
x_forward_raw=x.clone()
|
| 330 |
+
nd_flag = 0
|
| 331 |
+
if x.dim() > 2:
|
| 332 |
+
xsize = x.shape
|
| 333 |
+
x = x.reshape(-1, x.shape[-1])
|
| 334 |
+
nd_flag = 1
|
| 335 |
+
|
| 336 |
+
if len(self.qual_kernel_columns) > 0:
|
| 337 |
+
embeddings = []
|
| 338 |
+
for i in range(len(self.qual_kernel_columns)):
|
| 339 |
+
temp= self.transform_categorical(x=x[:,self.qual_kernel_columns[i]].clone().type(torch.int64),
|
| 340 |
+
perm_dict = self.perm_dict[i], zeta = self.zeta[i])
|
| 341 |
+
embeddings.append(self.A_matrix[i](temp))
|
| 342 |
+
|
| 343 |
+
x= torch.cat([embeddings[0],x[...,self.quant_index]],dim=-1)
|
| 344 |
+
|
| 345 |
+
if nd_flag == 1:
|
| 346 |
+
x = x.reshape(*xsize[:-1], -1)
|
| 347 |
+
|
| 348 |
+
#################### Multiple bases (General Case) ####################################
|
| 349 |
+
def multi_mean(x,x_forward_raw):
|
| 350 |
+
mean_x=torch.zeros_like(x[:,-1])
|
| 351 |
+
if self.basis=='single':
|
| 352 |
+
mean_x=self.mean_module(x)
|
| 353 |
+
elif self.basis=='multiple_constant':
|
| 354 |
+
for i in range(len(mean_x)):
|
| 355 |
+
qq=int(x_forward_raw[i,-1])
|
| 356 |
+
mean_x[i]=getattr(self,'mean_module_'+str(qq))(torch.tensor(x[i,-1].clone()).reshape(-1,1))
|
| 357 |
+
elif self.basis=='multiple_polynomial':
|
| 358 |
+
for i in range(len(mean_x)):
|
| 359 |
+
qq=int(x_forward_raw[i,-1])
|
| 360 |
+
mean_x[i]=getattr(self,'mean_module_'+str(qq))(torch.cat((torch.tensor((x[i,-1].clone().double().reshape(-1,1))**2),torch.tensor(x[i,-1].clone().double()).reshape(-1,1)),1))
|
| 361 |
+
|
| 362 |
+
elif self.basis=='neural_network':
|
| 363 |
+
mean_x = getattr(self,'mean_module_NN_All')(x.clone())#.reshape(-1) #### FOR MULTIOUTPUT DELETE RESHAPE
|
| 364 |
+
|
| 365 |
+
elif self.basis=='M3':
|
| 366 |
+
if hasattr(self, 'name_output'):
|
| 367 |
+
mean_x = getattr(self,'mean_module_NN_All')(x.clone())
|
| 368 |
+
else:
|
| 369 |
+
mean_x = getattr(self,'mean_module_NN_All')(x.clone()).reshape(-1)
|
| 370 |
+
|
| 371 |
+
return mean_x
|
| 372 |
+
##########################################################################################
|
| 373 |
+
if self.name_output == 'u':
|
| 374 |
+
mean_x = multi_mean(x,x_forward_raw)[:,0].reshape(-1)
|
| 375 |
+
if self.name_output == 'v':
|
| 376 |
+
mean_x = multi_mean(x,x_forward_raw)[:,1].reshape(-1)
|
| 377 |
+
if self.name_output == 'p':
|
| 378 |
+
mean_x = multi_mean(x,x_forward_raw)[:,2].reshape(-1)
|
| 379 |
+
if self.name_output == 'ro':
|
| 380 |
+
mean_x = multi_mean(x,x_forward_raw)[:,3].reshape(-1)
|
| 381 |
+
covar_x = self.covar_module(x)
|
| 382 |
+
|
| 383 |
+
return MultivariateNormal(mean_x,covar_x)
|
| 384 |
+
|
| 385 |
+
def predict(self, Xtest,return_std=True, include_noise = True):
|
| 386 |
+
with torch.no_grad():
|
| 387 |
+
return super().predict(Xtest, return_std = return_std, include_noise= include_noise)
|
| 388 |
+
|
| 389 |
+
def predict_with_grad(self, Xtest,return_std=True, include_noise = True):
|
| 390 |
+
return super().predict(Xtest, return_std = return_std, include_noise= include_noise)
|
| 391 |
+
|
| 392 |
+
def noise_value(self):
|
| 393 |
+
noise = self.likelihood.noise_covar.noise.detach() * self.y_std**2
|
| 394 |
+
return noise
|
| 395 |
+
|
| 396 |
+
def visualize_latent(self, suptitle = None):
|
| 397 |
+
if len(self.qual_kernel_columns) > 0:
|
| 398 |
+
for i in range(len(self.qual_kernel_columns)):
|
| 399 |
+
zeta = self.zeta[i]
|
| 400 |
+
A = self.A_matrix[i]
|
| 401 |
+
|
| 402 |
+
positions = A(zeta)
|
| 403 |
+
level = torch.max(self.perm[i], axis = 0)[0].tolist()
|
| 404 |
+
perm = self.perm[i]
|
| 405 |
+
plot_sep(positions = positions, levels = level, perm = perm, constraints_flag=True, )
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
def visualize_latent_position(self,lv_columns=None):
|
| 409 |
+
if len(self.qual_kernel_columns) > 0:
|
| 410 |
+
for i in range(len(self.qual_kernel_columns)):
|
| 411 |
+
zeta = self.zeta[i]
|
| 412 |
+
A = self.A_matrix[i]
|
| 413 |
+
positions = A(zeta)
|
| 414 |
+
if self.qual_kernel_columns[i]==lv_columns[0]:
|
| 415 |
+
return positions
|
| 416 |
+
|
| 417 |
+
|
| 418 |
+
def visualize_latent_position_simple(self, suptitle = None):
|
| 419 |
+
if len(self.qual_kernel_columns) > 0:
|
| 420 |
+
for i in range(len(self.qual_kernel_columns)):
|
| 421 |
+
zeta = self.zeta[i]
|
| 422 |
+
A = self.A_matrix[i]
|
| 423 |
+
positions = A(zeta)
|
| 424 |
+
return positions
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
@classmethod
|
| 428 |
+
def show(cls):
|
| 429 |
+
plt.show()
|
| 430 |
+
|
| 431 |
+
def get_params(self, name = None):
|
| 432 |
+
params = {}
|
| 433 |
+
print('###################Parameters###########################')
|
| 434 |
+
for n, value in self.named_parameters():
|
| 435 |
+
params[n] = value
|
| 436 |
+
if name is None:
|
| 437 |
+
print(params)
|
| 438 |
+
return params
|
| 439 |
+
else:
|
| 440 |
+
if name == 'Mean':
|
| 441 |
+
key = 'mean_module.constant'
|
| 442 |
+
elif name == 'Sigma':
|
| 443 |
+
key = 'covar_module.raw_outputscale'
|
| 444 |
+
elif name == 'Noise':
|
| 445 |
+
key = 'likelihood.noise_covar.raw_noise'
|
| 446 |
+
elif name == 'Omega':
|
| 447 |
+
for n in params.keys():
|
| 448 |
+
if 'raw_lengthscale' in n and params[n].numel() > 1:
|
| 449 |
+
key = n
|
| 450 |
+
print(params[key])
|
| 451 |
+
return params[key]
|
| 452 |
+
|
| 453 |
+
def get_latent_space(self):
|
| 454 |
+
if len(self.qual_index) > 0:
|
| 455 |
+
zeta = torch.tensor(self.zeta)
|
| 456 |
+
positions = self.nn_model(zeta)
|
| 457 |
+
return positions.detach()
|
| 458 |
+
else:
|
| 459 |
+
print('No categorical Variable, No latent positions')
|
| 460 |
+
return None
|
| 461 |
+
|
| 462 |
+
def zeta_matrix(self,
|
| 463 |
+
num_levels:int,
|
| 464 |
+
lv_dim:int,
|
| 465 |
+
batch_shape=torch.Size()
|
| 466 |
+
) -> None:
|
| 467 |
+
|
| 468 |
+
if any([i == 1 for i in num_levels]):
|
| 469 |
+
raise ValueError('Categorical variable has only one level!')
|
| 470 |
+
|
| 471 |
+
if lv_dim == 1:
|
| 472 |
+
raise RuntimeWarning('1D latent variables are difficult to optimize!')
|
| 473 |
+
|
| 474 |
+
for level in num_levels:
|
| 475 |
+
if lv_dim > level - 0:
|
| 476 |
+
lv_dim = min(lv_dim, level-1)
|
| 477 |
+
raise RuntimeWarning(
|
| 478 |
+
'The LV dimension can atmost be num_levels-1. '
|
| 479 |
+
'Setting it to %s in place of %s' %(level-1,lv_dim)
|
| 480 |
+
)
|
| 481 |
+
|
| 482 |
+
from itertools import product
|
| 483 |
+
levels = []
|
| 484 |
+
for l in num_levels:
|
| 485 |
+
levels.append(torch.arange(l))
|
| 486 |
+
|
| 487 |
+
perm = list(product(*levels))
|
| 488 |
+
perm = torch.tensor(perm, dtype=torch.int64)
|
| 489 |
+
|
| 490 |
+
#-------------Mapping-------------------------
|
| 491 |
+
perm_dic = {}
|
| 492 |
+
for i, row in enumerate(perm):
|
| 493 |
+
temp = str(row.tolist())
|
| 494 |
+
if temp not in perm_dic.keys():
|
| 495 |
+
perm_dic[temp] = i
|
| 496 |
+
|
| 497 |
+
#-------------One_hot_encoding------------------
|
| 498 |
+
for ii in range(perm.shape[-1]):
|
| 499 |
+
if perm[...,ii].min() != 0:
|
| 500 |
+
perm[...,ii] -= perm[...,ii].min()
|
| 501 |
+
|
| 502 |
+
perm_one_hot = []
|
| 503 |
+
for i in range(perm.size()[1]):
|
| 504 |
+
perm_one_hot.append( torch.nn.functional.one_hot(perm[:,i]) )
|
| 505 |
+
|
| 506 |
+
perm_one_hot = torch.concat(perm_one_hot, axis=1)
|
| 507 |
+
|
| 508 |
+
return perm_one_hot, perm, perm_dic
|
| 509 |
+
|
| 510 |
+
|
| 511 |
+
def transform_categorical(self, x:torch.Tensor,perm_dict = [], zeta = []) -> None:
|
| 512 |
+
if x.dim() == 1:
|
| 513 |
+
x = x.reshape(-1,1)
|
| 514 |
+
# categorical should start from 0
|
| 515 |
+
if self.training == False:
|
| 516 |
+
x = setlevels(x.cpu())
|
| 517 |
+
if self.encoding_type == 'one-hot':
|
| 518 |
+
index = [perm_dict[str(row.tolist())] for row in x]
|
| 519 |
+
|
| 520 |
+
if x.dim() == 1:
|
| 521 |
+
x = x.reshape(len(x),)
|
| 522 |
+
|
| 523 |
+
return zeta[index,:]
|
| 524 |
+
|
| 525 |
+
elif self.encoding_type == 'uniform':
|
| 526 |
+
|
| 527 |
+
temp2=np.random.uniform(0,1,(len(self.perm), self.uniform_encoding_columns))
|
| 528 |
+
dict={}
|
| 529 |
+
dict2={}
|
| 530 |
+
|
| 531 |
+
for i in range(0,self.perm.shape[0]):
|
| 532 |
+
dict[tuple((self.perm[i,:]).numpy())]=temp2[i,:]
|
| 533 |
+
|
| 534 |
+
for i in range(0,x.shape[0]):
|
| 535 |
+
dict2[i]=dict[tuple((x[i]).numpy())]
|
| 536 |
+
|
| 537 |
+
x_one_hot= torch.from_numpy(np.array(list(dict2.values())))
|
| 538 |
+
else:
|
| 539 |
+
raise ValueError ('Invalid type')
|
| 540 |
+
|
| 541 |
+
|
| 542 |
+
return x_one_hot
|
| 543 |
+
|
| 544 |
+
|
| 545 |
+
##########################################################################################################################################################
|
| 546 |
+
|
| 547 |
+
class LinearMean_with_prior(gpytorch.means.Mean):
|
| 548 |
+
def __init__(self, input_size, batch_shape=torch.Size(), bias=True):
|
| 549 |
+
super().__init__()
|
| 550 |
+
self.register_parameter(name="weights", parameter=torch.nn.Parameter(torch.randn(*batch_shape, input_size, 1)))
|
| 551 |
+
self.register_prior(name = 'weights_prior', prior=gpytorch.priors.NormalPrior(0.,1.), param_or_closure='weights')
|
| 552 |
+
|
| 553 |
+
if bias:
|
| 554 |
+
self.register_parameter(name="bias", parameter=torch.nn.Parameter(torch.randn(*batch_shape, 1)))
|
| 555 |
+
self.register_prior(name = 'bias_prior', prior=gpytorch.priors.NormalPrior(0.,1.), param_or_closure='bias')
|
| 556 |
+
|
| 557 |
+
else:
|
| 558 |
+
self.bias = None
|
| 559 |
+
|
| 560 |
+
def forward(self, x):
|
| 561 |
+
res = x.matmul(self.weights).squeeze(-1)
|
| 562 |
+
if self.bias is not None:
|
| 563 |
+
res = res + self.bias
|
| 564 |
+
return res
|
| 565 |
+
|
| 566 |
+
|
| 567 |
+
###############################################################################################################################################################################################################################################################
|
| 568 |
+
class FFNN(torch.nn.Module):
|
| 569 |
+
def __init__(self, lmgp, input_size, num_classes, layers,name):
|
| 570 |
+
super(FFNN, self).__init__()
|
| 571 |
+
# Our first linear layer take input_size, in this case 784 nodes to 50
|
| 572 |
+
# and our second linear layer takes 50 to the num_classes we have, in
|
| 573 |
+
# this case 10.
|
| 574 |
+
self.hidden_num = len(layers)
|
| 575 |
+
if self.hidden_num > 0:
|
| 576 |
+
self.fci = torch.nn.Linear(input_size, layers[0], bias=False)
|
| 577 |
+
lmgp.register_parameter('fci', self.fci.weight)
|
| 578 |
+
lmgp.register_prior(name = 'latent_prior_fci', prior=gpytorch.priors.NormalPrior(0.,3.), param_or_closure='fci')
|
| 579 |
+
|
| 580 |
+
for i in range(1,self.hidden_num):
|
| 581 |
+
#self.h = nn.Linear(neuran[i-1], neuran[i])
|
| 582 |
+
setattr(self, 'h' + str(i), torch.nn.Linear(layers[i-1], layers[i], bias=False))
|
| 583 |
+
lmgp.register_parameter('h'+str(i), getattr(self, 'h' + str(i)).weight )
|
| 584 |
+
lmgp.register_prior(name = 'latent_prior'+str(i), prior=gpytorch.priors.NormalPrior(0.,3.), param_or_closure='h'+str(i))
|
| 585 |
+
|
| 586 |
+
self.fce = torch.nn.Linear(layers[-1], num_classes, bias= False)
|
| 587 |
+
lmgp.register_parameter('fce', self.fce.weight)
|
| 588 |
+
lmgp.register_prior(name = 'latent_prior_fce', prior=gpytorch.priors.NormalPrior(0.,3.), param_or_closure='fce')
|
| 589 |
+
else:
|
| 590 |
+
self.fci = Linear_MAP(input_size, num_classes, bias = False)
|
| 591 |
+
lmgp.register_parameter(name, self.fci.weight)
|
| 592 |
+
lmgp.register_prior(name = 'latent_prior_'+name, prior=gpytorch.priors.NormalPrior(0,3) , param_or_closure=name)
|
| 593 |
+
|
| 594 |
+
|
| 595 |
+
|
| 596 |
+
def forward(self, x, transform = lambda x: x):
|
| 597 |
+
"""
|
| 598 |
+
x here is the mnist images and we run it through fc1, fc2 that we created above.
|
| 599 |
+
we also add a ReLU activation function in between and for that (since it has no parameters)
|
| 600 |
+
I recommend using nn.functional (F)
|
| 601 |
+
"""
|
| 602 |
+
if self.hidden_num > 0:
|
| 603 |
+
x = torch.tanh(self.fci(x))
|
| 604 |
+
for i in range(1,self.hidden_num):
|
| 605 |
+
x = torch.tanh( getattr(self, 'h' + str(i))(x) )
|
| 606 |
+
|
| 607 |
+
x = self.fce(x)
|
| 608 |
+
else:
|
| 609 |
+
x = self.fci(x, transform)
|
| 610 |
+
return x
|
| 611 |
+
|
| 612 |
+
class FFNN_for_Mean(gpytorch.Module):
|
| 613 |
+
def __init__(self, lmgp, input_size, num_classes, layers, name):
|
| 614 |
+
super(FFNN_for_Mean, self).__init__()
|
| 615 |
+
self.dropout = torch.nn.Dropout(0.0)
|
| 616 |
+
# Our first linear layer take input_size, in this case 784 nodes to 50
|
| 617 |
+
# and our second linear layer takes 50 to the num_classes we have, in
|
| 618 |
+
# this case 10.
|
| 619 |
+
self.hidden_num = len(layers)
|
| 620 |
+
if self.hidden_num > 0:
|
| 621 |
+
self.fci = Linear_new(input_size, layers[0], bias=True, name='fci')
|
| 622 |
+
for i in range(1,self.hidden_num):
|
| 623 |
+
setattr(self, 'h' + str(i), Linear_new(layers[i-1], layers[i], bias=True,name='h' + str(i)))
|
| 624 |
+
|
| 625 |
+
self.fce = Linear_new(layers[-1], num_classes, bias=True,name='fce')
|
| 626 |
+
else:
|
| 627 |
+
self.fci = Linear_new(input_size, num_classes, bias=True,name='fci') #Linear_MAP(input_size, num_classes, bias = True)
|
| 628 |
+
|
| 629 |
+
def forward(self, x, transform = lambda x: x):
|
| 630 |
+
"""
|
| 631 |
+
x here is the mnist images and we run it through fc1, fc2 that we created above.
|
| 632 |
+
we also add a ReLU activation function in between and for that (since it has no parameters)
|
| 633 |
+
I recommend using nn.functional (F)
|
| 634 |
+
"""
|
| 635 |
+
if self.hidden_num > 0:
|
| 636 |
+
|
| 637 |
+
x = torch.tanh(self.fci(x))
|
| 638 |
+
# x = self.dropout(x)
|
| 639 |
+
# x = self.fci(x)
|
| 640 |
+
for i in range(1,self.hidden_num):
|
| 641 |
+
x = torch.tanh( getattr(self, 'h' + str(i))(x) )
|
| 642 |
+
|
| 643 |
+
x = self.fce(x)
|
| 644 |
+
#x = torch.cat([x[...,:3] , torch.sigmoid(x[...,3]).unsqueeze(-1)] , dim = -1)
|
| 645 |
+
else:
|
| 646 |
+
x = self.fci(x)
|
| 647 |
+
#x = torch.cat([x[...,:3] , torch.tanh(x[...,3]).unsqueeze(-1)] , dim = -1)
|
| 648 |
+
|
| 649 |
+
return x
|
| 650 |
+
|
| 651 |
+
|
| 652 |
+
|
| 653 |
+
class NetworkM4(torch.nn.Module):
|
| 654 |
+
def __init__(self, input_dim = 2, output_dim = 1, layers = [40, 40, 40, 40], activation = 'tanh', collocation_x = []) -> None:
|
| 655 |
+
super(NetworkM4, self).__init__()
|
| 656 |
+
activation_list = {'tanh':torch.nn.Tanh(), 'Silu':torch.nn.SiLU(), 'Sigmoid':torch.nn.Sigmoid()}
|
| 657 |
+
activation = activation_list[activation]
|
| 658 |
+
self.dim = layers[0]
|
| 659 |
+
|
| 660 |
+
self.U = torch.nn.Linear(input_dim, self.dim).to('cuda')
|
| 661 |
+
self.V = torch.nn.Linear(input_dim, self.dim).to('cuda')
|
| 662 |
+
self.H1 = torch.nn.Linear(input_dim, self.dim).to('cuda')
|
| 663 |
+
self.last= torch.nn.Linear(self.dim, output_dim).to('cuda')
|
| 664 |
+
|
| 665 |
+
self.collocation_x = collocation_x
|
| 666 |
+
self.alpha = 1.0
|
| 667 |
+
self.beta = 1.0
|
| 668 |
+
|
| 669 |
+
l = torch.nn.ModuleList()
|
| 670 |
+
for _ in range(len(layers)):
|
| 671 |
+
l.append(torch.nn.Linear(self.dim, self.dim))
|
| 672 |
+
l.append(activation)
|
| 673 |
+
self.layers = torch.nn.Sequential(*l).to('cuda')
|
| 674 |
+
|
| 675 |
+
def forward(self, input):
|
| 676 |
+
U = torch.nn.Tanh()(self.U(input))
|
| 677 |
+
V = torch.nn.Tanh()(self.V(input))
|
| 678 |
+
H = torch.nn.Tanh()(self.H1(input))
|
| 679 |
+
|
| 680 |
+
for layer in self.layers:
|
| 681 |
+
Z = layer(H)
|
| 682 |
+
H = (1-Z)*U + Z*V
|
| 683 |
+
|
| 684 |
+
out = self.last(H)
|
| 685 |
+
return out
|
| 686 |
+
|
| 687 |
+
class Linear_new(gpytorch.means.Mean):
|
| 688 |
+
r"""Applies a linear transformation to the incoming data: :math:`y = xA^T + b`
|
| 689 |
+
|
| 690 |
+
This module supports :ref:`TensorFloat32<tf32_on_ampere>`.
|
| 691 |
+
|
| 692 |
+
On certain ROCm devices, when using float16 inputs this module will use :ref:`different precision<fp16_on_mi200>` for backward.
|
| 693 |
+
|
| 694 |
+
Args:
|
| 695 |
+
in_features: size of each input sample
|
| 696 |
+
out_features: size of each output sample
|
| 697 |
+
bias: If set to ``False``, the layer will not learn an additive bias.
|
| 698 |
+
Default: ``True``
|
| 699 |
+
|
| 700 |
+
Shape:
|
| 701 |
+
- Input: :math:`(*, H_{in})` where :math:`*` means any number of
|
| 702 |
+
dimensions including none and :math:`H_{in} = \text{in\_features}`.
|
| 703 |
+
- Output: :math:`(*, H_{out})` where all but the last dimension
|
| 704 |
+
are the same shape as the input and :math:`H_{out} = \text{out\_features}`.
|
| 705 |
+
|
| 706 |
+
Attributes:
|
| 707 |
+
weight: the learnable weights of the module of shape
|
| 708 |
+
:math:`(\text{out\_features}, \text{in\_features})`. The values are
|
| 709 |
+
initialized from :math:`\mathcal{U}(-\sqrt{k}, \sqrt{k})`, where
|
| 710 |
+
:math:`k = \frac{1}{\text{in\_features}}`
|
| 711 |
+
bias: the learnable bias of the module of shape :math:`(\text{out\_features})`.
|
| 712 |
+
If :attr:`bias` is ``True``, the values are initialized from
|
| 713 |
+
:math:`\mathcal{U}(-\sqrt{k}, \sqrt{k})` where
|
| 714 |
+
:math:`k = \frac{1}{\text{in\_features}}`
|
| 715 |
+
|
| 716 |
+
Examples::
|
| 717 |
+
|
| 718 |
+
>>> m = nn.Linear(20, 30)
|
| 719 |
+
>>> input = torch.randn(128, 20)
|
| 720 |
+
>>> output = m(input)
|
| 721 |
+
>>> print(output.size())
|
| 722 |
+
torch.Size([128, 30])
|
| 723 |
+
"""
|
| 724 |
+
__constants__ = ['in_features', 'out_features']
|
| 725 |
+
in_features: int
|
| 726 |
+
out_features: int
|
| 727 |
+
weight: torch.Tensor
|
| 728 |
+
|
| 729 |
+
def __init__(self, in_features: int, out_features: int, bias: bool = True, name=None,
|
| 730 |
+
device=None, dtype=None) -> None:
|
| 731 |
+
# factory_kwargs = {'device': device, 'dtype': dtype}
|
| 732 |
+
# factory_kwargs=tkwargs
|
| 733 |
+
super(Linear_new, self).__init__()
|
| 734 |
+
self.in_features = in_features
|
| 735 |
+
self.out_features = out_features
|
| 736 |
+
|
| 737 |
+
self.name=str(name)
|
| 738 |
+
self.register_parameter(name=str(self.name)+'weight', parameter= Parameter(torch.empty((out_features, in_features))))
|
| 739 |
+
self.register_prior(name =str(self.name)+ 'prior_m_weight_fci', prior=gpytorch.priors.NormalPrior(0.,1.), param_or_closure=str(self.name)+'weight')
|
| 740 |
+
|
| 741 |
+
if bias:
|
| 742 |
+
self.register_parameter(name=str(self.name)+'bias', parameter=Parameter(torch.empty(out_features)))
|
| 743 |
+
self.register_prior(name= str(self.name)+'prior_m_bias_fci', prior=gpytorch.priors.NormalPrior(0.,1.), param_or_closure=str(self.name)+'bias')
|
| 744 |
+
else:
|
| 745 |
+
self.register_parameter('bias', None)
|
| 746 |
+
self.reset_parameters()
|
| 747 |
+
|
| 748 |
+
def reset_parameters(self) -> None:
|
| 749 |
+
# Setting a=sqrt(5) in kaiming_uniform is the same as initializing with
|
| 750 |
+
# uniform(-1/sqrt(in_features), 1/sqrt(in_features)). For details, see
|
| 751 |
+
# https://github.com/pytorch/pytorch/issues/57109
|
| 752 |
+
torch.nn.init.kaiming_uniform_( getattr(self,str(self.name)+'weight'), a=math.sqrt(5))
|
| 753 |
+
if getattr(self,str(self.name)+'bias') is not None:
|
| 754 |
+
fan_in, _ = torch.nn.init._calculate_fan_in_and_fan_out(getattr(self,str(self.name)+'weight'))
|
| 755 |
+
bound = 1 / math.sqrt(fan_in) if fan_in > 0 else 0
|
| 756 |
+
torch.nn.init.uniform_(getattr(self,str(self.name)+'bias'), -bound, bound)
|
| 757 |
+
|
| 758 |
+
def forward(self, input) -> torch.Tensor:
|
| 759 |
+
# return F.linear(input, self.weight, self.bias)
|
| 760 |
+
# print(getattr(self,str(self.name)+'weight'))
|
| 761 |
+
|
| 762 |
+
# return F.linear(input, getattr(self,str(self.name)+'weight').double(), getattr(self,str(self.name)+'bias').double()) ### Forced to Add .double() for NN in mean function
|
| 763 |
+
return F.linear(input, getattr(self,str(self.name)+'weight'), getattr(self,str(self.name)+'bias')) ### Forced to Add .double() for NN in mean function
|
| 764 |
+
|
| 765 |
+
def extra_repr(self) -> str:
|
| 766 |
+
return 'in_features={}, out_features={}, bias={}'.format(
|
| 767 |
+
self.in_features, self.out_features, self.bias is not None
|
| 768 |
+
)
|
| 769 |
+
|
| 770 |
+
class Linear_MAP(torch.nn.Linear):
|
| 771 |
+
def __init__(self, in_features: int, out_features: int, bias: bool = True, device=None, dtype=None) -> None:
|
| 772 |
+
super().__init__(in_features, out_features, bias, device, dtype)
|
| 773 |
+
|
| 774 |
+
def forward(self, input, transform = lambda x: x):
|
| 775 |
+
return F.linear(input,transform(self.weight), self.bias)
|
| 776 |
+
|
| 777 |
+
|
| 778 |
+
|
model/gpregression.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import gpytorch
|
| 3 |
+
import math
|
| 4 |
+
from gpytorch.models import ExactGP
|
| 5 |
+
from gpytorch import settings as gptsettings
|
| 6 |
+
from gpytorch.constraints import GreaterThan,Positive
|
| 7 |
+
from gpytorch.distributions import MultivariateNormal
|
| 8 |
+
import gpytorch.kernels as kernels
|
| 9 |
+
from .horseshoe import LogHalfHorseshoePrior
|
| 10 |
+
from .mollified_uniform import MollifiedUniformPrior
|
| 11 |
+
from gpytorch.priors import NormalPrior,LogNormalPrior
|
| 12 |
+
from onescience.utils.GP_TO.transforms import softplus,inv_softplus
|
| 13 |
+
|
| 14 |
+
from typing import List,Tuple,Union
|
| 15 |
+
|
| 16 |
+
from typing import List, Union
|
| 17 |
+
|
| 18 |
+
class GPR(ExactGP):
|
| 19 |
+
"""Standard GP regression module for numerical inputs
|
| 20 |
+
|
| 21 |
+
:param train_x: The training inputs (size N x d). All input variables are expected
|
| 22 |
+
to be numerical. For best performance, scale the variables to the unit hypercube.
|
| 23 |
+
:type train_x: torch.Tensor
|
| 24 |
+
:param train_y: The training targets (size N)
|
| 25 |
+
:type train_y: torch.Tensor
|
| 26 |
+
:param correlation_kernel: Either a `gpytorch.kernels.Kernel` instance or one of the
|
| 27 |
+
following strings - 'RBFKernel' (radial basis kernel), 'Matern52Kernel' (twice
|
| 28 |
+
differentiable Matern kernel), 'Matern32Kernel' (first order differentiable Matern
|
| 29 |
+
kernel). If the former is specified, any hyperparameters to be estimated need to have
|
| 30 |
+
associated priors for multi-start optimization. If the latter is specified, then
|
| 31 |
+
the kernel uses a separate lengthscale for each input variable.
|
| 32 |
+
:type correlation_kernel: Union[gpytorch.kernels.Kernel,str]
|
| 33 |
+
:param noise: The (initial) noise variance.
|
| 34 |
+
:type noise: float, optional
|
| 35 |
+
:param fix_noise: Fixes the noise variance at the current level if `True` is specifed.
|
| 36 |
+
Defaults to `False`
|
| 37 |
+
:type fix_noise: bool, optional
|
| 38 |
+
:param lb_noise: Lower bound on the noise variance. Setting a higher value results in
|
| 39 |
+
more stable computations, when optimizing noise variance, but might reduce
|
| 40 |
+
prediction quality. Defaults to 1e-6
|
| 41 |
+
:type lb_noise: float, optional
|
| 42 |
+
"""
|
| 43 |
+
def __init__(
|
| 44 |
+
self,
|
| 45 |
+
train_x:torch.Tensor,
|
| 46 |
+
train_y:torch.Tensor,
|
| 47 |
+
correlation_kernel,
|
| 48 |
+
noise_indices:List[int],
|
| 49 |
+
noise:float=1e-4,
|
| 50 |
+
fix_noise:bool=False,
|
| 51 |
+
lb_noise:float=1e-12,
|
| 52 |
+
) -> None:
|
| 53 |
+
# check inputs
|
| 54 |
+
if not torch.is_tensor(train_x):
|
| 55 |
+
raise RuntimeError("'train_x' must be a tensor")
|
| 56 |
+
if not torch.is_tensor(train_y):
|
| 57 |
+
raise RuntimeError("'train_y' must be a tensor")
|
| 58 |
+
|
| 59 |
+
if train_x.shape[0] != train_y.shape[0]:
|
| 60 |
+
raise RuntimeError("Inputs and output have different number of observations")
|
| 61 |
+
|
| 62 |
+
# initializing likelihood
|
| 63 |
+
noise_constraint=GreaterThan(lb_noise,transform=torch.exp,inv_transform=torch.log)
|
| 64 |
+
|
| 65 |
+
if len(noise_indices) == 0:
|
| 66 |
+
likelihood = gpytorch.likelihoods.GaussianLikelihood(noise_constraint=noise_constraint)
|
| 67 |
+
|
| 68 |
+
y_mean = torch.tensor(0.0)
|
| 69 |
+
y_std = torch.tensor(1.0)
|
| 70 |
+
train_y_sc = (train_y-y_mean)/y_std
|
| 71 |
+
|
| 72 |
+
ExactGP.__init__(self, train_x,train_y_sc, likelihood)
|
| 73 |
+
|
| 74 |
+
# registering mean and std of the raw response
|
| 75 |
+
self.register_buffer('y_mean',y_mean)
|
| 76 |
+
self.register_buffer('y_std',y_std)
|
| 77 |
+
self.register_buffer('y_scaled',train_y_sc)
|
| 78 |
+
|
| 79 |
+
self._num_outputs = 1
|
| 80 |
+
|
| 81 |
+
# initializing and fixing noise
|
| 82 |
+
if noise is not None:
|
| 83 |
+
self.likelihood.initialize(noise=noise)
|
| 84 |
+
|
| 85 |
+
self.likelihood.register_prior('noise_prior',LogHalfHorseshoePrior(0.01,lb_noise),'raw_noise')
|
| 86 |
+
if fix_noise:
|
| 87 |
+
self.likelihood.raw_noise.requires_grad_(False)
|
| 88 |
+
|
| 89 |
+
if isinstance(correlation_kernel,str):
|
| 90 |
+
try:
|
| 91 |
+
correlation_kernel_class = getattr(kernels,correlation_kernel)
|
| 92 |
+
correlation_kernel = correlation_kernel_class(
|
| 93 |
+
ard_num_dims = self.train_inputs[0].size(1),
|
| 94 |
+
lengthscale_constraint=Positive(transform=torch.exp,inv_transform=torch.log),
|
| 95 |
+
)
|
| 96 |
+
correlation_kernel.register_prior(
|
| 97 |
+
'lengthscale_prior',MollifiedUniformPrior(math.log(0.1),math.log(10)),'raw_lengthscale'
|
| 98 |
+
)
|
| 99 |
+
except:
|
| 100 |
+
raise RuntimeError(
|
| 101 |
+
"%s not an allowed kernel" % correlation_kernel
|
| 102 |
+
)
|
| 103 |
+
elif not isinstance(correlation_kernel,gpytorch.kernels.Kernel):
|
| 104 |
+
raise RuntimeError(
|
| 105 |
+
"specified correlation kernel is not a `gpytorch.kernels.Kernel` instance"
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
self.covar_module = kernels.ScaleKernel(
|
| 109 |
+
base_kernel = correlation_kernel,
|
| 110 |
+
outputscale_constraint=Positive(transform=softplus,inv_transform=inv_softplus),
|
| 111 |
+
)
|
| 112 |
+
# register priors
|
| 113 |
+
self.covar_module.register_prior(
|
| 114 |
+
'outputscale_prior',LogNormalPrior(1e-6,1.),'outputscale'
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def forward(self,x:torch.Tensor)->MultivariateNormal:
|
| 120 |
+
mean_x = self.mean_module(x)
|
| 121 |
+
covar_x = self.covar_module(x)
|
| 122 |
+
return MultivariateNormal(mean_x,covar_x)
|
| 123 |
+
|
| 124 |
+
def predict(
|
| 125 |
+
self,x:torch.Tensor,return_std:bool=False,include_noise:bool=False
|
| 126 |
+
)-> Union[torch.Tensor,Tuple[torch.Tensor]]:
|
| 127 |
+
"""Returns the predictive mean, and optionally the standard deviation at the given points
|
| 128 |
+
|
| 129 |
+
:param x: The input variables at which the predictions are sought.
|
| 130 |
+
:type x: torch.Tensor
|
| 131 |
+
:param return_std: Standard deviation is returned along the predictions if `True`.
|
| 132 |
+
Defaults to `False`.
|
| 133 |
+
:type return_std: bool, optional
|
| 134 |
+
:param include_noise: Noise variance is included in the standard deviation if `True`.
|
| 135 |
+
Defaults to `False`.
|
| 136 |
+
:type include_noise: bool
|
| 137 |
+
"""
|
| 138 |
+
self.eval()
|
| 139 |
+
with gptsettings.fast_computations(log_prob=False):
|
| 140 |
+
# determine if batched or not
|
| 141 |
+
ndim = self.train_targets.ndim
|
| 142 |
+
if ndim == 1:
|
| 143 |
+
output = self(x)
|
| 144 |
+
else:
|
| 145 |
+
# for batched GPs
|
| 146 |
+
num_samples = self.train_targets.shape[0]
|
| 147 |
+
output = self(x.unsqueeze(0).repeat(num_samples,1,1))
|
| 148 |
+
|
| 149 |
+
if return_std and include_noise:
|
| 150 |
+
output = self.likelihood(output)
|
| 151 |
+
|
| 152 |
+
out_mean = self.y_mean + self.y_std*output.mean
|
| 153 |
+
|
| 154 |
+
# standard deviation may not always be needed
|
| 155 |
+
if return_std:
|
| 156 |
+
out_std = output.variance.sqrt()*self.y_std
|
| 157 |
+
return out_mean,out_std
|
| 158 |
+
|
| 159 |
+
return out_mean
|
model/horseshoe.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import torch
|
| 3 |
+
from gpytorch.priors import Prior
|
| 4 |
+
from torch.distributions import HalfCauchy,HalfNormal,constraints
|
| 5 |
+
from torch.distributions.utils import broadcast_all
|
| 6 |
+
from numbers import Number
|
| 7 |
+
|
| 8 |
+
class LogHalfHorseshoePrior(Prior):
|
| 9 |
+
"""Prior for the log-noise variance hyperparameter for GPs.
|
| 10 |
+
|
| 11 |
+
This is parameterized by `scale` and `lb`. `lb` is the lower bound on the noise variance.
|
| 12 |
+
The `scale` parameter is more important. The default value for `scale` - 0.01 - works well
|
| 13 |
+
for deterministic and low-noise situations. A larger value may be need in noisy sitations
|
| 14 |
+
and small training datasets. A larger scale implies more noisy data as the prior.
|
| 15 |
+
|
| 16 |
+
To change the scale for this prior for a model to say 0.1,
|
| 17 |
+
>>> model.likelihood.register(
|
| 18 |
+
>>> 'noise_prior',LogHalfHorseshoePrior(0.1,model.likelihood.noise_prior.lb),
|
| 19 |
+
>>> 'raw_noise'
|
| 20 |
+
>>> )
|
| 21 |
+
|
| 22 |
+
.. note::
|
| 23 |
+
The `log_prob` method is only approximate and unnormalized. There is no closed form
|
| 24 |
+
expression for the underlying horseshoe distribution. The lower and upper bounds on
|
| 25 |
+
its' density are, however, known. Here, we use the same approximate density value that
|
| 26 |
+
the spearmint package uses.
|
| 27 |
+
|
| 28 |
+
:param scale: scale parameter of the Horseshoe distribution
|
| 29 |
+
:type scale: float or torch.Tensor
|
| 30 |
+
|
| 31 |
+
:param lb: lower bound on the original scale. Defaults to 1e-6
|
| 32 |
+
:type lb: float or torch.Tensor, optional
|
| 33 |
+
"""
|
| 34 |
+
arg_constraints = {"scale": constraints.positive,"lb":constraints.positive}
|
| 35 |
+
support = constraints.real
|
| 36 |
+
def __init__(self, scale, lb=1e-6,validate_args=None):
|
| 37 |
+
self.scale,self.lb = broadcast_all(scale,lb)
|
| 38 |
+
if isinstance(scale,Number):
|
| 39 |
+
batch_shape = torch.Size()
|
| 40 |
+
else:
|
| 41 |
+
batch_shape = self.scale.size()
|
| 42 |
+
super().__init__(batch_shape,validate_args=validate_args)
|
| 43 |
+
|
| 44 |
+
def transform(self, x):
|
| 45 |
+
return self.lb + torch.exp(x)
|
| 46 |
+
|
| 47 |
+
def log_prob(self, X):
|
| 48 |
+
# first term is the density in the original scale
|
| 49 |
+
# the second term is for the transformation
|
| 50 |
+
return torch.log(torch.log(1+3*(self.scale / self.transform(X)) ** 2))+ X
|
| 51 |
+
|
| 52 |
+
def rsample(self, sample_shape=torch.Size([])):
|
| 53 |
+
local_shrinkage = HalfCauchy(1).rsample(self.scale.shape).to(self.lb)
|
| 54 |
+
param_sample = HalfNormal(local_shrinkage * self.scale).rsample(sample_shape).to(self.lb)
|
| 55 |
+
if len(self.lb) > 1:
|
| 56 |
+
param_sample[param_sample<self.lb[0]] = self.lb[0]
|
| 57 |
+
else:
|
| 58 |
+
param_sample[param_sample<self.lb] = self.lb
|
| 59 |
+
return param_sample.log()
|
| 60 |
+
|
| 61 |
+
def expand(self,expand_shape, _instance=None):
|
| 62 |
+
batch_shape = torch.Size(expand_shape)
|
| 63 |
+
return LogHalfHorseshoePrior(self.scale.expand(batch_shape))
|
model/mollified_uniform.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import torch
|
| 3 |
+
from gpytorch.priors import Prior
|
| 4 |
+
from torch.distributions import constraints,Uniform,Normal
|
| 5 |
+
from torch.distributions.utils import broadcast_all
|
| 6 |
+
from numbers import Number
|
| 7 |
+
|
| 8 |
+
class MollifiedUniformPrior(Prior):
|
| 9 |
+
r"""Uniform distribution that is differentiable everywhere
|
| 10 |
+
|
| 11 |
+
This is an approximation to the Uniform distribution which maintains differentiability by placing a
|
| 12 |
+
Gaussian distribution over points away from the original support. The density for a single dimension is
|
| 13 |
+
|
| 14 |
+
.. math::
|
| 15 |
+
|
| 16 |
+
p(x) &= \frac{M}{\sqrt{2\pi\sigma^2}}\exp\left[-\frac{d(x)^2}{2\sigma^2}\right], \\
|
| 17 |
+
d(x) &= \begin{cases}
|
| 18 |
+
a-x & x < a \\
|
| 19 |
+
0 & a\leq x < b\\
|
| 20 |
+
x-b & x \geq b
|
| 21 |
+
\end{cases}
|
| 22 |
+
|
| 23 |
+
:param a: lower range (inclusive)
|
| 24 |
+
:type a: float or torch.Tensor
|
| 25 |
+
|
| 26 |
+
:param b: upper range (exclusive)
|
| 27 |
+
:type b: float or torch.Tensor
|
| 28 |
+
|
| 29 |
+
:param tail_sigma: Standard deviation of the Gaussian distributions on the tails. Lower values make the
|
| 30 |
+
approximation closer to Uniform, but may increase the optimization effort. Defaults to 0.1
|
| 31 |
+
:type tail_sigma: float or torch.Tensor, optional
|
| 32 |
+
|
| 33 |
+
.. note::
|
| 34 |
+
The `rsample` method for this distribution returns uniformly distributed samples from the interval `[a,b)`,
|
| 35 |
+
and **not** from the Mollified distribution. The `log_prob` method, however, returns the correct probability.
|
| 36 |
+
The purpose of the priors in this package is to generate initial starting points for optimizing
|
| 37 |
+
hyperparameters and for MAP estimation.
|
| 38 |
+
"""
|
| 39 |
+
arg_constraints = {'a':constraints.real,'b':constraints.real,'tail_sigma':constraints.positive}
|
| 40 |
+
support = constraints.real
|
| 41 |
+
def __init__(self,a,b,tail_sigma=0.1):
|
| 42 |
+
self.a,self.b,self.tail_sigma = broadcast_all(a,b,tail_sigma)
|
| 43 |
+
|
| 44 |
+
if isinstance(a,Number) or isinstance(b,Number):
|
| 45 |
+
batch_shape = torch.Size()
|
| 46 |
+
else:
|
| 47 |
+
batch_shape = self.a.size()
|
| 48 |
+
|
| 49 |
+
super().__init__(batch_shape)
|
| 50 |
+
|
| 51 |
+
@property
|
| 52 |
+
def mean(self):
|
| 53 |
+
return (self.a+self.b)/2
|
| 54 |
+
|
| 55 |
+
@property
|
| 56 |
+
def _half_range(self):
|
| 57 |
+
return (self.b-self.a)/2
|
| 58 |
+
|
| 59 |
+
@property
|
| 60 |
+
def _log_normalization_constant(self):
|
| 61 |
+
return -torch.log(1+(self.b-self.a)/(math.sqrt(2*math.pi)*self.tail_sigma))
|
| 62 |
+
|
| 63 |
+
def log_prob(self,X):
|
| 64 |
+
# expression preserving gradients under automatic differentiation
|
| 65 |
+
tail_dist = ((X-self.mean).abs()-self._half_range).clamp(min=0)
|
| 66 |
+
return Normal(loc=torch.zeros_like(self.a),scale=self.tail_sigma).log_prob(tail_dist)+self._log_normalization_constant
|
| 67 |
+
|
| 68 |
+
def rsample(self,sample_shape=torch.Size([])):
|
| 69 |
+
return Uniform(self.a,self.b).rsample(sample_shape).to(self.a)
|
| 70 |
+
|
| 71 |
+
def expand(self,expand_shape):
|
| 72 |
+
batch_shape = torch.Size(expand_shape)
|
| 73 |
+
return MollifiedUniformPrior(
|
| 74 |
+
self.a.expand(batch_shape),
|
| 75 |
+
self.b.expand(batch_shape),
|
| 76 |
+
self.tail_sigma.expand(batch_shape)
|
| 77 |
+
)
|
scripts/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
scripts/common.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import yaml
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 11 |
+
CONFIG_PATH = PROJECT_ROOT / "conf" / "config.yaml"
|
| 12 |
+
PROBLEMS = ("doublepipe", "diffuser", "rugby", "pipebend")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def ensure_onescience_path(explicit_src=None):
|
| 16 |
+
candidates = []
|
| 17 |
+
if explicit_src:
|
| 18 |
+
candidates.append(Path(explicit_src).expanduser())
|
| 19 |
+
if os.environ.get("ONESCIENCE_SRC"):
|
| 20 |
+
candidates.append(Path(os.environ["ONESCIENCE_SRC"]).expanduser())
|
| 21 |
+
for parent in (PROJECT_ROOT, *PROJECT_ROOT.parents):
|
| 22 |
+
candidates.append(parent / "refactor" / "onescience" / "src")
|
| 23 |
+
candidates.append(parent / "onescience" / "src")
|
| 24 |
+
|
| 25 |
+
for candidate in candidates:
|
| 26 |
+
if (candidate / "onescience").is_dir():
|
| 27 |
+
path = str(candidate.resolve())
|
| 28 |
+
if path not in sys.path:
|
| 29 |
+
sys.path.insert(0, path)
|
| 30 |
+
return candidate.resolve()
|
| 31 |
+
return None
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def load_config():
|
| 35 |
+
with CONFIG_PATH.open("r", encoding="utf-8") as f:
|
| 36 |
+
return yaml.safe_load(f)["root"]
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def resolve_path(path_value):
|
| 40 |
+
path = Path(path_value)
|
| 41 |
+
return path if path.is_absolute() else PROJECT_ROOT / path
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def select_device(section):
|
| 45 |
+
requested = section.get("device", "auto")
|
| 46 |
+
gpu = int(section.get("gpu", 0))
|
| 47 |
+
if requested == "auto":
|
| 48 |
+
return torch.device(f"cuda:{gpu}" if torch.cuda.is_available() else "cpu")
|
| 49 |
+
if requested.startswith("cuda") and not torch.cuda.is_available():
|
| 50 |
+
print(f"Requested {requested}, but CUDA is not available. Falling back to CPU.")
|
| 51 |
+
return torch.device("cpu")
|
| 52 |
+
return torch.device(requested)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def dtype_from_config(name):
|
| 56 |
+
mapping = {
|
| 57 |
+
"float32": torch.float32,
|
| 58 |
+
"float": torch.float32,
|
| 59 |
+
"float64": torch.float64,
|
| 60 |
+
"double": torch.float64,
|
| 61 |
+
}
|
| 62 |
+
if name not in mapping:
|
| 63 |
+
raise ValueError(f"Unsupported dtype: {name}")
|
| 64 |
+
return mapping[name]
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def set_nested(cfg, section, key, value):
|
| 68 |
+
if value is not None:
|
| 69 |
+
cfg[section][key] = value
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def build_models(cfg, device, *, n_col_domain=None, n_train_per_bc=None, problem=None):
|
| 73 |
+
ensure_onescience_path(cfg.get("runtime", {}).get("onescience_src"))
|
| 74 |
+
from model import GPPLUS
|
| 75 |
+
from onescience.utils.GP_TO import get_data_fluid, set_seed
|
| 76 |
+
|
| 77 |
+
problem = problem or cfg["problem"]
|
| 78 |
+
n_col_domain = int(n_col_domain or cfg["data"]["n_col_domain"])
|
| 79 |
+
n_train_per_bc = int(n_train_per_bc or cfg["data"]["n_train_per_bc"])
|
| 80 |
+
dtype = dtype_from_config(cfg["model"].get("dtype", "float32"))
|
| 81 |
+
|
| 82 |
+
set_seed(int(cfg["seed"]))
|
| 83 |
+
x_col, x_train, sol_train = get_data_fluid(
|
| 84 |
+
problem=problem,
|
| 85 |
+
N_col_domain=n_col_domain,
|
| 86 |
+
N_train=n_train_per_bc,
|
| 87 |
+
)
|
| 88 |
+
collocation_x = x_col.to(device=device, dtype=dtype).clone().requires_grad_(True)
|
| 89 |
+
|
| 90 |
+
models = []
|
| 91 |
+
for i, name in enumerate(cfg["output_names"]):
|
| 92 |
+
model = GPPLUS(
|
| 93 |
+
train_x=x_train[i].type(dtype),
|
| 94 |
+
train_y=sol_train[i].type(dtype),
|
| 95 |
+
collocation_x=collocation_x,
|
| 96 |
+
basis=cfg["model"]["mean_function"],
|
| 97 |
+
NN_layers_base=cfg["model"]["nn_layers_base"],
|
| 98 |
+
name_output=name,
|
| 99 |
+
device=device,
|
| 100 |
+
dtype=dtype,
|
| 101 |
+
).to(device=device, dtype=dtype)
|
| 102 |
+
models.append(model)
|
| 103 |
+
|
| 104 |
+
return models, {
|
| 105 |
+
"problem": problem,
|
| 106 |
+
"n_col_domain": n_col_domain,
|
| 107 |
+
"n_train_per_bc": n_train_per_bc,
|
| 108 |
+
"x_col_shape": tuple(x_col.shape),
|
| 109 |
+
"x_train_shapes": [tuple(x.shape) for x in x_train],
|
| 110 |
+
"sol_train_shapes": [tuple(y.shape) for y in sol_train],
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def save_checkpoint(path, model_list, cfg, metadata, loss_history):
|
| 115 |
+
path = resolve_path(path)
|
| 116 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 117 |
+
torch.save(
|
| 118 |
+
{
|
| 119 |
+
"model_state_dicts": [model.state_dict() for model in model_list],
|
| 120 |
+
"config": cfg,
|
| 121 |
+
"metadata": metadata,
|
| 122 |
+
"loss_history": loss_history,
|
| 123 |
+
},
|
| 124 |
+
path,
|
| 125 |
+
)
|
| 126 |
+
return path
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def load_checkpoint(path, model_list, device):
|
| 130 |
+
path = resolve_path(path)
|
| 131 |
+
if not path.is_file():
|
| 132 |
+
raise FileNotFoundError(f"Missing checkpoint: {path}. Run scripts/train.py first.")
|
| 133 |
+
try:
|
| 134 |
+
checkpoint = torch.load(path, map_location=device, weights_only=False)
|
| 135 |
+
except TypeError:
|
| 136 |
+
checkpoint = torch.load(path, map_location=device)
|
| 137 |
+
|
| 138 |
+
state_dicts = checkpoint.get("model_state_dicts")
|
| 139 |
+
if state_dicts is None:
|
| 140 |
+
raise KeyError(f"Checkpoint does not contain model_state_dicts: {path}")
|
| 141 |
+
for model, state_dict in zip(model_list, state_dicts):
|
| 142 |
+
model.load_state_dict(state_dict)
|
| 143 |
+
return checkpoint
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def dump_json(data, path):
|
| 147 |
+
path = resolve_path(path)
|
| 148 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 149 |
+
with path.open("w", encoding="utf-8") as f:
|
| 150 |
+
json.dump(data, f, indent=2)
|
| 151 |
+
return path
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def tensor_to_numpy_dict(fields):
|
| 155 |
+
return {key: value.detach().cpu().numpy() for key, value in fields.items()}
|
scripts/fake_data.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import json
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 10 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 11 |
+
|
| 12 |
+
from scripts.common import PROBLEMS, ensure_onescience_path, load_config, resolve_path
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def parse_args():
|
| 16 |
+
parser = argparse.ArgumentParser(description="Generate GP_for_TO runtime sample tensors.")
|
| 17 |
+
parser.add_argument("--problem", choices=PROBLEMS, default=None)
|
| 18 |
+
parser.add_argument("--n-col-domain", type=int, default=None)
|
| 19 |
+
parser.add_argument("--n-train-per-bc", type=int, default=None)
|
| 20 |
+
parser.add_argument("--output-dir", default=None)
|
| 21 |
+
return parser.parse_args()
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def main():
|
| 25 |
+
args = parse_args()
|
| 26 |
+
cfg = load_config()
|
| 27 |
+
ensure_onescience_path(cfg.get("runtime", {}).get("onescience_src"))
|
| 28 |
+
from onescience.utils.GP_TO import get_data_fluid, set_seed
|
| 29 |
+
|
| 30 |
+
problem = args.problem or cfg["fake_data"]["problem"]
|
| 31 |
+
n_col_domain = args.n_col_domain or cfg["fake_data"]["n_col_domain"]
|
| 32 |
+
n_train_per_bc = args.n_train_per_bc or cfg["fake_data"]["n_train_per_bc"]
|
| 33 |
+
output_dir = resolve_path(args.output_dir or cfg["fake_data"]["output_dir"])
|
| 34 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 35 |
+
|
| 36 |
+
set_seed(int(cfg["seed"]))
|
| 37 |
+
x_col, x_train, sol_train = get_data_fluid(
|
| 38 |
+
problem=problem,
|
| 39 |
+
N_col_domain=n_col_domain,
|
| 40 |
+
N_train=n_train_per_bc,
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
npz_path = output_dir / f"{problem}_samples.npz"
|
| 44 |
+
arrays = {"x_col": x_col.cpu().numpy()}
|
| 45 |
+
for i, name in enumerate(cfg["output_names"]):
|
| 46 |
+
arrays[f"x_train_{name}"] = x_train[i].cpu().numpy()
|
| 47 |
+
arrays[f"target_{name}"] = sol_train[i].cpu().numpy()
|
| 48 |
+
np.savez(npz_path, **arrays)
|
| 49 |
+
|
| 50 |
+
metadata = {
|
| 51 |
+
"problem": problem,
|
| 52 |
+
"n_col_domain_requested": int(n_col_domain),
|
| 53 |
+
"n_train_per_bc": int(n_train_per_bc),
|
| 54 |
+
"x_col_shape": list(x_col.shape),
|
| 55 |
+
"x_train_shapes": [list(x.shape) for x in x_train],
|
| 56 |
+
"target_shapes": [list(y.shape) for y in sol_train],
|
| 57 |
+
}
|
| 58 |
+
metadata_path = output_dir / f"{problem}_metadata.json"
|
| 59 |
+
metadata_path.write_text(json.dumps(metadata, indent=2), encoding="utf-8")
|
| 60 |
+
print(f"Fake GP_for_TO tensors written to {npz_path}")
|
| 61 |
+
print(json.dumps(metadata, indent=2))
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
if __name__ == "__main__":
|
| 65 |
+
main()
|
scripts/inference.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import json
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 12 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 13 |
+
|
| 14 |
+
from scripts.common import (
|
| 15 |
+
PROBLEMS,
|
| 16 |
+
build_models,
|
| 17 |
+
ensure_onescience_path,
|
| 18 |
+
load_checkpoint,
|
| 19 |
+
load_config,
|
| 20 |
+
resolve_path,
|
| 21 |
+
select_device,
|
| 22 |
+
tensor_to_numpy_dict,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
ensure_onescience_path()
|
| 26 |
+
from scripts.topology_optimization import clear_cached_kernels, predict_fields, share_mean_module
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def parse_args():
|
| 30 |
+
parser = argparse.ArgumentParser(description="Run GP_for_TO field inference from a checkpoint.")
|
| 31 |
+
parser.add_argument("--problem", choices=PROBLEMS, default=None)
|
| 32 |
+
parser.add_argument("--gpu", type=int, default=None)
|
| 33 |
+
parser.add_argument("--device", default=None)
|
| 34 |
+
parser.add_argument("--n-col-domain", type=int, default=None)
|
| 35 |
+
parser.add_argument("--n-train-per-bc", type=int, default=None)
|
| 36 |
+
parser.add_argument("--checkpoint-path", default=None)
|
| 37 |
+
parser.add_argument("--output-dir", default=None)
|
| 38 |
+
return parser.parse_args()
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def main():
|
| 42 |
+
args = parse_args()
|
| 43 |
+
cfg = load_config()
|
| 44 |
+
if args.problem:
|
| 45 |
+
cfg["problem"] = args.problem
|
| 46 |
+
if args.gpu is not None:
|
| 47 |
+
cfg["inference"]["gpu"] = args.gpu
|
| 48 |
+
if args.device:
|
| 49 |
+
cfg["inference"]["device"] = args.device
|
| 50 |
+
if args.n_col_domain is not None:
|
| 51 |
+
cfg["inference"]["n_col_domain"] = args.n_col_domain
|
| 52 |
+
if args.n_train_per_bc is not None:
|
| 53 |
+
cfg["data"]["n_train_per_bc"] = args.n_train_per_bc
|
| 54 |
+
if args.checkpoint_path:
|
| 55 |
+
cfg["inference"]["checkpoint_path"] = args.checkpoint_path
|
| 56 |
+
if args.output_dir:
|
| 57 |
+
cfg["inference"]["output_dir"] = args.output_dir
|
| 58 |
+
|
| 59 |
+
os.chdir(PROJECT_ROOT)
|
| 60 |
+
device = select_device(cfg["inference"])
|
| 61 |
+
models, metadata = build_models(
|
| 62 |
+
cfg,
|
| 63 |
+
device,
|
| 64 |
+
n_col_domain=cfg["inference"].get("n_col_domain", cfg["data"]["n_col_domain"]),
|
| 65 |
+
n_train_per_bc=cfg["data"]["n_train_per_bc"],
|
| 66 |
+
problem=cfg["problem"],
|
| 67 |
+
)
|
| 68 |
+
checkpoint = load_checkpoint(cfg["inference"]["checkpoint_path"], models, device)
|
| 69 |
+
share_mean_module(models)
|
| 70 |
+
for model in models:
|
| 71 |
+
model.eval()
|
| 72 |
+
clear_cached_kernels(models)
|
| 73 |
+
|
| 74 |
+
with torch.no_grad():
|
| 75 |
+
fields = predict_fields(models)
|
| 76 |
+
|
| 77 |
+
output_dir = resolve_path(cfg["inference"]["output_dir"])
|
| 78 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 79 |
+
npz_path = output_dir / "predictions.npz"
|
| 80 |
+
np.savez(npz_path, **tensor_to_numpy_dict(fields))
|
| 81 |
+
|
| 82 |
+
summary = {
|
| 83 |
+
**metadata,
|
| 84 |
+
"checkpoint_metadata": checkpoint.get("metadata", {}),
|
| 85 |
+
"output_file": str(npz_path),
|
| 86 |
+
"field_shapes": {key: list(value.shape) for key, value in fields.items()},
|
| 87 |
+
}
|
| 88 |
+
(output_dir / "inference_summary.json").write_text(json.dumps(summary, indent=2), encoding="utf-8")
|
| 89 |
+
print(f"Saved predictions to {npz_path}")
|
| 90 |
+
print(json.dumps(summary["field_shapes"], indent=2))
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
if __name__ == "__main__":
|
| 94 |
+
main()
|
scripts/result.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import json
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import matplotlib.pyplot as plt
|
| 7 |
+
import numpy as np
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 11 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 12 |
+
|
| 13 |
+
from scripts.common import load_config, resolve_path
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def parse_args():
|
| 17 |
+
parser = argparse.ArgumentParser(description="Summarize GP_for_TO inference outputs.")
|
| 18 |
+
parser.add_argument("--output-dir", default=None)
|
| 19 |
+
parser.add_argument("--no-plot", action="store_true")
|
| 20 |
+
return parser.parse_args()
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def save_plot(data, output_dir):
|
| 24 |
+
x = data["x"]
|
| 25 |
+
fields = [("u", data["u"]), ("v", data["v"]), ("p", data["p"]), ("ro", data["ro"])]
|
| 26 |
+
fig, axes = plt.subplots(2, 2, figsize=(9, 7))
|
| 27 |
+
for ax, (name, values) in zip(axes.reshape(-1), fields):
|
| 28 |
+
im = ax.tricontourf(x[:, 0], x[:, 1], values, levels=32, cmap="viridis")
|
| 29 |
+
ax.set_title(name)
|
| 30 |
+
ax.set_xlabel("x")
|
| 31 |
+
ax.set_ylabel("y")
|
| 32 |
+
fig.colorbar(im, ax=ax)
|
| 33 |
+
fig.tight_layout()
|
| 34 |
+
path = output_dir / "field_summary.png"
|
| 35 |
+
fig.savefig(path, dpi=160)
|
| 36 |
+
plt.close(fig)
|
| 37 |
+
return path
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def main():
|
| 41 |
+
args = parse_args()
|
| 42 |
+
cfg = load_config()
|
| 43 |
+
output_dir = resolve_path(args.output_dir or cfg["inference"]["output_dir"])
|
| 44 |
+
pred_path = output_dir / "predictions.npz"
|
| 45 |
+
summary_path = output_dir / "inference_summary.json"
|
| 46 |
+
if not pred_path.is_file():
|
| 47 |
+
raise FileNotFoundError(f"Missing inference output: {pred_path}")
|
| 48 |
+
|
| 49 |
+
data = np.load(pred_path)
|
| 50 |
+
print(f"Prediction file: {pred_path}")
|
| 51 |
+
for name in ("x", "u", "v", "p", "ro"):
|
| 52 |
+
arr = data[name]
|
| 53 |
+
print(
|
| 54 |
+
f"{name}: shape={arr.shape}, dtype={arr.dtype}, "
|
| 55 |
+
f"min={float(arr.min()):.6e}, max={float(arr.max()):.6e}, mean={float(arr.mean()):.6e}"
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
if summary_path.is_file():
|
| 59 |
+
summary = json.loads(summary_path.read_text(encoding="utf-8"))
|
| 60 |
+
print(f"Problem: {summary.get('problem')}, checkpoint source: {summary.get('checkpoint_metadata', {}).get('problem')}")
|
| 61 |
+
|
| 62 |
+
if not args.no_plot:
|
| 63 |
+
plot_path = save_plot(data, output_dir)
|
| 64 |
+
print(f"Plot: {plot_path}")
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
if __name__ == "__main__":
|
| 68 |
+
main()
|
scripts/topology_optimization.py
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import math
|
| 3 |
+
import numpy as np
|
| 4 |
+
from gpytorch.settings import cholesky_jitter
|
| 5 |
+
import matplotlib.pyplot as plt
|
| 6 |
+
import torch.optim
|
| 7 |
+
import matplotlib.pyplot as plt
|
| 8 |
+
from tqdm import tqdm
|
| 9 |
+
from onescience.utils.GP_TO import plot_predictions_and_residuals,plot_loss_history,plot_density_and_velocity_fields
|
| 10 |
+
plt.rcParams['font.family'] = 'DejaVu Sans'
|
| 11 |
+
plt.rcParams['font.size'] = 15
|
| 12 |
+
plt.rcParams['figure.dpi'] = 150
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
checkpoints=[1000,10000,20000,30000,40000,50000]
|
| 16 |
+
lambdaa = 0.1
|
| 17 |
+
|
| 18 |
+
# Determine volume loss based on title
|
| 19 |
+
gamma_values = {
|
| 20 |
+
'rugby': 0.9,
|
| 21 |
+
'pipebend': 0.08 * torch.pi,
|
| 22 |
+
'doublepipe': 1 / 3,
|
| 23 |
+
'diffuser': 0.5
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
def modified_sigmoid(x, alpha=12.0):
|
| 27 |
+
"""Compute the modified sigmoid function."""
|
| 28 |
+
return 1 / (1 + torch.exp(-alpha * (x - 0.5)))
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def infer_square_grid(num_points):
|
| 32 |
+
side = int(math.sqrt(num_points))
|
| 33 |
+
if side * side != num_points:
|
| 34 |
+
raise ValueError(
|
| 35 |
+
"Numerical differentiation expects a square collocation grid. "
|
| 36 |
+
f"Got {num_points} points."
|
| 37 |
+
)
|
| 38 |
+
return side, side
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def share_mean_module(model_list):
|
| 42 |
+
model_ref = model_list[0]
|
| 43 |
+
for model in model_list:
|
| 44 |
+
model.mean_module_NN_All = model_ref.mean_module_NN_All
|
| 45 |
+
return model_ref
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def clear_cached_kernels(model_list):
|
| 49 |
+
for model in model_list:
|
| 50 |
+
model.k_xX = None
|
| 51 |
+
model.chol_decomp = None
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def predict_fields(model_list):
|
| 55 |
+
model_ref = share_mean_module(model_list)
|
| 56 |
+
collocation_x = model_ref.collocation_x
|
| 57 |
+
m_col = model_ref.mean_module_NN_All(collocation_x)
|
| 58 |
+
|
| 59 |
+
if model_ref.k_xX is None:
|
| 60 |
+
for model in model_list:
|
| 61 |
+
model.k_xX = model.covar_module(model.train_inputs[0], collocation_x).evaluate()
|
| 62 |
+
|
| 63 |
+
if model_ref.chol_decomp is None:
|
| 64 |
+
with cholesky_jitter(1e-7):
|
| 65 |
+
for model in model_list:
|
| 66 |
+
model.chol_decomp = model.covar_module(model.train_inputs[0]).cholesky()
|
| 67 |
+
|
| 68 |
+
offsets = []
|
| 69 |
+
for i, model in enumerate(model_list):
|
| 70 |
+
target_offset = model.train_targets.unsqueeze(-1) - model.mean_module_NN_All(model.train_inputs[0])[:, i].unsqueeze(-1)
|
| 71 |
+
offsets.append(model.chol_decomp._cholesky_solve(target_offset))
|
| 72 |
+
|
| 73 |
+
u = (m_col[:, 0].unsqueeze(-1) + model_list[0].k_xX.t() @ offsets[0]).squeeze(-1)
|
| 74 |
+
v = (m_col[:, 1].unsqueeze(-1) + model_list[1].k_xX.t() @ offsets[1]).squeeze(-1)
|
| 75 |
+
p = (m_col[:, 2].unsqueeze(-1) + model_list[2].k_xX.t() @ offsets[2]).squeeze(-1)
|
| 76 |
+
ro_tensor = (m_col[:, 3].unsqueeze(-1) + model_list[3].k_xX.t() @ offsets[3]).squeeze(-1)
|
| 77 |
+
ro = modified_sigmoid(ro_tensor)
|
| 78 |
+
return {"x": collocation_x, "u": u, "v": v, "p": p, "ro": ro}
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def compute_dynamic_weights_ic(pde_loss, bc_loss, ic_loss, model):
|
| 82 |
+
"""
|
| 83 |
+
Compute dynamic weights for loss functions based on gradients.
|
| 84 |
+
"""
|
| 85 |
+
params_to_update = [param for param in model.parameters() if param.requires_grad]
|
| 86 |
+
|
| 87 |
+
def compute_gradients(loss, scaling_factor):
|
| 88 |
+
gradients = torch.autograd.grad(scaling_factor * loss, params_to_update, retain_graph=True, allow_unused=True)
|
| 89 |
+
values = [p.reshape(-1).cpu().tolist() for p in gradients if p is not None]
|
| 90 |
+
return torch.abs(torch.tensor([v for val in values for v in val]))
|
| 91 |
+
|
| 92 |
+
delta_pde = compute_gradients(pde_loss, 1.0)
|
| 93 |
+
delta_bc = compute_gradients(bc_loss, model.alpha)
|
| 94 |
+
delta_ic = compute_gradients(ic_loss, model.beta)
|
| 95 |
+
|
| 96 |
+
temp_bc = torch.max(delta_pde) / torch.mean(delta_bc)
|
| 97 |
+
temp_ic = torch.max(delta_pde) / torch.mean(delta_ic)
|
| 98 |
+
|
| 99 |
+
return (
|
| 100 |
+
(1.0 - lambdaa) * model.alpha + lambdaa * temp_bc,
|
| 101 |
+
(1.0 - lambdaa) * model.beta + lambdaa * temp_ic
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
def compute_fvm_residuals_higher_order(u, v, p, f_x, f_y, nu, dx, dy):
|
| 105 |
+
# Check the device of the input tensors and move other tensors to the same device
|
| 106 |
+
device = u.device
|
| 107 |
+
|
| 108 |
+
# Extend the fields with ghost cells to handle boundary conditions
|
| 109 |
+
u_ext = torch.zeros((u.shape[0] + 4, u.shape[1] + 4), device=device)
|
| 110 |
+
v_ext = torch.zeros((v.shape[0] + 4, v.shape[1] + 4), device=device)
|
| 111 |
+
p_ext = torch.zeros((p.shape[0] + 4, p.shape[1] + 4), device=device)
|
| 112 |
+
|
| 113 |
+
# Copy the interior
|
| 114 |
+
u_ext[2:-2, 2:-2] = u
|
| 115 |
+
v_ext[2:-2, 2:-2] = v
|
| 116 |
+
p_ext[2:-2, 2:-2] = p
|
| 117 |
+
|
| 118 |
+
# Apply ghost cells for no-slip boundary conditions
|
| 119 |
+
u_ext[:2, :] = u_ext[2:4, :]
|
| 120 |
+
u_ext[-2:, :] = u_ext[-4:-2, :]
|
| 121 |
+
u_ext[:, :2] = u_ext[:, 2:4]
|
| 122 |
+
u_ext[:, -2:] = u_ext[:, -4:-2]
|
| 123 |
+
|
| 124 |
+
v_ext[:2, :] = v_ext[2:4, :]
|
| 125 |
+
v_ext[-2:, :] = v_ext[-4:-2, :]
|
| 126 |
+
v_ext[:, :2] = v_ext[:, 2:4]
|
| 127 |
+
v_ext[:, -2:] = v_ext[:, -4:-2]
|
| 128 |
+
|
| 129 |
+
# Laplacian of u and v (fourth-order central differences for second derivatives)
|
| 130 |
+
u_xx = (-u_ext[4:, 2:-2] + 16 * u_ext[3:-1, 2:-2] - 30 * u_ext[2:-2, 2:-2] + 16 * u_ext[1:-3, 2:-2] - u_ext[:-4, 2:-2]) / (12 * dx**2)
|
| 131 |
+
u_yy = (-u_ext[2:-2, 4:] + 16 * u_ext[2:-2, 3:-1] - 30 * u_ext[2:-2, 2:-2] + 16 * u_ext[2:-2, 1:-3] - u_ext[2:-2, :-4]) / (12 * dy**2)
|
| 132 |
+
laplacian_u = u_xx + u_yy
|
| 133 |
+
|
| 134 |
+
v_xx = (-v_ext[4:, 2:-2] + 16 * v_ext[3:-1, 2:-2] - 30 * v_ext[2:-2, 2:-2] + 16 * v_ext[1:-3, 2:-2] - v_ext[:-4, 2:-2]) / (12 * dx**2)
|
| 135 |
+
v_yy = (-v_ext[2:-2, 4:] + 16 * v_ext[2:-2, 3:-1] - 30 * v_ext[2:-2, 2:-2] + 16 * v_ext[2:-2, 1:-3] - v_ext[2:-2, :-4]) / (12 * dy**2)
|
| 136 |
+
laplacian_v = v_xx + v_yy
|
| 137 |
+
|
| 138 |
+
# Gradients of p using fourth-order central difference
|
| 139 |
+
grad_p_x = (-p_ext[4:, 2:-2] + 8 * p_ext[3:-1, 2:-2] - 8 * p_ext[1:-3, 2:-2] + p_ext[:-4, 2:-2]) / (12 * dx)
|
| 140 |
+
grad_p_y = (-p_ext[2:-2, 4:] + 8 * p_ext[2:-2, 3:-1] - 8 * p_ext[2:-2, 1:-3] + p_ext[2:-2, :-4]) / (12 * dy)
|
| 141 |
+
|
| 142 |
+
# Momentum equation residuals
|
| 143 |
+
residual_u = -nu * laplacian_u + grad_p_x + f_x
|
| 144 |
+
residual_v = -nu * laplacian_v + grad_p_y + f_y
|
| 145 |
+
|
| 146 |
+
# Mass conservation (divergence of velocity) using central differences
|
| 147 |
+
div_u = (u_ext[2:-2, 2:-2] - u_ext[1:-3, 2:-2]) / dx
|
| 148 |
+
div_v = (v_ext[2:-2, 2:-2] - v_ext[2:-2, 1:-3]) / dy
|
| 149 |
+
|
| 150 |
+
u_y = (u_ext[2:-2, 2:-2] - u_ext[2:-2, 1:-3]) / dy
|
| 151 |
+
v_x = (v_ext[2:-2, 2:-2] - v_ext[1:-3, 2:-2]) / dx
|
| 152 |
+
residual_mass = div_u + div_v
|
| 153 |
+
|
| 154 |
+
return residual_u.reshape(-1), residual_v.reshape(-1), residual_mass.reshape(-1), div_u.reshape(-1), div_v.reshape(-1), u_y.reshape(-1), v_x.reshape(-1)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def compute_autograd_derivatives(model, z_column, collocation_x, grad_order=1):
|
| 159 |
+
"""
|
| 160 |
+
Compute first and second-order derivatives using PyTorch autograd.
|
| 161 |
+
:param model: Model to train
|
| 162 |
+
:param z_column: Column of `z_all` for differentiation
|
| 163 |
+
:param collocation_x: Input coordinates
|
| 164 |
+
:param grad_order: Order of gradients (1 for first, 2 for second)
|
| 165 |
+
:return: First and second-order derivatives as tensors
|
| 166 |
+
"""
|
| 167 |
+
model.train()
|
| 168 |
+
grad_1 = torch.autograd.grad(z_column, collocation_x, torch.ones_like(z_column), create_graph=True)[0]
|
| 169 |
+
if grad_order > 1:
|
| 170 |
+
grad_2_x = torch.autograd.grad(grad_1[:, 0], collocation_x, torch.ones_like(grad_1[:, 0]), create_graph=True)[0][:, 0]
|
| 171 |
+
grad_2_y = torch.autograd.grad(grad_1[:, 1], collocation_x, torch.ones_like(grad_1[:, 1]), create_graph=True)[0][:, 1]
|
| 172 |
+
return grad_1[:, 0], grad_1[:, 1], grad_2_x, grad_2_y
|
| 173 |
+
return grad_1[:, 0], grad_1[:, 1], None, None
|
| 174 |
+
|
| 175 |
+
w_1,w_2,w_3, w_4, w_5 =0.01,0.01,1e2,1e5,1e4
|
| 176 |
+
|
| 177 |
+
def loss_volume(y, gamma=0.5):
|
| 178 |
+
"""
|
| 179 |
+
Compute the volume loss as the squared difference between the mean of y and gamma.
|
| 180 |
+
"""
|
| 181 |
+
mean_y = torch.mean(y)
|
| 182 |
+
return torch.square(mean_y - gamma)
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def dissipated_power(u, u_x, v_y, u_y, v_x):
|
| 186 |
+
"""
|
| 187 |
+
Compute the total dissipated power based on the input velocity gradients and displacements.
|
| 188 |
+
"""
|
| 189 |
+
# First part of the dissipated power
|
| 190 |
+
p1 = (u_x**2 + v_y**2 + u_y**2 + v_x**2).sum(dim=1, keepdim=True)
|
| 191 |
+
|
| 192 |
+
# Second part of the dissipated power
|
| 193 |
+
u2 = (u[:, :2]**2).sum(dim=1, keepdim=True)
|
| 194 |
+
p2 = alpha(u[:, 3:]) * u2
|
| 195 |
+
|
| 196 |
+
return 0.5 * (p1 + p2)
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def alpha(rho):
|
| 200 |
+
"""
|
| 201 |
+
Compute the alpha parameter based on rho.
|
| 202 |
+
"""
|
| 203 |
+
alpha_max = 2.5e4
|
| 204 |
+
alpha_min = 2.5e-4
|
| 205 |
+
q = 0.1
|
| 206 |
+
return alpha_max + (alpha_min - alpha_max) * rho * (1 + q) / (rho + q)
|
| 207 |
+
|
| 208 |
+
def calculate_loss_multioutput(
|
| 209 |
+
model_list,
|
| 210 |
+
iteration,
|
| 211 |
+
diff_method='Numerical',
|
| 212 |
+
title="default",
|
| 213 |
+
problem="doublepipe",
|
| 214 |
+
checkpoint_steps=None,
|
| 215 |
+
plot_outputs=True,
|
| 216 |
+
):
|
| 217 |
+
"""
|
| 218 |
+
Calculate loss for multi-output models based on PDE, BC, and IC residuals.
|
| 219 |
+
"""
|
| 220 |
+
# Clone collocation points
|
| 221 |
+
collocation_x = model_list[0].collocation_x.clone()
|
| 222 |
+
|
| 223 |
+
# Compute mean values at collocation points
|
| 224 |
+
m_col = model_list[0].mean_module_NN_All(collocation_x)
|
| 225 |
+
|
| 226 |
+
# Evaluate covariance matrices if not already done
|
| 227 |
+
if model_list[0].k_xX is None:
|
| 228 |
+
for i, model in enumerate(model_list):
|
| 229 |
+
model.k_xX = model.covar_module(model.train_inputs[0], collocation_x).evaluate()
|
| 230 |
+
|
| 231 |
+
# Perform Cholesky decomposition if not already done
|
| 232 |
+
if model_list[0].chol_decomp is None:
|
| 233 |
+
with cholesky_jitter(1e-7):
|
| 234 |
+
for model in model_list:
|
| 235 |
+
model.chol_decomp = model.covar_module(model.train_inputs[0]).cholesky()
|
| 236 |
+
|
| 237 |
+
# Solve for offsets using Cholesky decomposition
|
| 238 |
+
offsets = []
|
| 239 |
+
for i, model in enumerate(model_list):
|
| 240 |
+
target_offset = model.train_targets.unsqueeze(-1) - model.mean_module_NN_All(model.train_inputs[0])[:, i].unsqueeze(-1)
|
| 241 |
+
offsets.append(model.chol_decomp._cholesky_solve(target_offset))
|
| 242 |
+
|
| 243 |
+
# Compute values for velocity, pressure, and density
|
| 244 |
+
ro_tensor = (m_col[:, 3].unsqueeze(-1) + model_list[3].k_xX.t() @ offsets[3]).squeeze(-1)
|
| 245 |
+
ro = modified_sigmoid(ro_tensor)
|
| 246 |
+
|
| 247 |
+
u = (m_col[:, 0].unsqueeze(-1) + model_list[0].k_xX.t() @ offsets[0]).squeeze(-1)
|
| 248 |
+
v = (m_col[:, 1].unsqueeze(-1) + model_list[1].k_xX.t() @ offsets[1]).squeeze(-1)
|
| 249 |
+
p = (m_col[:, 2].unsqueeze(-1) + model_list[2].k_xX.t() @ offsets[2]).squeeze(-1)
|
| 250 |
+
|
| 251 |
+
z_all = torch.cat((u.unsqueeze(1), v.unsqueeze(1), p.unsqueeze(1), ro.unsqueeze(1)), dim=1)
|
| 252 |
+
f = alpha(z_all[:, 3:]) * z_all[:, :2]
|
| 253 |
+
fx, fy = f[:, :1], f[:, 1:]
|
| 254 |
+
|
| 255 |
+
# Compute residuals
|
| 256 |
+
if diff_method == 'Numerical':
|
| 257 |
+
nx, ny = infer_square_grid(collocation_x.shape[0])
|
| 258 |
+
dx, dy = 1.0 / nx, 1.0 / ny
|
| 259 |
+
u, v, p = [arr.reshape(nx, ny) for arr in (u, v, p)]
|
| 260 |
+
fx, fy = [arr.reshape(nx, ny) for arr in (fx, fy)]
|
| 261 |
+
residuals = compute_fvm_residuals_higher_order(u, v, p, fx, fy, nu=1, dx=dx, dy=dy)
|
| 262 |
+
residual_pde1, residual_pde2, residual_pde3, u_x, v_y, u_y, v_x = residuals
|
| 263 |
+
residual_pde1, residual_pde2, residual_pde3 = [res * w for res, w in zip(residuals[:3], (w_1, w_2, w_3))]
|
| 264 |
+
u, v, p = [arr.reshape(-1) for arr in (u, v, p)]
|
| 265 |
+
fx, fy = [arr.reshape(-1) for arr in (fx, fy)]
|
| 266 |
+
else:
|
| 267 |
+
u_x, u_y, u_xx, u_yy = compute_autograd_derivatives(model_list[0], z_all[:, 0], collocation_x, grad_order=2)
|
| 268 |
+
v_x, v_y, v_xx, v_yy = compute_autograd_derivatives(model_list[1], z_all[:, 1], collocation_x, grad_order=2)
|
| 269 |
+
p_x, p_y, _, _ = compute_autograd_derivatives(model_list[2], z_all[:, 2], collocation_x, grad_order=1)
|
| 270 |
+
residual_pde1 = (- (u_xx + u_yy) + p_x + fx[:, 0]) * w_1
|
| 271 |
+
residual_pde2 = (- (v_xx + v_yy) + p_y + fy[:, 0]) * w_2
|
| 272 |
+
residual_pde3 = (u_x + v_y) * w_3
|
| 273 |
+
|
| 274 |
+
# Plot fields at checkpoints
|
| 275 |
+
active_checkpoints = checkpoints if checkpoint_steps is None else checkpoint_steps
|
| 276 |
+
if plot_outputs and iteration in active_checkpoints:
|
| 277 |
+
plot_density_and_velocity_fields(u, v, p, ro, collocation_x, iteration, problem)
|
| 278 |
+
plot_predictions_and_residuals(u, v, p, ro, collocation_x, iteration, residual_pde1, residual_pde2, residual_pde3, 1, 1, 1, problem)
|
| 279 |
+
# Calculate losses
|
| 280 |
+
loss_pde1 = torch.mean(residual_pde1**2)
|
| 281 |
+
loss_pde2 = torch.mean(residual_pde2**2)
|
| 282 |
+
loss_pde3 = torch.mean(residual_pde3**2)
|
| 283 |
+
dp_loss = dissipated_power(z_all, u_x.reshape(-1, 1), v_y.reshape(-1, 1), u_y.reshape(-1, 1), v_x.reshape(-1, 1))
|
| 284 |
+
|
| 285 |
+
gamma = next((val for key, val in gamma_values.items() if key in title), 0.5)
|
| 286 |
+
vol_loss = loss_volume(z_all[:, 3:], gamma=gamma)* w_4
|
| 287 |
+
|
| 288 |
+
# Return final losses
|
| 289 |
+
return loss_pde1, loss_pde2, loss_pde3, torch.sum(dp_loss), vol_loss
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def find_TO(
|
| 295 |
+
model_list,
|
| 296 |
+
lr_default: float = 0.01,
|
| 297 |
+
num_iter: int = 500,
|
| 298 |
+
title: str = 'default',
|
| 299 |
+
problem: str = 'doublepipe', # 设置默认值
|
| 300 |
+
diff_method: str = 'Numerical',
|
| 301 |
+
checkpoint_steps=None,
|
| 302 |
+
plot_outputs=True,
|
| 303 |
+
) -> float:
|
| 304 |
+
"""
|
| 305 |
+
Train models to minimize total loss using dynamic weights and record progress.
|
| 306 |
+
|
| 307 |
+
Args:
|
| 308 |
+
model_list: List of models to be trained.
|
| 309 |
+
lr_default: Default learning rate for the optimizer.
|
| 310 |
+
num_iter: Number of training iterations.
|
| 311 |
+
title: Title for specific settings.
|
| 312 |
+
diff_method: Differentiation method ('Numerical' or 'Autograd').
|
| 313 |
+
|
| 314 |
+
Returns:
|
| 315 |
+
loss history
|
| 316 |
+
"""
|
| 317 |
+
# Use the first model as a reference for shared NN
|
| 318 |
+
model_ref = share_mean_module(model_list)
|
| 319 |
+
for model in model_list:
|
| 320 |
+
model.train()
|
| 321 |
+
|
| 322 |
+
# Initialize variables
|
| 323 |
+
loss_total, loss_hist, loss_hist_total = [], [], []
|
| 324 |
+
loss_pde1_hist, loss_pde2_hist, loss_pde3_hist = [], [], []
|
| 325 |
+
loss_dp_hist, vol_loss_hist = [], []
|
| 326 |
+
scaled_loss_pde1_hist, scaled_loss_pde2_hist = [], []
|
| 327 |
+
scaled_loss_pde3_hist, scaled_loss_dp_hist, scaled_vol_loss_hist = [], [], []
|
| 328 |
+
weights = {'alpha': [], 'beta': [], 'mu_p': []}
|
| 329 |
+
result_loss_thetas = {'largest_eigval_hist': [], 'condition_number_hist': [], 'Hessian': [], 'grads': []}
|
| 330 |
+
GP_NN_hist, NN_hist, time_hist = [], [], []
|
| 331 |
+
dynamic_weights = True
|
| 332 |
+
sigma_1, beta_1, alph_1 = 10, 10, 0.1
|
| 333 |
+
f_inc, mu_F = math.inf, 1
|
| 334 |
+
|
| 335 |
+
# Initialize model-specific parameters
|
| 336 |
+
model1 = model_list[0]
|
| 337 |
+
model1.alpha, model1.beta, model1.theta = 1, 1, 1
|
| 338 |
+
|
| 339 |
+
# Set up optimizer and scheduler
|
| 340 |
+
optimizer = torch.optim.Adam(model_ref.parameters(), lr=lr_default)
|
| 341 |
+
milestones = sorted({int(x) for x in np.linspace(0, max(num_iter, 1), 4).tolist() if int(x) > 0})
|
| 342 |
+
scheduler = torch.optim.lr_scheduler.MultiStepLR(
|
| 343 |
+
optimizer,
|
| 344 |
+
milestones=milestones,
|
| 345 |
+
gamma=0.75
|
| 346 |
+
)
|
| 347 |
+
|
| 348 |
+
# Training loop
|
| 349 |
+
with tqdm(range(num_iter + 1), desc='Epoch', position=0, leave=True) as pbar:
|
| 350 |
+
for j in pbar:
|
| 351 |
+
optimizer.zero_grad()
|
| 352 |
+
|
| 353 |
+
# Calculate losses
|
| 354 |
+
loss_pde1, loss_pde2, loss_pde3, loss_dp, vol_loss = calculate_loss_multioutput(
|
| 355 |
+
model_list,
|
| 356 |
+
j,
|
| 357 |
+
diff_method=diff_method,
|
| 358 |
+
title=title,
|
| 359 |
+
problem=problem,
|
| 360 |
+
checkpoint_steps=checkpoint_steps,
|
| 361 |
+
plot_outputs=plot_outputs,
|
| 362 |
+
)
|
| 363 |
+
loss_pde = loss_pde1 + loss_pde3
|
| 364 |
+
|
| 365 |
+
# Dynamic weight adjustments
|
| 366 |
+
if dynamic_weights:
|
| 367 |
+
alpha, beta = compute_dynamic_weights_ic(
|
| 368 |
+
loss_pde1 + loss_pde2, loss_pde3, vol_loss, model1
|
| 369 |
+
)
|
| 370 |
+
if all(torch.is_tensor(val) and not (torch.isnan(val).any() or torch.isinf(val).any()) for val in [alpha, beta]):
|
| 371 |
+
model1.alpha, model1.beta = alpha, beta
|
| 372 |
+
|
| 373 |
+
weights['alpha'].append(alpha.detach().cpu().item())
|
| 374 |
+
weights['beta'].append(beta.detach().cpu().item())
|
| 375 |
+
weights['mu_p'].append(mu_F)
|
| 376 |
+
|
| 377 |
+
loss = loss_dp + mu_F * (
|
| 378 |
+
loss_pde1 + loss_pde2 + model1.alpha * loss_pde3 + model1.beta * vol_loss
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
if (j + 1) % 50 == 0:
|
| 382 |
+
mu_F = min(mu_F * 1.05, 5e2)
|
| 383 |
+
else:
|
| 384 |
+
alph_1 = 1e-6 / (j + 1)
|
| 385 |
+
beta_1, sigma_1 = 2 * (j + 1), 5 * (j + 1)
|
| 386 |
+
loss = sigma_1 * loss_pde + alph_1 * loss_dp + beta_1 * vol_loss
|
| 387 |
+
|
| 388 |
+
# Record loss history
|
| 389 |
+
loss_total.append(loss.item())
|
| 390 |
+
loss_pde1_hist.append((1/w_1) * loss_pde1.item())
|
| 391 |
+
loss_pde2_hist.append((1/w_2) * loss_pde2.item())
|
| 392 |
+
loss_pde3_hist.append((1/w_3) * loss_pde3.item())
|
| 393 |
+
loss_dp_hist.append((1/w_5) * loss_dp.item())
|
| 394 |
+
vol_loss_hist.append((1/w_4) * vol_loss.item())
|
| 395 |
+
scaled_loss_pde1_hist.append(loss_pde1.item())
|
| 396 |
+
scaled_loss_pde2_hist.append(loss_pde2.item())
|
| 397 |
+
scaled_loss_pde3_hist.append(model1.alpha * loss_pde3.item())
|
| 398 |
+
scaled_loss_dp_hist.append(loss_dp.item())
|
| 399 |
+
scaled_vol_loss_hist.append(model1.beta * vol_loss.item())
|
| 400 |
+
|
| 401 |
+
# Plot loss history at checkpoints
|
| 402 |
+
active_checkpoints = checkpoints if checkpoint_steps is None else checkpoint_steps
|
| 403 |
+
if plot_outputs and j in active_checkpoints:
|
| 404 |
+
plot_loss_history(
|
| 405 |
+
loss_total,
|
| 406 |
+
[loss_pde1_hist, scaled_loss_pde1_hist],
|
| 407 |
+
[loss_pde2_hist, scaled_loss_pde2_hist],
|
| 408 |
+
[loss_pde3_hist, scaled_loss_pde3_hist],
|
| 409 |
+
[loss_dp_hist, scaled_loss_dp_hist],
|
| 410 |
+
[vol_loss_hist, scaled_vol_loss_hist],
|
| 411 |
+
j,
|
| 412 |
+
problem
|
| 413 |
+
)
|
| 414 |
+
|
| 415 |
+
# Backpropagation and optimizer step
|
| 416 |
+
loss.backward(retain_graph=True)
|
| 417 |
+
optimizer.step()
|
| 418 |
+
scheduler.step()
|
| 419 |
+
|
| 420 |
+
# Update progress description
|
| 421 |
+
pbar.set_postfix(loss=f"{loss.item():.6f}")
|
| 422 |
+
|
| 423 |
+
loss_hist.append(loss.item())
|
| 424 |
+
|
| 425 |
+
loss_hist_total = loss_hist
|
| 426 |
+
|
| 427 |
+
return loss_hist_total
|
scripts/train.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
import time
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 12 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 13 |
+
|
| 14 |
+
from scripts.common import (
|
| 15 |
+
PROBLEMS,
|
| 16 |
+
build_models,
|
| 17 |
+
dump_json,
|
| 18 |
+
ensure_onescience_path,
|
| 19 |
+
load_config,
|
| 20 |
+
resolve_path,
|
| 21 |
+
save_checkpoint,
|
| 22 |
+
select_device,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
ensure_onescience_path()
|
| 26 |
+
from scripts.topology_optimization import find_TO
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def parse_args():
|
| 30 |
+
parser = argparse.ArgumentParser(description="Train GP_for_TO topology optimization models.")
|
| 31 |
+
parser.add_argument("--problem", choices=PROBLEMS, default=None)
|
| 32 |
+
parser.add_argument("--gpu", type=int, default=None)
|
| 33 |
+
parser.add_argument("--device", default=None)
|
| 34 |
+
parser.add_argument("--num-iter", type=int, default=None)
|
| 35 |
+
parser.add_argument("--n-col-domain", type=int, default=None)
|
| 36 |
+
parser.add_argument("--n-train-per-bc", type=int, default=None)
|
| 37 |
+
parser.add_argument("--diff-method", choices=("Numerical", "Autograd"), default=None)
|
| 38 |
+
parser.add_argument("--lr", type=float, default=None)
|
| 39 |
+
parser.add_argument("--checkpoint-path", default=None)
|
| 40 |
+
parser.add_argument("--output-dir", default=None)
|
| 41 |
+
parser.add_argument("--no-plot", action="store_true")
|
| 42 |
+
return parser.parse_args()
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def main():
|
| 46 |
+
args = parse_args()
|
| 47 |
+
cfg = load_config()
|
| 48 |
+
|
| 49 |
+
if args.problem:
|
| 50 |
+
cfg["problem"] = args.problem
|
| 51 |
+
if args.gpu is not None:
|
| 52 |
+
cfg["training"]["gpu"] = args.gpu
|
| 53 |
+
if args.device:
|
| 54 |
+
cfg["training"]["device"] = args.device
|
| 55 |
+
if args.num_iter is not None:
|
| 56 |
+
cfg["training"]["num_iter"] = args.num_iter
|
| 57 |
+
if args.n_col_domain is not None:
|
| 58 |
+
cfg["data"]["n_col_domain"] = args.n_col_domain
|
| 59 |
+
if args.n_train_per_bc is not None:
|
| 60 |
+
cfg["data"]["n_train_per_bc"] = args.n_train_per_bc
|
| 61 |
+
if args.diff_method:
|
| 62 |
+
cfg["training"]["diff_method"] = args.diff_method
|
| 63 |
+
if args.lr is not None:
|
| 64 |
+
cfg["training"]["lr_default"] = args.lr
|
| 65 |
+
if args.checkpoint_path:
|
| 66 |
+
cfg["training"]["checkpoint_path"] = args.checkpoint_path
|
| 67 |
+
if args.output_dir:
|
| 68 |
+
cfg["training"]["output_dir"] = args.output_dir
|
| 69 |
+
if args.no_plot:
|
| 70 |
+
cfg["training"]["plot_outputs"] = False
|
| 71 |
+
|
| 72 |
+
os.chdir(PROJECT_ROOT)
|
| 73 |
+
device = select_device(cfg["training"])
|
| 74 |
+
models, metadata = build_models(
|
| 75 |
+
cfg,
|
| 76 |
+
device,
|
| 77 |
+
n_col_domain=cfg["data"]["n_col_domain"],
|
| 78 |
+
n_train_per_bc=cfg["data"]["n_train_per_bc"],
|
| 79 |
+
problem=cfg["problem"],
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
title = f"seed{cfg['seed']}_{cfg['problem']}_{datetime.now().strftime('%B%d_%H-%M')}"
|
| 83 |
+
start_time = time.time()
|
| 84 |
+
loss_history = find_TO(
|
| 85 |
+
model_list=models,
|
| 86 |
+
num_iter=int(cfg["training"]["num_iter"]),
|
| 87 |
+
lr_default=float(cfg["training"]["lr_default"]),
|
| 88 |
+
title=title,
|
| 89 |
+
problem=cfg["problem"],
|
| 90 |
+
diff_method=cfg["training"]["diff_method"],
|
| 91 |
+
checkpoint_steps=list(cfg["training"].get("checkpoints", [])),
|
| 92 |
+
plot_outputs=bool(cfg["training"].get("plot_outputs", True)),
|
| 93 |
+
)
|
| 94 |
+
elapsed = time.time() - start_time
|
| 95 |
+
|
| 96 |
+
output_dir = resolve_path(cfg["training"]["output_dir"])
|
| 97 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 98 |
+
np.save(output_dir / "loss_history.npy", np.asarray(loss_history, dtype=np.float64))
|
| 99 |
+
summary = {
|
| 100 |
+
**metadata,
|
| 101 |
+
"device": str(device),
|
| 102 |
+
"num_iter": int(cfg["training"]["num_iter"]),
|
| 103 |
+
"diff_method": cfg["training"]["diff_method"],
|
| 104 |
+
"elapsed_seconds": elapsed,
|
| 105 |
+
"final_loss": float(loss_history[-1]) if loss_history else None,
|
| 106 |
+
}
|
| 107 |
+
dump_json(summary, output_dir / "training_summary.json")
|
| 108 |
+
ckpt_path = save_checkpoint(cfg["training"]["checkpoint_path"], models, cfg, summary, loss_history)
|
| 109 |
+
|
| 110 |
+
print(f"Training finished in {elapsed:.2f}s")
|
| 111 |
+
print(f"Loss history: {output_dir / 'loss_history.npy'}")
|
| 112 |
+
print(f"Checkpoint: {ckpt_path}")
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
if __name__ == "__main__":
|
| 116 |
+
main()
|
weight/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|