Image-to-Image
Transformers
Safetensors
fela_pde_fno2d
feature-extraction
fela
fourier-neural-operator
fno
cpu
on-device
pde-surrogate
thermal-simulation
battery
custom_code
Instructions to use lowdown-labs/fela-pde with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use lowdown-labs/fela-pde with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-to-image", model="lowdown-labs/fela-pde", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("lowdown-labs/fela-pde", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Commit ·
cdcc0fd
0
Parent(s):
initial commit
Browse files- .gitattributes +1 -0
- .gitignore +6 -0
- LICENSE +474 -0
- README.md +218 -0
- config.json +82 -0
- configuration_pde.py +47 -0
- example.py +28 -0
- input_builder.py +117 -0
- model.safetensors +3 -0
- modeling.py +132 -0
- modeling_pde.py +61 -0
- pack.csv +2 -0
- pack.json +13 -0
- streaming/manifest.json +14 -0
- streaming/model_fp16.safetensors +3 -0
- train.py +417 -0
- verify.py +44 -0
.gitattributes
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.ruff_cache/
|
| 2 |
+
.venv/
|
| 3 |
+
__pycache__/
|
| 4 |
+
*.swp
|
| 5 |
+
*.kate-swp
|
| 6 |
+
*~
|
LICENSE
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Lowdown Labs Lovely License 1.0 (LLLL-1.0)
|
| 2 |
+
|
| 3 |
+
Everything in this repository (the model weights, the configuration, and the code) is
|
| 4 |
+
released by Lowdown Labs under two licenses that apply at the same time. To use this work
|
| 5 |
+
you must comply with BOTH of them. Where a term in one is stricter than the other, the
|
| 6 |
+
stricter term controls. "Lowdown Labs Lovely License 1.0" is a convenience name for this
|
| 7 |
+
exact pair; it is not a new legal instrument.
|
| 8 |
+
|
| 9 |
+
SPDX-License-Identifier: CC-BY-NC-4.0 AND LicenseRef-Hippocratic-3.0
|
| 10 |
+
|
| 11 |
+
Commercial licensing. The grant below is non-commercial only. Commercial licenses are
|
| 12 |
+
sold separately by Lowdown Labs on a per-customer basis. To use this work, its weights, or
|
| 13 |
+
its outputs for any commercial purpose, contact Lowdown Labs to purchase a commercial
|
| 14 |
+
license. A commercial license does not remove the Hippocratic ethical-use obligations in
|
| 15 |
+
Part 2; those apply to commercial licensees as well.
|
| 16 |
+
|
| 17 |
+
==============================================================================
|
| 18 |
+
Part 1 of 2. Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)
|
| 19 |
+
==============================================================================
|
| 20 |
+
|
| 21 |
+
You may share and adapt this work for non-commercial purposes, with attribution to
|
| 22 |
+
Lowdown Labs. Commercial use is not granted under this license.
|
| 23 |
+
|
| 24 |
+
Full legal text: https://creativecommons.org/licenses/by-nc/4.0/legalcode
|
| 25 |
+
Plain-language summary: https://creativecommons.org/licenses/by-nc/4.0/
|
| 26 |
+
SPDX-License-Identifier: CC-BY-NC-4.0
|
| 27 |
+
|
| 28 |
+
==============================================================================
|
| 29 |
+
Part 2 of 2. The Hippocratic License 3.0 (ethical use)
|
| 30 |
+
==============================================================================
|
| 31 |
+
|
| 32 |
+
Module set enabled: bds, cl, eco, extr, ffd, law, media, mil, my, soc, sup, sv, usta.
|
| 33 |
+
Canonical build: https://firstdonoharm.dev/build/?modules=bds,cl,eco,extr,ffd,law,media,mil,my,soc,sup,sv,usta
|
| 34 |
+
|
| 35 |
+
The verbatim official Hippocratic License 3.0 text for exactly this module set follows,
|
| 36 |
+
between the markers.
|
| 37 |
+
|
| 38 |
+
--------------------- BEGIN OFFICIAL HIPPOCRATIC LICENSE 3.0 TEXT ---------------------
|
| 39 |
+
|
| 40 |
+
HIPPOCRATIC LICENSE
|
| 41 |
+
|
| 42 |
+
Version 3.0, October 2021
|
| 43 |
+
|
| 44 |
+
https://firstdonoharm.dev/version/3/0/bds-cl-eco-extr-ffd-law-media-mil-my-soc-sup-sv-usta.txt
|
| 45 |
+
|
| 46 |
+
TERMS AND CONDITIONS
|
| 47 |
+
|
| 48 |
+
TERMS AND CONDITIONS FOR USE, COPY, MODIFICATION, PREPARATION OF DERIVATIVE
|
| 49 |
+
WORK, REPRODUCTION, AND DISTRIBUTION:
|
| 50 |
+
|
| 51 |
+
1. DEFINITIONS:
|
| 52 |
+
|
| 53 |
+
This section defines certain terms used throughout this license agreement.
|
| 54 |
+
|
| 55 |
+
1.1. “License” means the terms and conditions, as stated herein, for use, copy,
|
| 56 |
+
modification, preparation of derivative work, reproduction, and distribution of
|
| 57 |
+
Software (as defined below).
|
| 58 |
+
|
| 59 |
+
1.2. “Licensor” means the copyright and/or patent owner or entity authorized by
|
| 60 |
+
the copyright and/or patent owner that is granting the License.
|
| 61 |
+
|
| 62 |
+
1.3. “Licensee” means the individual or entity exercising permissions granted by
|
| 63 |
+
this License, including the use, copy, modification, preparation of derivative
|
| 64 |
+
work, reproduction, and distribution of Software (as defined below).
|
| 65 |
+
|
| 66 |
+
1.4. “Software” means any copyrighted work, including but not limited to
|
| 67 |
+
software code, authored by Licensor and made available under this License.
|
| 68 |
+
|
| 69 |
+
1.5. “Supply Chain” means the sequence of processes involved in the production
|
| 70 |
+
and/or distribution of a commodity, good, or service offered by the Licensee.
|
| 71 |
+
|
| 72 |
+
1.6. “Supply Chain Impacted Party” or “Supply Chain Impacted Parties” means any
|
| 73 |
+
person(s) directly impacted by any of Licensee’s Supply Chain, including the
|
| 74 |
+
practices of all persons or entities within the Supply Chain prior to a good or
|
| 75 |
+
service reaching the Licensee.
|
| 76 |
+
|
| 77 |
+
1.7. “Duty of Care” is defined by its use in tort law, delict law, and/or
|
| 78 |
+
similar bodies of law closely related to tort and/or delict law, including
|
| 79 |
+
without limitation, a requirement to act with the watchfulness, attention,
|
| 80 |
+
caution, and prudence that a reasonable person in the same or similar
|
| 81 |
+
circumstances would use towards any Supply Chain Impacted Party.
|
| 82 |
+
|
| 83 |
+
1.8. “Worker” is defined to include any and all permanent, temporary, and agency
|
| 84 |
+
workers, as well as piece-rate, salaried, hourly paid, legal young (minors),
|
| 85 |
+
part-time, night, and migrant workers.
|
| 86 |
+
|
| 87 |
+
2. INTELLECTUAL PROPERTY GRANTS:
|
| 88 |
+
|
| 89 |
+
This section identifies intellectual property rights granted to a Licensee.
|
| 90 |
+
|
| 91 |
+
2.1. Grant of Copyright License: Subject to the terms and conditions of this
|
| 92 |
+
License, Licensor hereby grants to Licensee a worldwide, non-exclusive,
|
| 93 |
+
no-charge, royalty-free copyright license to use, copy, modify, prepare
|
| 94 |
+
derivative work, reproduce, or distribute the Software, Licensor authored
|
| 95 |
+
modified software, or other work derived from the Software.
|
| 96 |
+
|
| 97 |
+
2.2. Grant of Patent License: Subject to the terms and conditions of this
|
| 98 |
+
License, Licensor hereby grants Licensee a worldwide, non-exclusive, no-charge,
|
| 99 |
+
royalty-free patent license to make, have made, use, offer to sell, sell,
|
| 100 |
+
import, and otherwise transfer Software.
|
| 101 |
+
|
| 102 |
+
3. ETHICAL STANDARDS:
|
| 103 |
+
|
| 104 |
+
This section lists conditions the Licensee must comply with in order to have
|
| 105 |
+
rights under this License.
|
| 106 |
+
|
| 107 |
+
The rights granted to the Licensee by this License are expressly made subject to
|
| 108 |
+
the Licensee’s ongoing compliance with the following conditions:
|
| 109 |
+
|
| 110 |
+
* 3.1. The Licensee SHALL NOT, whether directly or indirectly, through agents
|
| 111 |
+
or assigns:
|
| 112 |
+
|
| 113 |
+
* 3.1.1. Infringe upon any person’s right to life or security of person,
|
| 114 |
+
engage in extrajudicial killings, or commit murder, without lawful cause
|
| 115 |
+
(See Article 3, United Nations Universal Declaration of Human Rights;
|
| 116 |
+
Article 6, International Covenant on Civil and Political Rights)
|
| 117 |
+
|
| 118 |
+
* 3.1.2. Hold any person in slavery, servitude, or forced labor (See Article
|
| 119 |
+
4, United Nations Universal Declaration of Human Rights; Article 8,
|
| 120 |
+
International Covenant on Civil and Political Rights);
|
| 121 |
+
|
| 122 |
+
* 3.1.3. Contribute to the institution of slavery, slave trading, forced
|
| 123 |
+
labor, or unlawful child labor (See Article 4, United Nations Universal
|
| 124 |
+
Declaration of Human Rights; Article 8, International Covenant on Civil and
|
| 125 |
+
Political Rights);
|
| 126 |
+
|
| 127 |
+
* 3.1.4. Torture or subject any person to cruel, inhumane, or degrading
|
| 128 |
+
treatment or punishment (See Article 5, United Nations Universal
|
| 129 |
+
Declaration of Human Rights; Article 7, International Covenant on Civil and
|
| 130 |
+
Political Rights);
|
| 131 |
+
|
| 132 |
+
* 3.1.5. Discriminate on the basis of sex, gender, sexual orientation, race,
|
| 133 |
+
ethnicity, nationality, religion, caste, age, medical disability or
|
| 134 |
+
impairment, and/or any other like circumstances (See Article 7, United
|
| 135 |
+
Nations Universal Declaration of Human Rights; Article 2, International
|
| 136 |
+
Covenant on Economic, Social and Cultural Rights; Article 26, International
|
| 137 |
+
Covenant on Civil and Political Rights);
|
| 138 |
+
|
| 139 |
+
* 3.1.6. Prevent any person from exercising his/her/their right to seek an
|
| 140 |
+
effective remedy by a competent court or national tribunal (including
|
| 141 |
+
domestic judicial systems, international courts, arbitration bodies, and
|
| 142 |
+
other adjudicating bodies) for actions violating the fundamental rights
|
| 143 |
+
granted to him/her/them by applicable constitutions, applicable laws, or by
|
| 144 |
+
this License (See Article 8, United Nations Universal Declaration of Human
|
| 145 |
+
Rights; Articles 9 and 14, International Covenant on Civil and Political
|
| 146 |
+
Rights);
|
| 147 |
+
|
| 148 |
+
* 3.1.7. Subject any person to arbitrary arrest, detention, or exile (See
|
| 149 |
+
Article 9, United Nations Universal Declaration of Human Rights; Article 9,
|
| 150 |
+
International Covenant on Civil and Political Rights);
|
| 151 |
+
|
| 152 |
+
* 3.1.8. Subject any person to arbitrary interference with a person’s
|
| 153 |
+
privacy, family, home, or correspondence without the express written
|
| 154 |
+
consent of the person (See Article 12, United Nations Universal Declaration
|
| 155 |
+
of Human Rights; Article 17, International Covenant on Civil and Political
|
| 156 |
+
Rights);
|
| 157 |
+
|
| 158 |
+
* 3.1.9. Arbitrarily deprive any person of his/her/their property (See
|
| 159 |
+
Article 17, United Nations Universal Declaration of Human Rights);
|
| 160 |
+
|
| 161 |
+
* 3.1.10. Forcibly remove indigenous peoples from their lands or territories
|
| 162 |
+
or take any action with the aim or effect of dispossessing indigenous
|
| 163 |
+
peoples from their lands, territories, or resources, including without
|
| 164 |
+
limitation the intellectual property or traditional knowledge of indigenous
|
| 165 |
+
peoples, without the free, prior, and informed consent of indigenous
|
| 166 |
+
peoples concerned (See Articles 8 and 10, United Nations Declaration on the
|
| 167 |
+
Rights of Indigenous Peoples);
|
| 168 |
+
* 3.1.11. Fossil Fuel Divestment: Be an individual or entity, or a
|
| 169 |
+
representative, agent, affiliate, successor, attorney, or assign of an
|
| 170 |
+
individual or entity, on the FFI Solutions Carbon Underground 200 list
|
| 171 |
+
[https://www.ffisolutions.com/research-analytics-index-solutions/research-screening/the-carbon-underground-200/?cn-reloaded=1];
|
| 172 |
+
|
| 173 |
+
* 3.1.12. Ecocide: Commit ecocide:
|
| 174 |
+
|
| 175 |
+
* 3.1.12.1. For the purpose of this section, “ecocide” means unlawful or
|
| 176 |
+
wanton acts committed with knowledge that there is a substantial
|
| 177 |
+
likelihood of severe and either widespread or long-term damage to the
|
| 178 |
+
environment being caused by those acts;
|
| 179 |
+
|
| 180 |
+
* 3.1.12.2. For the purpose of further defining ecocide and the terms
|
| 181 |
+
contained in the previous paragraph:
|
| 182 |
+
|
| 183 |
+
* 3.1.12.2.1. “Wanton” means with reckless disregard for damage which
|
| 184 |
+
would be clearly excessive in relation to the social and economic
|
| 185 |
+
benefits anticipated;
|
| 186 |
+
|
| 187 |
+
* 3.1.12.2.2. “Severe” means damage which involves very serious adverse
|
| 188 |
+
changes, disruption, or harm to any element of the environment,
|
| 189 |
+
including grave impacts on human life or natural, cultural, or
|
| 190 |
+
economic resources;
|
| 191 |
+
|
| 192 |
+
* 3.1.12.2.3. “Widespread” means damage which extends beyond a limited
|
| 193 |
+
geographic area, crosses state boundaries, or is suffered by an entire
|
| 194 |
+
ecosystem or species or a large number of human beings;
|
| 195 |
+
|
| 196 |
+
* 3.1.12.2.4. “Long-term” means damage which is irreversible or which
|
| 197 |
+
cannot be redressed through natural recovery within a reasonable
|
| 198 |
+
period of time; and
|
| 199 |
+
|
| 200 |
+
* 3.1.12.2.5. “Environment” means the earth, its biosphere, cryosphere,
|
| 201 |
+
lithosphere, hydrosphere, and atmosphere, as well as outer space
|
| 202 |
+
|
| 203 |
+
(See Section II, Independent Expert Panel for the Legal Definition of
|
| 204 |
+
Ecocide, Stop Ecocide Foundation and the Promise Institute for Human
|
| 205 |
+
Rights at UCLA School of Law, June 2021);
|
| 206 |
+
|
| 207 |
+
* 3.1.13. Extractive Industries: Be an individual or entity, or a
|
| 208 |
+
representative, agent, affiliate, successor, attorney, or assign of an
|
| 209 |
+
individual or entity, that engages in fossil fuel or mineral exploration,
|
| 210 |
+
extraction, development, or sale;
|
| 211 |
+
|
| 212 |
+
* 3.1.14. Boycott / Divestment / Sanctions: Be an individual or entity, or a
|
| 213 |
+
representative, agent, affiliate, successor, attorney, or assign of an
|
| 214 |
+
individual or entity, identified by the Boycott, Divestment, Sanctions
|
| 215 |
+
(“BDS”) movement on its website (https://bdsmovement.net/
|
| 216 |
+
[https://bdsmovement.net/] and
|
| 217 |
+
https://bdsmovement.net/get-involved/what-to-boycott
|
| 218 |
+
[https://bdsmovement.net/get-involved/what-to-boycott]) as a target for
|
| 219 |
+
boycott;
|
| 220 |
+
|
| 221 |
+
* 3.1.15. Myanmar: Be an individual or entity that:
|
| 222 |
+
|
| 223 |
+
* 3.1.15.1. engages in any commercial transactions with the
|
| 224 |
+
Myanmar/Burmese military junta; or
|
| 225 |
+
|
| 226 |
+
* 3.1.15.2. is a representative, agent, affiliate, successor, attorney, or
|
| 227 |
+
assign of the Myanmar/Burmese government;
|
| 228 |
+
|
| 229 |
+
* 3.1.16. US Tariff Act: Be an individual or entity:
|
| 230 |
+
|
| 231 |
+
* 3.1.16.1. which U.S. Customs and Border Protection (CBP) has currently
|
| 232 |
+
issued a Withhold Release Order (WRO) or finding against based on
|
| 233 |
+
reasonable suspicion of forced labor; or
|
| 234 |
+
|
| 235 |
+
* 3.1.16.2. that is a representative, agent, affiliate, successor,
|
| 236 |
+
attorney, or assign of an individual or entity that does business with
|
| 237 |
+
an individual or entity which currently has a WRO or finding from CBP
|
| 238 |
+
issued against it based on reasonable suspicion of forced labor;
|
| 239 |
+
|
| 240 |
+
* 3.1.17. Mass Surveillance: Be a government agency or multinational
|
| 241 |
+
corporation, or a representative, agent, affiliate, successor, attorney,
|
| 242 |
+
or assign of a government or multinational corporation, which participates
|
| 243 |
+
in mass surveillance programs;
|
| 244 |
+
|
| 245 |
+
* 3.1.18. Military Activities: Be an entity or a representative, agent,
|
| 246 |
+
affiliate, successor, attorney, or assign of an entity which conducts
|
| 247 |
+
military activities;
|
| 248 |
+
|
| 249 |
+
* 3.1.19. Law Enforcement: Be an individual or entity, or a representative,
|
| 250 |
+
agent, affiliate, successor, attorney, or assign of an individual or
|
| 251 |
+
entity, that provides good or services to, or otherwise enters into any
|
| 252 |
+
commercial contracts with, any local, state, or federal law enforcement
|
| 253 |
+
agency;
|
| 254 |
+
|
| 255 |
+
* 3.1.20. Media: Be an individual or entity, or a representative, agent,
|
| 256 |
+
affiliate, successor, attorney, or assign of an individual or entity, that
|
| 257 |
+
broadcasts messages promoting killing, torture, or other forms of extreme
|
| 258 |
+
violence;
|
| 259 |
+
|
| 260 |
+
* 3.1.21. Interfere with Workers’ free exercise of the right to organize and
|
| 261 |
+
associate (See Article 20, United Nations Universal Declaration of Human
|
| 262 |
+
Rights; C087 - Freedom of Association and Protection of the Right to
|
| 263 |
+
Organise Convention, 1948 (No. 87), International Labour Organization;
|
| 264 |
+
Article 8, International Covenant on Economic, Social and Cultural Rights);
|
| 265 |
+
and
|
| 266 |
+
|
| 267 |
+
* 3.1.22. Harm the environment in a manner inconsistent with local, state,
|
| 268 |
+
national, or international law.
|
| 269 |
+
|
| 270 |
+
* 3.2. The Licensee SHALL:
|
| 271 |
+
|
| 272 |
+
* 3.2.1. Social Auditing: Only use social auditing mechanisms that adhere to
|
| 273 |
+
Worker-Driven Social Responsibility Network’s Statement of Principles
|
| 274 |
+
(https://wsr-network.org/what-is-wsr/statement-of-principles/
|
| 275 |
+
[https://wsr-network.org/what-is-wsr/statement-of-principles/]) over
|
| 276 |
+
traditional social auditing mechanisms, to the extent the Licensee uses
|
| 277 |
+
any social auditing mechanisms at all;
|
| 278 |
+
|
| 279 |
+
* 3.2.2. Supply Chain: Provide clear, accessible supply chain data to the
|
| 280 |
+
public in accordance with the following conditions:
|
| 281 |
+
|
| 282 |
+
* 3.2.2.1. All data will be on Licensee’s website and/or, to the extent
|
| 283 |
+
Licensee is a representative, agent, affiliate, successor, attorney,
|
| 284 |
+
subsidiary, or assign, on Licensee’s principal’s or parent’s website or
|
| 285 |
+
some other online platform accessible to the public via an internet
|
| 286 |
+
search on a common internet search engine; and
|
| 287 |
+
|
| 288 |
+
* 3.2.2.2. Data published will include, where applicable, manufacturers,
|
| 289 |
+
top tier suppliers, subcontractors, cooperatives, component parts
|
| 290 |
+
producers, and farms;
|
| 291 |
+
|
| 292 |
+
* 3.2.3. Provide equal pay for equal work where the performance of such work
|
| 293 |
+
requires equal skill, effort, and responsibility, and which are performed
|
| 294 |
+
under similar working conditions, except where such payment is made
|
| 295 |
+
pursuant to:
|
| 296 |
+
|
| 297 |
+
* 3.2.3.1. A seniority system;
|
| 298 |
+
|
| 299 |
+
* 3.2.3.2. A merit system;
|
| 300 |
+
|
| 301 |
+
* 3.2.3.3. A system which measures earnings by quantity or quality of
|
| 302 |
+
production; or
|
| 303 |
+
|
| 304 |
+
* 3.2.3.4. A differential based on any other factor other than sex, gender,
|
| 305 |
+
sexual orientation, race, ethnicity, nationality, religion, caste, age,
|
| 306 |
+
medical disability or impairment, and/or any other like circumstances
|
| 307 |
+
(See 29 U.S.C.A. § 206(d)(1); Article 23, United Nations Universal
|
| 308 |
+
Declaration of Human Rights; Article 7, International Covenant on
|
| 309 |
+
Economic, Social and Cultural Rights; Article 26, International Covenant
|
| 310 |
+
on Civil and Political Rights); and
|
| 311 |
+
|
| 312 |
+
* 3.2.4. Allow for reasonable limitation of working hours and periodic
|
| 313 |
+
holidays with pay (See Article 24, United Nations Universal Declaration of
|
| 314 |
+
Human Rights; Article 7, International Covenant on Economic, Social and
|
| 315 |
+
Cultural Rights).
|
| 316 |
+
|
| 317 |
+
4. SUPPLY CHAIN IMPACTED PARTIES:
|
| 318 |
+
|
| 319 |
+
This section identifies additional individuals or entities that a Licensee could
|
| 320 |
+
harm as a result of violating the Ethical Standards section, the condition that
|
| 321 |
+
the Licensee must voluntarily accept a Duty of Care for those individuals or
|
| 322 |
+
entities, and the right to a private right of action that those individuals or
|
| 323 |
+
entities possess as a result of violations of the Ethical Standards section.
|
| 324 |
+
|
| 325 |
+
4.1. In addition to the above Ethical Standards, Licensee voluntarily accepts a
|
| 326 |
+
Duty of Care for Supply Chain Impacted Parties of this License, including
|
| 327 |
+
individuals and communities impacted by violations of the Ethical Standards. The
|
| 328 |
+
Duty of Care is breached when a provision within the Ethical Standards section
|
| 329 |
+
is violated by a Licensee, one of its successors or assigns, or by an individual
|
| 330 |
+
or entity that exists within the Supply Chain prior to a good or service
|
| 331 |
+
reaching the Licensee.
|
| 332 |
+
|
| 333 |
+
4.2. Breaches of the Duty of Care, as stated within this section, shall create a
|
| 334 |
+
private right of action, allowing any Supply Chain Impacted Party harmed by the
|
| 335 |
+
Licensee to take legal action against the Licensee in accordance with applicable
|
| 336 |
+
negligence laws, whether they be in tort law, delict law, and/or similar bodies
|
| 337 |
+
of law closely related to tort and/or delict law, regardless if Licensee is
|
| 338 |
+
directly responsible for the harms suffered by a Supply Chain Impacted Party.
|
| 339 |
+
Nothing in this section shall be interpreted to include acts committed by
|
| 340 |
+
individuals outside of the scope of his/her/their employment.
|
| 341 |
+
|
| 342 |
+
5. NOTICE: This section explains when a Licensee must notify others of the
|
| 343 |
+
License.
|
| 344 |
+
|
| 345 |
+
5.1. Distribution of Notice: Licensee must ensure that everyone who receives a
|
| 346 |
+
copy of or uses any part of Software from Licensee, with or without changes,
|
| 347 |
+
also receives the License and the copyright notice included with Software (and
|
| 348 |
+
if included by the Licensor, patent, trademark, and attribution notice).
|
| 349 |
+
Licensee must ensure that License is prominently displayed so that any
|
| 350 |
+
individual or entity seeking to download, copy, use, or otherwise receive any
|
| 351 |
+
part of Software from Licensee is notified of this License and its terms and
|
| 352 |
+
conditions. Licensee must cause any modified versions of the Software to carry
|
| 353 |
+
prominent notices stating that Licensee changed the Software.
|
| 354 |
+
|
| 355 |
+
5.2. Modified Software: Licensee is free to create modifications of the Software
|
| 356 |
+
and distribute only the modified portion created by Licensee, however, any
|
| 357 |
+
derivative work stemming from the Software or its code must be distributed
|
| 358 |
+
pursuant to this License, including this Notice provision.
|
| 359 |
+
|
| 360 |
+
5.3. Recipients as Licensees: Any individual or entity that uses, copies,
|
| 361 |
+
modifies, reproduces, distributes, or prepares derivative work based upon the
|
| 362 |
+
Software, all or part of the Software’s code, or a derivative work developed by
|
| 363 |
+
using the Software, including a portion of its code, is a Licensee as defined
|
| 364 |
+
above and is subject to the terms and conditions of this License.
|
| 365 |
+
|
| 366 |
+
6. REPRESENTATIONS AND WARRANTIES:
|
| 367 |
+
|
| 368 |
+
6.1. Disclaimer of Warranty: TO THE FULL EXTENT ALLOWED BY LAW, THIS SOFTWARE
|
| 369 |
+
COMES “AS IS,” WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED, AND LICENSOR SHALL NOT
|
| 370 |
+
BE LIABLE TO ANY PERSON OR ENTITY FOR ANY DAMAGES OR OTHER LIABILITY ARISING
|
| 371 |
+
FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THIS LICENSE, UNDER ANY
|
| 372 |
+
LEGAL CLAIM.
|
| 373 |
+
|
| 374 |
+
6.2. Limitation of Liability: LICENSEE SHALL HOLD LICENSOR HARMLESS AGAINST ANY
|
| 375 |
+
AND ALL CLAIMS, DEBTS, DUES, LIABILITIES, LIENS, CAUSES OF ACTION, DEMANDS,
|
| 376 |
+
OBLIGATIONS, DISPUTES, DAMAGES, LOSSES, EXPENSES, ATTORNEYS’ FEES, COSTS,
|
| 377 |
+
LIABILITIES, AND ALL OTHER CLAIMS OF EVERY KIND AND NATURE WHATSOEVER, WHETHER
|
| 378 |
+
KNOWN OR UNKNOWN, ANTICIPATED OR UNANTICIPATED, FORESEEN OR UNFORESEEN, ACCRUED
|
| 379 |
+
OR UNACCRUED, DISCLOSED OR UNDISCLOSED, ARISING OUT OF OR RELATING TO LICENSEE’S
|
| 380 |
+
USE OF THE SOFTWARE. NOTHING IN THIS SECTION SHOULD BE INTERPRETED TO REQUIRE
|
| 381 |
+
LICENSEE TO INDEMNIFY LICENSOR, NOR REQUIRE LICENSOR TO INDEMNIFY LICENSEE.
|
| 382 |
+
|
| 383 |
+
7. TERMINATION
|
| 384 |
+
|
| 385 |
+
7.1. Violations of Ethical Standards or Breaching Duty of Care: If Licensee
|
| 386 |
+
violates the Ethical Standards section or Licensee, or any other person or
|
| 387 |
+
entity within the Supply Chain prior to a good or service reaching the Licensee,
|
| 388 |
+
breaches its Duty of Care to Supply Chain Impacted Parties, Licensee must remedy
|
| 389 |
+
the violation or harm caused by Licensee within 30 days of being notified of the
|
| 390 |
+
violation or harm. If Licensee fails to remedy the violation or harm within 30
|
| 391 |
+
days, all rights in the Software granted to Licensee by License will be null and
|
| 392 |
+
void as between Licensor and Licensee.
|
| 393 |
+
|
| 394 |
+
7.2. Failure of Notice: If any person or entity notifies Licensee in writing
|
| 395 |
+
that Licensee has not complied with the Notice section of this License, Licensee
|
| 396 |
+
can keep this License by taking all practical steps to comply within 30 days
|
| 397 |
+
after the notice of noncompliance. If Licensee does not do so, Licensee’s
|
| 398 |
+
License (and all rights licensed hereunder) will end immediately.
|
| 399 |
+
|
| 400 |
+
7.3. Judicial Findings: In the event Licensee is found by a civil, criminal,
|
| 401 |
+
administrative, or other court of competent jurisdiction, or some other
|
| 402 |
+
adjudicating body with legal authority, to have committed actions which are in
|
| 403 |
+
violation of the Ethical Standards or Supply Chain Impacted Party sections of
|
| 404 |
+
this License, all rights granted to Licensee by this License will terminate
|
| 405 |
+
immediately.
|
| 406 |
+
|
| 407 |
+
7.4. Patent Litigation: If Licensee institutes patent litigation against any
|
| 408 |
+
entity (including a cross-claim or counterclaim in a suit) alleging that the
|
| 409 |
+
Software, all or part of the Software’s code, or a derivative work developed
|
| 410 |
+
using the Software, including a portion of its code, constitutes direct or
|
| 411 |
+
contributory patent infringement, then any patent license, along with all other
|
| 412 |
+
rights, granted to Licensee under this License will terminate as of the date
|
| 413 |
+
such litigation is filed.
|
| 414 |
+
|
| 415 |
+
7.5. Additional Remedies: Termination of the License by failing to remedy harms
|
| 416 |
+
in no way prevents Licensor or Supply Chain Impacted Party from seeking
|
| 417 |
+
appropriate remedies at law or in equity.
|
| 418 |
+
|
| 419 |
+
8. MISCELLANEOUS:
|
| 420 |
+
|
| 421 |
+
8.1. Conditions: Sections 3, 4.1, 5.1, 5.2, 7.1, 7.2, 7.3, and 7.4 are
|
| 422 |
+
conditions of the rights granted to Licensee in the License.
|
| 423 |
+
|
| 424 |
+
8.2. Equitable Relief: Licensor and any Supply Chain Impacted Party shall be
|
| 425 |
+
entitled to equitable relief, including injunctive relief or specific
|
| 426 |
+
performance of the terms hereof, in addition to any other remedy to which they
|
| 427 |
+
are entitled at law or in equity.
|
| 428 |
+
|
| 429 |
+
8.3. Copyleft: Modified software, source code, or other derivative work must be
|
| 430 |
+
licensed, in its entirety, under the exact same conditions as this License.
|
| 431 |
+
|
| 432 |
+
8.4. Severability: If any term or provision of this License is determined to be
|
| 433 |
+
invalid, illegal, or unenforceable by a court of competent jurisdiction, any
|
| 434 |
+
such determination of invalidity, illegality, or unenforceability shall not
|
| 435 |
+
affect any other term or provision of this License or invalidate or render
|
| 436 |
+
unenforceable such term or provision in any other jurisdiction. If the
|
| 437 |
+
determination of invalidity, illegality, or unenforceability by a court of
|
| 438 |
+
competent jurisdiction pertains to the terms or provisions contained in the
|
| 439 |
+
Ethical Standards section of this License, all rights in the Software granted to
|
| 440 |
+
Licensee shall be deemed null and void as between Licensor and Licensee.
|
| 441 |
+
|
| 442 |
+
8.5. Section Titles: Section titles are solely written for organizational
|
| 443 |
+
purposes and should not be used to interpret the language within each section.
|
| 444 |
+
|
| 445 |
+
8.6. Citations: Citations are solely written to provide context for the source
|
| 446 |
+
of the provisions in the Ethical Standards.
|
| 447 |
+
|
| 448 |
+
8.7. Section Summaries: Some sections have a brief italicized description which
|
| 449 |
+
is provided for the sole purpose of briefly describing the section and should
|
| 450 |
+
not be used to interpret the terms of the License.
|
| 451 |
+
|
| 452 |
+
8.8. Entire License: This is the entire License between the Licensor and
|
| 453 |
+
Licensee with respect to the claims released herein and that the consideration
|
| 454 |
+
stated herein is the only consideration or compensation to be paid or exchanged
|
| 455 |
+
between them for this License. This License cannot be modified or amended except
|
| 456 |
+
in a writing signed by Licensor and Licensee.
|
| 457 |
+
|
| 458 |
+
8.9. Successors and Assigns: This License shall be binding upon and inure to the
|
| 459 |
+
benefit of the Licensor’s and Licensee’s respective heirs, successors, and
|
| 460 |
+
assigns.
|
| 461 |
+
---------------------- END OFFICIAL HIPPOCRATIC LICENSE 3.0 TEXT ----------------------
|
| 462 |
+
|
| 463 |
+
==============================================================================
|
| 464 |
+
Attribution, commercial use, and warranty
|
| 465 |
+
==============================================================================
|
| 466 |
+
|
| 467 |
+
Attribution: cite this work as described in the model card (README.md), section
|
| 468 |
+
"How to cite". Attribution to Lowdown Labs is required under CC BY-NC 4.0.
|
| 469 |
+
|
| 470 |
+
Commercial use: CC BY-NC 4.0 does not grant commercial rights. Commercial licenses are
|
| 471 |
+
sold by Lowdown Labs; contact Lowdown Labs to purchase one.
|
| 472 |
+
|
| 473 |
+
No warranty: this work is provided as is, without warranty of any kind. See the model card
|
| 474 |
+
for the intended use, the evaluated conditions, and the known limitations.
|
README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: other
|
| 3 |
+
license_name: lowdown-labs-lovely-license-1.0
|
| 4 |
+
license_link: LICENSE
|
| 5 |
+
tags:
|
| 6 |
+
- fela
|
| 7 |
+
- fourier-neural-operator
|
| 8 |
+
- fno
|
| 9 |
+
- cpu
|
| 10 |
+
- on-device
|
| 11 |
+
- pde-surrogate
|
| 12 |
+
- thermal-simulation
|
| 13 |
+
- battery
|
| 14 |
+
library_name: transformers
|
| 15 |
+
pipeline_tag: image-to-image
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
# DISCLAIMER
|
| 19 |
+
|
| 20 |
+
This model is a research preview. Lowdown Labs has put together
|
| 21 |
+
this model in the interest of advancing public science.
|
| 22 |
+
|
| 23 |
+
# FELA PDE: on device 2D thermal field surrogate for battery packs
|
| 24 |
+
|
| 25 |
+
Give FELA PDE the layout of a battery pack, its heat load, and how it is being cooled, and it
|
| 26 |
+
tells you where the pack runs hot. It returns the full steady state temperature map in one fast
|
| 27 |
+
pass, standing in for a slower finite volume solve. It runs on a plain CPU with no GPU, so it can
|
| 28 |
+
sit inside a battery management tool, a design loop, or an on premises engineering app and flag
|
| 29 |
+
hot spots without a cloud round trip.
|
| 30 |
+
|
| 31 |
+
What ships in this repo is the small web lite version: 892,545 parameters, about 7.1 MB in fp32.
|
| 32 |
+
The larger validated teacher is a separate line and is not shipped here.
|
| 33 |
+
|
| 34 |
+
# What goes in, what comes out
|
| 35 |
+
|
| 36 |
+
- Input: an 8 channel physics field on a 96x96 grid, shape `(1, 8, 96, 96)`. The channels, in
|
| 37 |
+
order, are `mask` (pack solid region), `q_source` (volumetric heat source, the hot spot),
|
| 38 |
+
`k_field` (thermal conductivity), `h_conv` (convective heat transfer coefficient), `T_amb`
|
| 39 |
+
(ambient temperature), `x_coord`, `y_coord` (normalized 0..1 coordinates), and
|
| 40 |
+
`log_domain_L` (log of the physical domain size). Each channel is standardized with the
|
| 41 |
+
training statistics that ship in `config.json`; `modeling.preprocess` does this for you.
|
| 42 |
+
- Output: a 1 channel normalized temperature field, shape `(1, 1, 96, 96)`.
|
| 43 |
+
`modeling.denormalize` converts it to degrees Celsius using the training y statistics
|
| 44 |
+
(`Y_degC = Ynorm * y_std + y_mean`).
|
| 45 |
+
- In plain terms: give it the pack geometry, the heat load, and the cooling conditions, and it
|
| 46 |
+
returns the predicted temperature map so an engineer can see where the pack runs hot.
|
| 47 |
+
|
| 48 |
+
# Building an input (for battery and BMS engineers)
|
| 49 |
+
|
| 50 |
+
You do not hand build the 8 channel tensor. `input_builder.py` (an add on shipped in this repo)
|
| 51 |
+
builds it from ordinary pack parameters, matching the exact encoding the model was trained on
|
| 52 |
+
(the coordinate planes, `log_domain_L`, and the per channel standardization from `config.json`).
|
| 53 |
+
Two of the channels and `log_domain_L` are model conventions, not physics you supply. The
|
| 54 |
+
physical channels and their units are:
|
| 55 |
+
|
| 56 |
+
| Channel | Meaning | Units | Typical range |
|
| 57 |
+
|---|---|---|---|
|
| 58 |
+
| mask | 1 inside a cell, 0 in the coolant | none | 0 or 1 |
|
| 59 |
+
| q_source | heat source density in the cells | W/m3 | derived from current, SoC, R0 |
|
| 60 |
+
| k_field | thermal conductivity | W/(m K) | cell 1 to 30, coolant 0.1 to 1.5 |
|
| 61 |
+
| h_conv | convective heat transfer coefficient | W/(m2 K) | 5 to 200 |
|
| 62 |
+
| T_amb | ambient temperature | degC | 15 to 40 |
|
| 63 |
+
| x_coord | normalized column position (the builder sets this) | none | 0 to 1 |
|
| 64 |
+
| y_coord | normalized row position (the builder sets this) | none | 0 to 1 |
|
| 65 |
+
| log_domain_L | natural log of the physical pack size (the builder sets this) | ln(m) | pack 0.02 to 0.12 m |
|
| 66 |
+
|
| 67 |
+
The model was trained on this distribution; inputs well outside these ranges are not characterized.
|
| 68 |
+
|
| 69 |
+
## The BMS path: from_pack
|
| 70 |
+
|
| 71 |
+
Give it a cell layout and pack parameters. It computes the heat source
|
| 72 |
+
(`P = current^2 * R0 * (1 + beta * (1 - SoC)^2)`, spread over the cell area), the conductivity map,
|
| 73 |
+
and the rest, then returns a ready to run `(1, 8, 96, 96)` tensor:
|
| 74 |
+
|
| 75 |
+
```python
|
| 76 |
+
import torch
|
| 77 |
+
from input_builder import from_pack, cylinder_mask
|
| 78 |
+
from modeling import load_model, denormalize
|
| 79 |
+
|
| 80 |
+
model = load_model(".")
|
| 81 |
+
mask = cylinder_mask(rows=3, cols=4, radius_frac=0.4) # a 3 by 4 cylindrical cell pack
|
| 82 |
+
x = from_pack(
|
| 83 |
+
mask,
|
| 84 |
+
current_A=40.0, soc=0.3, R0_ohm=0.02,
|
| 85 |
+
k_cell_W_mK=20.0, k_coolant_W_mK=0.6,
|
| 86 |
+
h_conv_W_m2K=80.0, T_amb_degC=25.0, domain_L_m=0.08,
|
| 87 |
+
)
|
| 88 |
+
with torch.no_grad():
|
| 89 |
+
T = denormalize(model(x))[0, 0] # a 96 by 96 temperature map in degC
|
| 90 |
+
print("peak", float(T.max()), "degC")
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
`cylinder_mask(rows, cols, radius_frac)` and `rect_mask(aspect, fill)` build the geometry mask.
|
| 94 |
+
`example.py` runs this end to end and prints the peak temperature and hottest cell.
|
| 95 |
+
|
| 96 |
+
## The field path: from_fields
|
| 97 |
+
|
| 98 |
+
If you already have physical field maps (say from your own thermal model), pass them directly
|
| 99 |
+
instead of pack parameters:
|
| 100 |
+
|
| 101 |
+
```python
|
| 102 |
+
from input_builder import from_fields
|
| 103 |
+
x = from_fields(mask, q_source_W_m3, k_field_W_mK, h_conv_W_m2K, T_amb_degC, domain_L_m)
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
Each argument is a 96 by 96 array or a scalar (scalars are broadcast). The builder grids each to
|
| 107 |
+
96 by 96, adds the coordinate and size channels, standardizes, and returns the model ready tensor.
|
| 108 |
+
It is verified to reproduce the training encoding exactly.
|
| 109 |
+
|
| 110 |
+
## From a file: from_csv and from_json
|
| 111 |
+
|
| 112 |
+
If your pack parameters live in a file, point the builder at it. `pack.csv` (a header row plus one
|
| 113 |
+
values row) or `pack.json` (a flat object) use the same field names as `from_pack`, plus a geometry
|
| 114 |
+
(`rows`, `cols`, `radius_frac` for a cylindrical pack, or `aspect`, `fill` for a prismatic block):
|
| 115 |
+
|
| 116 |
+
```python
|
| 117 |
+
import torch
|
| 118 |
+
from input_builder import from_csv
|
| 119 |
+
from modeling import load_model, denormalize
|
| 120 |
+
|
| 121 |
+
x = from_csv("pack.csv") # from_json("pack.json") works the same way
|
| 122 |
+
with torch.no_grad():
|
| 123 |
+
T = denormalize(load_model(".")(x))[0, 0]
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
Example `pack.csv` and `pack.json` ship in this repo.
|
| 127 |
+
|
| 128 |
+
NB - real battery data comes in many shapes this repo does not read yet, such as
|
| 129 |
+
vendor spreadsheets with their own columns, CAD geometry like STEP or STL, and simulation exports
|
| 130 |
+
from tools like COMSOL or ANSYS. `from_csv` and `from_json` handle the flat parameter case, which
|
| 131 |
+
is the common one. If you already have a geometry or a field as numbers, load it into a 96 by 96
|
| 132 |
+
array yourself and pass it to `from_fields`.
|
| 133 |
+
|
| 134 |
+
# Why we built it this way
|
| 135 |
+
|
| 136 |
+
A temperature field is smooth and slowly varying, so we mix information in the frequency domain
|
| 137 |
+
rather than pixel by pixel. That is what a Fourier Neural Operator does, it learns filters that
|
| 138 |
+
act on the field's frequencies (an FFT, a learned filter, an inverse FFT), which suits a smooth
|
| 139 |
+
solution field well. The model is small and has no all pairs attention. One forward pass produces
|
| 140 |
+
the whole 96x96 map on a plain CPU, far faster than solving the same field directly with a
|
| 141 |
+
finite volume method.
|
| 142 |
+
|
| 143 |
+
# Architecture
|
| 144 |
+
|
| 145 |
+
- 2D FNO: a lifting `Conv2d(8 -> 32, 1x1)`, then 3 spectral plus pointwise residual blocks
|
| 146 |
+
(`SpectralConv2d` keeping 12x12 low and high Fourier modes plus a `Conv2d(32,32,1x1)` skip, GELU
|
| 147 |
+
residual), then a projection head `Conv2d(32 -> 128, 1x1) -> GELU -> Conv2d(128 -> 1, 1x1)`.
|
| 148 |
+
- 892,545 parameters. The full architecture is in `modeling.py`; the arch dims and
|
| 149 |
+
normalization statistics are in `config.json`.
|
| 150 |
+
|
| 151 |
+
# Training data
|
| 152 |
+
|
| 153 |
+
- Self generated. Every training, validation, and test sample is produced on CPU by a
|
| 154 |
+
deterministic steady state heat equation finite volume solver (pure NumPy and SciPy). No external
|
| 155 |
+
dataset is downloaded, scraped, or redistributed. The PDE, the geometry parameterization, and
|
| 156 |
+
the input and target encoding are reproduced in `train.py` (`--smoke` regenerates the split and
|
| 157 |
+
asserts the sizes). Full details, seeds, and the split are in `train.py`.
|
| 158 |
+
- License: none required. The generator is our own code; the finite volume method and the
|
| 159 |
+
Fourier Neural Operator (Li et al., ICLR 2021) are published methods, not licensed data.
|
| 160 |
+
Commercially clean.
|
| 161 |
+
|
| 162 |
+
The shipped `train.py` reproduces the primary battery 2D training recipe (a larger `FNO2dV32`
|
| 163 |
+
teacher with a length scale channel and an energy balance peak prior). The weights shipped here
|
| 164 |
+
are the distilled web lite student described above.
|
| 165 |
+
|
| 166 |
+
# How to run it
|
| 167 |
+
|
| 168 |
+
```python
|
| 169 |
+
from huggingface_hub import hf_hub_download
|
| 170 |
+
import modeling
|
| 171 |
+
|
| 172 |
+
path = hf_hub_download("lowdown-labs/fela-pde", "model.safetensors")
|
| 173 |
+
model = modeling.load_model(path) # or load_model("/path/to/weights_dir")
|
| 174 |
+
|
| 175 |
+
# raw_field: an (8, 96, 96) physics field in physical units
|
| 176 |
+
x = modeling.preprocess(raw_field) # standardizes and validates shape
|
| 177 |
+
import torch
|
| 178 |
+
with torch.no_grad():
|
| 179 |
+
y_norm = model(x) # (1, 1, 96, 96) normalized temperature
|
| 180 |
+
y_degc = modeling.denormalize(y_norm) # degrees Celsius
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
`verify.py` runs a fixed sample input and checks the output shape and a verification value.
|
| 184 |
+
|
| 185 |
+
# Serving artifacts
|
| 186 |
+
|
| 187 |
+
- `model.safetensors` plus `config.json` for the safetensors load path (fp32).
|
| 188 |
+
- `verify.py` runs a fixed sample input and checks the output shape and a verification value.
|
| 189 |
+
|
| 190 |
+
# Intended use, limitations, and safety
|
| 191 |
+
|
| 192 |
+
- This is a surrogate, not a certified thermal safety tool. Use its output as a fast screening
|
| 193 |
+
and design aid, not as the sole basis for a safety critical decision. Validate against a full
|
| 194 |
+
solver on your own configurations before relying on it.
|
| 195 |
+
- Trained and evaluated only on the self generated battery thermal distribution described above.
|
| 196 |
+
Geometries, materials, and boundary conditions outside that distribution are not characterized
|
| 197 |
+
here.
|
| 198 |
+
- This is the distilled web lite student. The larger validated checkpoints (battery and heatsink,
|
| 199 |
+
2D and 3D) are a separate line.
|
| 200 |
+
|
| 201 |
+
# Acknowledgements and references
|
| 202 |
+
|
| 203 |
+
- Fourier Neural Operator: Li, Z., Kovachki, N., Azizzadenesheli, K., et al. (2021). Fourier
|
| 204 |
+
Neural Operator for Parametric Partial Differential Equations. ICLR.
|
| 205 |
+
https://arxiv.org/abs/2010.08895
|
| 206 |
+
- Finite volume heat transfer: Patankar, S. V. (1980). Numerical Heat Transfer and Fluid Flow.
|
| 207 |
+
- SciPy: Virtanen, P., et al. (2020). Nature Methods 17, 261-272.
|
| 208 |
+
- PyTorch: Paszke, A., et al. (2019). NeurIPS. https://arxiv.org/abs/1912.01703
|
| 209 |
+
|
| 210 |
+
# Model family
|
| 211 |
+
|
| 212 |
+
This is part of the FELA family from Lowdown Labs: one Fourier Neural Operator architecture
|
| 213 |
+
across many modalities, all CPU native and subquadratic. Sibling repos are independently
|
| 214 |
+
trained per modality and share no weights, so none carries a `base_model` link.
|
| 215 |
+
|
| 216 |
+
# License
|
| 217 |
+
|
| 218 |
+
Released under the Lowdown Labs Lovely License 1.0 (CC BY-NC 4.0 plus Hippocratic License 3.0). See LICENSE. For most LL models, a commercial license may be available; contact Lowdown Labs.
|
config.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "fela_pde_fno2d",
|
| 3 |
+
"description": "battery pack thermal surrogate; 8 channel physics field in, temperature field out",
|
| 4 |
+
"architecture": "fno2d",
|
| 5 |
+
"framework": "pytorch",
|
| 6 |
+
"arch": {
|
| 7 |
+
"in_ch": 8,
|
| 8 |
+
"out_ch": 1,
|
| 9 |
+
"width": 32,
|
| 10 |
+
"modes": 12,
|
| 11 |
+
"layers": 3,
|
| 12 |
+
"proj_hidden": 128,
|
| 13 |
+
"grid_h": 96,
|
| 14 |
+
"grid_w": 96
|
| 15 |
+
},
|
| 16 |
+
"params": 892545,
|
| 17 |
+
"input_shape": [
|
| 18 |
+
1,
|
| 19 |
+
8,
|
| 20 |
+
96,
|
| 21 |
+
96
|
| 22 |
+
],
|
| 23 |
+
"output_shape": [
|
| 24 |
+
1,
|
| 25 |
+
1,
|
| 26 |
+
96,
|
| 27 |
+
96
|
| 28 |
+
],
|
| 29 |
+
"weights_safetensors": "model.safetensors",
|
| 30 |
+
"input_channels": [
|
| 31 |
+
"mask",
|
| 32 |
+
"q_source",
|
| 33 |
+
"k_field",
|
| 34 |
+
"h_conv",
|
| 35 |
+
"T_amb",
|
| 36 |
+
"x_coord",
|
| 37 |
+
"y_coord",
|
| 38 |
+
"log_domain_L"
|
| 39 |
+
],
|
| 40 |
+
"norm": {
|
| 41 |
+
"x_mean": [
|
| 42 |
+
0.4555675685405731,
|
| 43 |
+
33879.58984375,
|
| 44 |
+
4.985158443450928,
|
| 45 |
+
58.90073013305664,
|
| 46 |
+
27.50713348388672,
|
| 47 |
+
0.5,
|
| 48 |
+
0.5,
|
| 49 |
+
-2.6601810455322266
|
| 50 |
+
],
|
| 51 |
+
"x_std": [
|
| 52 |
+
0.4980228543281555,
|
| 53 |
+
89317.828125,
|
| 54 |
+
6.717851638793945,
|
| 55 |
+
51.0863151550293,
|
| 56 |
+
7.186279773712158,
|
| 57 |
+
0.29169902205467224,
|
| 58 |
+
0.29169902205467224,
|
| 59 |
+
0.3988337218761444
|
| 60 |
+
],
|
| 61 |
+
"y_mean": 34.82331466674805,
|
| 62 |
+
"y_std": 18.65639305114746,
|
| 63 |
+
"amb_channel": 4
|
| 64 |
+
},
|
| 65 |
+
"interface": "Ynorm = model((X - x_mean)/x_std); Y_degC = Ynorm*y_std + y_mean",
|
| 66 |
+
"license": "lowdown-labs-lovely-license-1.0",
|
| 67 |
+
"architectures": [
|
| 68 |
+
"FelaPdeModel"
|
| 69 |
+
],
|
| 70 |
+
"auto_map": {
|
| 71 |
+
"AutoConfig": "configuration_pde.FelaPdeConfig",
|
| 72 |
+
"AutoModel": "modeling_pde.FelaPdeModel"
|
| 73 |
+
},
|
| 74 |
+
"complex_keys": [
|
| 75 |
+
"sp.0.w1",
|
| 76 |
+
"sp.0.w2",
|
| 77 |
+
"sp.1.w1",
|
| 78 |
+
"sp.1.w2",
|
| 79 |
+
"sp.2.w1",
|
| 80 |
+
"sp.2.w2"
|
| 81 |
+
]
|
| 82 |
+
}
|
configuration_pde.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from transformers import PretrainedConfig
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class FelaPdeConfig(PretrainedConfig):
|
| 5 |
+
model_type = "fela_pde_fno2d"
|
| 6 |
+
|
| 7 |
+
def __init__(
|
| 8 |
+
self,
|
| 9 |
+
in_ch=8,
|
| 10 |
+
out_ch=1,
|
| 11 |
+
width=32,
|
| 12 |
+
modes=12,
|
| 13 |
+
layers=3,
|
| 14 |
+
proj_hidden=128,
|
| 15 |
+
grid_h=96,
|
| 16 |
+
grid_w=96,
|
| 17 |
+
arch=None,
|
| 18 |
+
**kwargs,
|
| 19 |
+
):
|
| 20 |
+
if isinstance(arch, dict):
|
| 21 |
+
in_ch = arch.get("in_ch", in_ch)
|
| 22 |
+
out_ch = arch.get("out_ch", out_ch)
|
| 23 |
+
width = arch.get("width", width)
|
| 24 |
+
modes = arch.get("modes", modes)
|
| 25 |
+
layers = arch.get("layers", layers)
|
| 26 |
+
proj_hidden = arch.get("proj_hidden", proj_hidden)
|
| 27 |
+
grid_h = arch.get("grid_h", grid_h)
|
| 28 |
+
grid_w = arch.get("grid_w", grid_w)
|
| 29 |
+
self.in_ch = in_ch
|
| 30 |
+
self.out_ch = out_ch
|
| 31 |
+
self.width = width
|
| 32 |
+
self.modes = modes
|
| 33 |
+
self.layers = layers
|
| 34 |
+
self.proj_hidden = proj_hidden
|
| 35 |
+
self.grid_h = grid_h
|
| 36 |
+
self.grid_w = grid_w
|
| 37 |
+
self.arch = {
|
| 38 |
+
"in_ch": in_ch,
|
| 39 |
+
"out_ch": out_ch,
|
| 40 |
+
"width": width,
|
| 41 |
+
"modes": modes,
|
| 42 |
+
"layers": layers,
|
| 43 |
+
"proj_hidden": proj_hidden,
|
| 44 |
+
"grid_h": grid_h,
|
| 45 |
+
"grid_w": grid_w,
|
| 46 |
+
}
|
| 47 |
+
super().__init__(**kwargs)
|
example.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
from input_builder import cylinder_mask, from_pack
|
| 4 |
+
from modeling import denormalize, load_model
|
| 5 |
+
|
| 6 |
+
model = load_model(".")
|
| 7 |
+
|
| 8 |
+
mask = cylinder_mask(rows=3, cols=4, radius_frac=0.4)
|
| 9 |
+
x = from_pack(
|
| 10 |
+
mask,
|
| 11 |
+
current_A=40.0,
|
| 12 |
+
soc=0.3,
|
| 13 |
+
R0_ohm=0.02,
|
| 14 |
+
k_cell_W_mK=20.0,
|
| 15 |
+
k_coolant_W_mK=0.6,
|
| 16 |
+
h_conv_W_m2K=80.0,
|
| 17 |
+
T_amb_degC=25.0,
|
| 18 |
+
domain_L_m=0.08,
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
with torch.no_grad():
|
| 22 |
+
T = denormalize(model(x))[0, 0]
|
| 23 |
+
|
| 24 |
+
hot = divmod(int(T.argmax()), T.shape[1])
|
| 25 |
+
print("temperature grid:", tuple(T.shape))
|
| 26 |
+
print("peak degC:", round(float(T.max()), 2))
|
| 27 |
+
print("mean degC:", round(float(T.mean()), 2))
|
| 28 |
+
print("hottest cell row,col:", [int(hot[0]), int(hot[1])])
|
input_builder.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import csv
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
from modeling import preprocess
|
| 7 |
+
|
| 8 |
+
N = 96
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def _coords():
|
| 12 |
+
yy, xx = np.meshgrid(np.linspace(0, 1, N), np.linspace(0, 1, N), indexing="ij")
|
| 13 |
+
return xx, yy
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def _to_field(v):
|
| 17 |
+
a = np.asarray(v, dtype=np.float64)
|
| 18 |
+
if a.ndim == 0:
|
| 19 |
+
return np.full((N, N), float(a))
|
| 20 |
+
if a.shape == (N, N):
|
| 21 |
+
return a
|
| 22 |
+
yi = np.linspace(0, a.shape[0] - 1, N).round().astype(int)
|
| 23 |
+
xi = np.linspace(0, a.shape[1] - 1, N).round().astype(int)
|
| 24 |
+
return a[yi][:, xi]
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def cylinder_mask(rows, cols, radius_frac=0.4):
|
| 28 |
+
xx, yy = _coords()
|
| 29 |
+
m = np.zeros((N, N))
|
| 30 |
+
px, py = 1.0 / cols, 1.0 / rows
|
| 31 |
+
r = radius_frac * min(px, py)
|
| 32 |
+
for i in range(rows):
|
| 33 |
+
cy = (i + 0.5) * py
|
| 34 |
+
for j in range(cols):
|
| 35 |
+
cx = (j + 0.5) * px
|
| 36 |
+
m[(xx - cx) ** 2 + (yy - cy) ** 2 <= r * r] = 1.0
|
| 37 |
+
return m
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def rect_mask(aspect=1.0, fill=0.7):
|
| 41 |
+
xx, yy = _coords()
|
| 42 |
+
m = np.zeros((N, N))
|
| 43 |
+
hh = min(0.98, (fill / aspect) ** 0.5)
|
| 44 |
+
ww = min(0.98, aspect * hh)
|
| 45 |
+
x0, x1 = 0.5 - ww / 2, 0.5 + ww / 2
|
| 46 |
+
y0, y1 = 0.5 - hh / 2, 0.5 + hh / 2
|
| 47 |
+
m[(xx >= x0) & (xx <= x1) & (yy >= y0) & (yy <= y1)] = 1.0
|
| 48 |
+
return m
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def from_fields(
|
| 52 |
+
mask, q_source_W_m3, k_field_W_mK, h_conv_W_m2K, T_amb_degC, domain_L_m
|
| 53 |
+
):
|
| 54 |
+
m = (_to_field(mask) > 0.5).astype(np.float64)
|
| 55 |
+
q = _to_field(q_source_W_m3) * m
|
| 56 |
+
k = _to_field(k_field_W_mK)
|
| 57 |
+
h = _to_field(h_conv_W_m2K)
|
| 58 |
+
ta = _to_field(T_amb_degC)
|
| 59 |
+
xx, yy = _coords()
|
| 60 |
+
logL = np.full((N, N), float(np.log(domain_L_m)))
|
| 61 |
+
field = np.stack([m, q, k, h, ta, xx, yy, logL], 0).astype(np.float32)
|
| 62 |
+
return preprocess(field)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def from_pack(
|
| 66 |
+
mask,
|
| 67 |
+
current_A,
|
| 68 |
+
soc,
|
| 69 |
+
R0_ohm,
|
| 70 |
+
k_cell_W_mK,
|
| 71 |
+
k_coolant_W_mK,
|
| 72 |
+
h_conv_W_m2K,
|
| 73 |
+
T_amb_degC,
|
| 74 |
+
domain_L_m,
|
| 75 |
+
beta=2.0,
|
| 76 |
+
):
|
| 77 |
+
m = (_to_field(mask) > 0.5).astype(np.float64)
|
| 78 |
+
hg = domain_L_m / (N - 1)
|
| 79 |
+
R_int = R0_ohm * (1.0 + beta * (1.0 - soc) ** 2)
|
| 80 |
+
P_total = current_A**2 * R_int
|
| 81 |
+
area = max(m.sum() * hg * hg, hg * hg)
|
| 82 |
+
q = m * (P_total / area)
|
| 83 |
+
k = np.where(m > 0, k_cell_W_mK, k_coolant_W_mK)
|
| 84 |
+
return from_fields(m, q, k, h_conv_W_m2K, T_amb_degC, domain_L_m)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def from_params(d):
|
| 88 |
+
if d.get("rows") not in (None, "") and d.get("cols") not in (None, ""):
|
| 89 |
+
mask = cylinder_mask(
|
| 90 |
+
int(float(d["rows"])),
|
| 91 |
+
int(float(d["cols"])),
|
| 92 |
+
float(d.get("radius_frac") or 0.4),
|
| 93 |
+
)
|
| 94 |
+
else:
|
| 95 |
+
mask = rect_mask(float(d.get("aspect") or 1.0), float(d.get("fill") or 0.7))
|
| 96 |
+
return from_pack(
|
| 97 |
+
mask,
|
| 98 |
+
float(d["current_A"]),
|
| 99 |
+
float(d["soc"]),
|
| 100 |
+
float(d["R0_ohm"]),
|
| 101 |
+
float(d["k_cell_W_mK"]),
|
| 102 |
+
float(d["k_coolant_W_mK"]),
|
| 103 |
+
float(d["h_conv_W_m2K"]),
|
| 104 |
+
float(d["T_amb_degC"]),
|
| 105 |
+
float(d["domain_L_m"]),
|
| 106 |
+
beta=float(d.get("beta") or 2.0),
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def from_json(path):
|
| 111 |
+
with open(path) as f:
|
| 112 |
+
return from_params(json.load(f))
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def from_csv(path):
|
| 116 |
+
with open(path) as f:
|
| 117 |
+
return from_params(next(csv.DictReader(f)))
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d7d0c9f3b6282896c4e4e21a453253561f7288390cd9f8e09c64d99cc81d6b0a
|
| 3 |
+
size 7110540
|
modeling.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
|
| 7 |
+
CONFIG = None
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def _config():
|
| 11 |
+
global CONFIG
|
| 12 |
+
if CONFIG is None:
|
| 13 |
+
here = os.path.dirname(os.path.abspath(__file__))
|
| 14 |
+
with open(os.path.join(here, "config.json")) as f:
|
| 15 |
+
CONFIG = json.load(f)
|
| 16 |
+
return CONFIG
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class SpectralConv2d(nn.Module):
|
| 20 |
+
def __init__(self, ci, co, m1, m2):
|
| 21 |
+
super().__init__()
|
| 22 |
+
self.m1, self.m2 = (m1, m2)
|
| 23 |
+
s = 1 / (ci * co)
|
| 24 |
+
self.w1 = nn.Parameter(s * torch.rand(ci, co, m1, m2, dtype=torch.cfloat))
|
| 25 |
+
self.w2 = nn.Parameter(s * torch.rand(ci, co, m1, m2, dtype=torch.cfloat))
|
| 26 |
+
|
| 27 |
+
def forward(self, x):
|
| 28 |
+
B, C, Hh, Ww = x.shape
|
| 29 |
+
xf = torch.fft.rfft2(x)
|
| 30 |
+
o = torch.zeros(
|
| 31 |
+
B, self.w1.shape[1], Hh, Ww // 2 + 1, dtype=torch.cfloat, device=x.device
|
| 32 |
+
)
|
| 33 |
+
o[:, :, : self.m1, : self.m2] = torch.einsum(
|
| 34 |
+
"bixy,ioxy->boxy", xf[:, :, : self.m1, : self.m2], self.w1
|
| 35 |
+
)
|
| 36 |
+
o[:, :, -self.m1 :, : self.m2] = torch.einsum(
|
| 37 |
+
"bixy,ioxy->boxy", xf[:, :, -self.m1 :, : self.m2], self.w2
|
| 38 |
+
)
|
| 39 |
+
return torch.fft.irfft2(o, s=(Hh, Ww))
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class FNO2d(nn.Module):
|
| 43 |
+
def __init__(self, in_ch=8, modes=12, width=32, L=3, proj_hidden=128):
|
| 44 |
+
super().__init__()
|
| 45 |
+
self.lift = nn.Conv2d(in_ch, width, 1)
|
| 46 |
+
self.sp = nn.ModuleList(
|
| 47 |
+
[SpectralConv2d(width, width, modes, modes) for _ in range(L)]
|
| 48 |
+
)
|
| 49 |
+
self.w = nn.ModuleList([nn.Conv2d(width, width, 1) for _ in range(L)])
|
| 50 |
+
self.proj = nn.Sequential(
|
| 51 |
+
nn.Conv2d(width, proj_hidden, 1), nn.GELU(), nn.Conv2d(proj_hidden, 1, 1)
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
def forward(self, x):
|
| 55 |
+
h = self.lift(x)
|
| 56 |
+
for sp, w in zip(self.sp, self.w):
|
| 57 |
+
h = h + F.gelu(sp(h) + w(h))
|
| 58 |
+
return self.proj(h)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def validate_input(x):
|
| 62 |
+
a = _config()["arch"]
|
| 63 |
+
if not isinstance(x, torch.Tensor):
|
| 64 |
+
raise TypeError(f"Expected a torch.Tensor, got {type(x)}")
|
| 65 |
+
if (
|
| 66 |
+
x.dim() != 4
|
| 67 |
+
or x.shape[1] != a["in_ch"]
|
| 68 |
+
or x.shape[2] != a["grid_h"]
|
| 69 |
+
or (x.shape[3] != a["grid_w"])
|
| 70 |
+
):
|
| 71 |
+
raise ValueError(
|
| 72 |
+
f"Expected an input of shape (batch, {a['in_ch']}, {a['grid_h']}, {a['grid_w']}), got {tuple(x.shape)}."
|
| 73 |
+
)
|
| 74 |
+
return x
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def preprocess(raw_field, mean=None, std=None):
|
| 78 |
+
x = torch.as_tensor(raw_field, dtype=torch.float32)
|
| 79 |
+
if x.dim() == 3:
|
| 80 |
+
x = x.unsqueeze(0)
|
| 81 |
+
norm = _config()["norm"]
|
| 82 |
+
m = torch.as_tensor(
|
| 83 |
+
mean if mean is not None else norm["x_mean"], dtype=torch.float32
|
| 84 |
+
).reshape(1, -1, 1, 1)
|
| 85 |
+
s = torch.as_tensor(
|
| 86 |
+
std if std is not None else norm["x_std"], dtype=torch.float32
|
| 87 |
+
).reshape(1, -1, 1, 1)
|
| 88 |
+
x = (x - m) / torch.clamp(s, min=1e-06)
|
| 89 |
+
return validate_input(x)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def denormalize(y_norm):
|
| 93 |
+
norm = _config()["norm"]
|
| 94 |
+
return torch.as_tensor(y_norm, dtype=torch.float32) * norm["y_std"] + norm["y_mean"]
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def _build():
|
| 98 |
+
a = _config()["arch"]
|
| 99 |
+
return FNO2d(
|
| 100 |
+
in_ch=a["in_ch"],
|
| 101 |
+
modes=a["modes"],
|
| 102 |
+
width=a["width"],
|
| 103 |
+
L=a["layers"],
|
| 104 |
+
proj_hidden=a["proj_hidden"],
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def load_model(path_or_repo, filename=None):
|
| 109 |
+
path = path_or_repo
|
| 110 |
+
fname = filename or _config()["weights_safetensors"]
|
| 111 |
+
if os.path.isdir(path):
|
| 112 |
+
path = os.path.join(path, fname)
|
| 113 |
+
elif not os.path.exists(path):
|
| 114 |
+
from huggingface_hub import hf_hub_download
|
| 115 |
+
|
| 116 |
+
path = hf_hub_download(path_or_repo, fname)
|
| 117 |
+
from safetensors.torch import load_file
|
| 118 |
+
|
| 119 |
+
state = load_file(path)
|
| 120 |
+
cplx = set(_config().get("complex_keys", []))
|
| 121 |
+
state = {
|
| 122 |
+
k: (torch.view_as_complex(v.contiguous()) if k in cplx else v)
|
| 123 |
+
for k, v in state.items()
|
| 124 |
+
}
|
| 125 |
+
model = _build()
|
| 126 |
+
model.load_state_dict(state, strict=True)
|
| 127 |
+
model.eval()
|
| 128 |
+
return model
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def from_pretrained(repo_id):
|
| 132 |
+
return load_model(repo_id)
|
modeling_pde.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import types
|
| 4 |
+
|
| 5 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
from transformers import PreTrainedModel
|
| 9 |
+
from transformers.modeling_outputs import CausalLMOutput
|
| 10 |
+
|
| 11 |
+
from .configuration_pde import FelaPdeConfig
|
| 12 |
+
from .modeling import FNO2d, SpectralConv2d
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _spectral_forward(self, x):
|
| 16 |
+
w1 = torch.view_as_complex(self.w1)
|
| 17 |
+
w2 = torch.view_as_complex(self.w2)
|
| 18 |
+
B, C, Hh, Ww = x.shape
|
| 19 |
+
xf = torch.fft.rfft2(x)
|
| 20 |
+
o = torch.zeros(
|
| 21 |
+
B, w1.shape[1], Hh, Ww // 2 + 1, dtype=torch.cfloat, device=x.device
|
| 22 |
+
)
|
| 23 |
+
o[:, :, : self.m1, : self.m2] = torch.einsum(
|
| 24 |
+
"bixy,ioxy->boxy", xf[:, :, : self.m1, : self.m2], w1
|
| 25 |
+
)
|
| 26 |
+
o[:, :, -self.m1 :, : self.m2] = torch.einsum(
|
| 27 |
+
"bixy,ioxy->boxy", xf[:, :, -self.m1 :, : self.m2], w2
|
| 28 |
+
)
|
| 29 |
+
return torch.fft.irfft2(o, s=(Hh, Ww))
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _realify(model):
|
| 33 |
+
for m in model.modules():
|
| 34 |
+
if isinstance(m, SpectralConv2d):
|
| 35 |
+
m.w1 = nn.Parameter(torch.view_as_real(m.w1.detach()).contiguous())
|
| 36 |
+
m.w2 = nn.Parameter(torch.view_as_real(m.w2.detach()).contiguous())
|
| 37 |
+
m.forward = types.MethodType(_spectral_forward, m)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class FelaPdeModel(PreTrainedModel):
|
| 41 |
+
config_class = FelaPdeConfig
|
| 42 |
+
base_model_prefix = "model"
|
| 43 |
+
main_input_name = "x"
|
| 44 |
+
|
| 45 |
+
def __init__(self, config):
|
| 46 |
+
super().__init__(config)
|
| 47 |
+
self.model = FNO2d(
|
| 48 |
+
in_ch=config.in_ch,
|
| 49 |
+
modes=config.modes,
|
| 50 |
+
width=config.width,
|
| 51 |
+
L=config.layers,
|
| 52 |
+
proj_hidden=config.proj_hidden,
|
| 53 |
+
)
|
| 54 |
+
_realify(self.model)
|
| 55 |
+
self.post_init()
|
| 56 |
+
|
| 57 |
+
def forward(self, x=None, input_values=None, **kwargs):
|
| 58 |
+
if x is None:
|
| 59 |
+
x = input_values
|
| 60 |
+
out = self.model(x)
|
| 61 |
+
return CausalLMOutput(logits=out)
|
pack.csv
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
current_A,soc,R0_ohm,k_cell_W_mK,k_coolant_W_mK,h_conv_W_m2K,T_amb_degC,domain_L_m,rows,cols,radius_frac
|
| 2 |
+
40,0.3,0.02,20,0.6,80,25,0.08,3,4,0.4
|
pack.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"current_A": 40,
|
| 3 |
+
"soc": 0.3,
|
| 4 |
+
"R0_ohm": 0.02,
|
| 5 |
+
"k_cell_W_mK": 20,
|
| 6 |
+
"k_coolant_W_mK": 0.6,
|
| 7 |
+
"h_conv_W_m2K": 80,
|
| 8 |
+
"T_amb_degC": 25,
|
| 9 |
+
"domain_L_m": 0.08,
|
| 10 |
+
"rows": 3,
|
| 11 |
+
"cols": 4,
|
| 12 |
+
"radius_frac": 0.4
|
| 13 |
+
}
|
streaming/manifest.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model": "pde",
|
| 3 |
+
"format": "fp16-streaming",
|
| 4 |
+
"note": "load order is smallest-first for progressive/streaming load",
|
| 5 |
+
"files": [
|
| 6 |
+
{
|
| 7 |
+
"file": "model_fp16.safetensors",
|
| 8 |
+
"source": "model.safetensors",
|
| 9 |
+
"dtype": "fp16",
|
| 10 |
+
"bytes": 3555962,
|
| 11 |
+
"approx_mb": 3.391
|
| 12 |
+
}
|
| 13 |
+
]
|
| 14 |
+
}
|
streaming/model_fp16.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:66cc543317c054e7c2c7ed7541d12248c9732e5a26d4b0e4814e4357164dc916
|
| 3 |
+
size 3555962
|
train.py
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
for _v in (
|
| 4 |
+
"OMP_NUM_THREADS",
|
| 5 |
+
"OPENBLAS_NUM_THREADS",
|
| 6 |
+
"MKL_NUM_THREADS",
|
| 7 |
+
"NUMEXPR_NUM_THREADS",
|
| 8 |
+
):
|
| 9 |
+
os.environ.setdefault(_v, "1")
|
| 10 |
+
import sys, time, json, argparse
|
| 11 |
+
import numpy as np
|
| 12 |
+
import scipy.sparse as sp
|
| 13 |
+
from scipy.sparse.linalg import spsolve
|
| 14 |
+
|
| 15 |
+
N = 96
|
| 16 |
+
CH8 = [
|
| 17 |
+
"mask",
|
| 18 |
+
"q_source",
|
| 19 |
+
"k_field",
|
| 20 |
+
"h_conv",
|
| 21 |
+
"T_amb",
|
| 22 |
+
"x_coord",
|
| 23 |
+
"y_coord",
|
| 24 |
+
"log_domain_L",
|
| 25 |
+
]
|
| 26 |
+
SEED_TRAIN = 12340000
|
| 27 |
+
SEED_HOT = 55000000
|
| 28 |
+
SEED_HOLDOUT = 900000000
|
| 29 |
+
SEED_EDGE = 900000000 + 500000000
|
| 30 |
+
RANGES = dict(
|
| 31 |
+
domain_L=(0.02, 0.12),
|
| 32 |
+
cyl_rows=(2, 5),
|
| 33 |
+
cyl_cols=(2, 5),
|
| 34 |
+
cell_radius_frac=(0.3, 0.48),
|
| 35 |
+
aspect=(0.4, 2.5),
|
| 36 |
+
cell_fill=(0.55, 0.9),
|
| 37 |
+
current=(1.0, 60.0),
|
| 38 |
+
soc=(0.05, 1.0),
|
| 39 |
+
R0=(0.005, 0.05),
|
| 40 |
+
beta=(0.5, 4.0),
|
| 41 |
+
k_cell=(1.0, 30.0),
|
| 42 |
+
k_coolant=(0.1, 1.5),
|
| 43 |
+
h_conv=(5.0, 200.0),
|
| 44 |
+
T_amb=(15.0, 40.0),
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def build_system(N, hg, k_field, q_field, h_conv, T_amb):
|
| 49 |
+
n = N * N
|
| 50 |
+
dx2 = hg * hg
|
| 51 |
+
k = k_field.astype(np.float64)
|
| 52 |
+
q = q_field.astype(np.float64)
|
| 53 |
+
|
| 54 |
+
def hmean(a, b):
|
| 55 |
+
return 2.0 * a * b / (a + b + 1e-30)
|
| 56 |
+
|
| 57 |
+
ids = np.arange(n).reshape(N, N)
|
| 58 |
+
rows = []
|
| 59 |
+
cols = []
|
| 60 |
+
vals = []
|
| 61 |
+
diag = np.zeros((N, N))
|
| 62 |
+
b = q * dx2
|
| 63 |
+
kf = hmean(k[1:, :], k[:-1, :])
|
| 64 |
+
p = ids[1:, :].ravel()
|
| 65 |
+
nb = ids[:-1, :].ravel()
|
| 66 |
+
kk = kf.ravel()
|
| 67 |
+
rows.append(p)
|
| 68 |
+
cols.append(nb)
|
| 69 |
+
vals.append(-kk)
|
| 70 |
+
diag[1:, :] += kf
|
| 71 |
+
p = ids[:-1, :].ravel()
|
| 72 |
+
nb = ids[1:, :].ravel()
|
| 73 |
+
rows.append(p)
|
| 74 |
+
cols.append(nb)
|
| 75 |
+
vals.append(-kk)
|
| 76 |
+
diag[:-1, :] += kf
|
| 77 |
+
kf = hmean(k[:, 1:], k[:, :-1])
|
| 78 |
+
p = ids[:, 1:].ravel()
|
| 79 |
+
nb = ids[:, :-1].ravel()
|
| 80 |
+
kk = kf.ravel()
|
| 81 |
+
rows.append(p)
|
| 82 |
+
cols.append(nb)
|
| 83 |
+
vals.append(-kk)
|
| 84 |
+
diag[:, 1:] += kf
|
| 85 |
+
p = ids[:, :-1].ravel()
|
| 86 |
+
nb = ids[:, 1:].ravel()
|
| 87 |
+
rows.append(p)
|
| 88 |
+
cols.append(nb)
|
| 89 |
+
vals.append(-kk)
|
| 90 |
+
diag[:, :-1] += kf
|
| 91 |
+
hdx = h_conv * hg
|
| 92 |
+
bmask = np.zeros((N, N))
|
| 93 |
+
bmask[0, :] += 1
|
| 94 |
+
bmask[-1, :] += 1
|
| 95 |
+
bmask[:, 0] += 1
|
| 96 |
+
bmask[:, -1] += 1
|
| 97 |
+
diag += hdx * bmask
|
| 98 |
+
b += hdx * T_amb * bmask
|
| 99 |
+
rows.append(ids.ravel())
|
| 100 |
+
cols.append(ids.ravel())
|
| 101 |
+
vals.append(diag.ravel())
|
| 102 |
+
rows = np.concatenate(rows)
|
| 103 |
+
cols = np.concatenate(cols)
|
| 104 |
+
vals = np.concatenate(vals)
|
| 105 |
+
A = sp.csr_matrix((vals, (rows, cols)), shape=(n, n))
|
| 106 |
+
return (A, b.ravel())
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def solve_steady(N, hg, k_field, q_field, h_conv, T_amb):
|
| 110 |
+
A, b = build_system(N, hg, k_field, q_field, h_conv, T_amb)
|
| 111 |
+
return spsolve(A.tocsc(), b).reshape(N, N)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def sample_params(rng):
|
| 115 |
+
p = {}
|
| 116 |
+
p["domain_L"] = rng.uniform(*RANGES["domain_L"])
|
| 117 |
+
p["geom"] = "cyl" if rng.random() < 0.6 else "pris"
|
| 118 |
+
p["current"] = rng.uniform(*RANGES["current"])
|
| 119 |
+
p["soc"] = rng.uniform(*RANGES["soc"])
|
| 120 |
+
p["R0"] = rng.uniform(*RANGES["R0"])
|
| 121 |
+
p["beta"] = rng.uniform(*RANGES["beta"])
|
| 122 |
+
p["k_cell"] = rng.uniform(*RANGES["k_cell"])
|
| 123 |
+
p["k_coolant"] = rng.uniform(*RANGES["k_coolant"])
|
| 124 |
+
p["h_conv"] = rng.uniform(*RANGES["h_conv"])
|
| 125 |
+
p["T_amb"] = rng.uniform(*RANGES["T_amb"])
|
| 126 |
+
if p["geom"] == "cyl":
|
| 127 |
+
p["rows"] = int(rng.integers(RANGES["cyl_rows"][0], RANGES["cyl_rows"][1] + 1))
|
| 128 |
+
p["cols"] = int(rng.integers(RANGES["cyl_cols"][0], RANGES["cyl_cols"][1] + 1))
|
| 129 |
+
p["rfrac"] = rng.uniform(*RANGES["cell_radius_frac"])
|
| 130 |
+
else:
|
| 131 |
+
p["aspect"] = rng.uniform(*RANGES["aspect"])
|
| 132 |
+
p["fill"] = rng.uniform(*RANGES["cell_fill"])
|
| 133 |
+
return p
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def build_fields(N, p):
|
| 137 |
+
L = p["domain_L"]
|
| 138 |
+
hg = L / (N - 1)
|
| 139 |
+
yy, xx = np.meshgrid(np.linspace(0, 1, N), np.linspace(0, 1, N), indexing="ij")
|
| 140 |
+
mask = np.zeros((N, N), dtype=np.float64)
|
| 141 |
+
R_int = p["R0"] * (1.0 + p["beta"] * (1.0 - p["soc"]) ** 2)
|
| 142 |
+
P_total = p["current"] ** 2 * R_int
|
| 143 |
+
if p["geom"] == "cyl":
|
| 144 |
+
rows, cols = (p["rows"], p["cols"])
|
| 145 |
+
pitch_x = 1.0 / cols
|
| 146 |
+
pitch_y = 1.0 / rows
|
| 147 |
+
r = p["rfrac"] * min(pitch_x, pitch_y)
|
| 148 |
+
for i in range(rows):
|
| 149 |
+
cy = (i + 0.5) * pitch_y
|
| 150 |
+
for j in range(cols):
|
| 151 |
+
cx = (j + 0.5) * pitch_x
|
| 152 |
+
d = (xx - cx) ** 2 + (yy - cy) ** 2
|
| 153 |
+
mask[d <= r * r] = 1.0
|
| 154 |
+
else:
|
| 155 |
+
a = p["aspect"]
|
| 156 |
+
f = p["fill"]
|
| 157 |
+
hh = min(0.98, np.sqrt(f / a))
|
| 158 |
+
ww = min(0.98, a * hh)
|
| 159 |
+
x0, x1 = (0.5 - ww / 2, 0.5 + ww / 2)
|
| 160 |
+
y0, y1 = (0.5 - hh / 2, 0.5 + hh / 2)
|
| 161 |
+
mask[(xx >= x0) & (xx <= x1) & (yy >= y0) & (yy <= y1)] = 1.0
|
| 162 |
+
cell_area_phys = mask.sum() * hg * hg
|
| 163 |
+
if cell_area_phys <= 0:
|
| 164 |
+
cell_area_phys = hg * hg
|
| 165 |
+
q_field = mask * (P_total / cell_area_phys)
|
| 166 |
+
k_field = np.where(mask > 0, p["k_cell"], p["k_coolant"])
|
| 167 |
+
return (mask, k_field, q_field, hg)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def build_X(p, mask, k_field, q_field):
|
| 171 |
+
yy, xx = np.meshgrid(np.linspace(0, 1, N), np.linspace(0, 1, N), indexing="ij")
|
| 172 |
+
logL = np.full((N, N), np.log(p["domain_L"]), dtype=np.float64)
|
| 173 |
+
return np.stack(
|
| 174 |
+
[
|
| 175 |
+
mask,
|
| 176 |
+
q_field,
|
| 177 |
+
k_field,
|
| 178 |
+
np.full((N, N), p["h_conv"]),
|
| 179 |
+
np.full((N, N), p["T_amb"]),
|
| 180 |
+
xx,
|
| 181 |
+
yy,
|
| 182 |
+
logL,
|
| 183 |
+
],
|
| 184 |
+
0,
|
| 185 |
+
).astype(np.float32)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def gen_uniform(seed):
|
| 189 |
+
rng = np.random.default_rng(seed)
|
| 190 |
+
p = sample_params(rng)
|
| 191 |
+
mask, k, q, hg = build_fields(N, p)
|
| 192 |
+
T = solve_steady(N, hg, k, q, p["h_conv"], p["T_amb"])
|
| 193 |
+
return (build_X(p, mask, k, q), T.astype(np.float32))
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def run(fn, jobs, workers):
|
| 197 |
+
if workers <= 1:
|
| 198 |
+
out = [fn(j) for j in jobs]
|
| 199 |
+
else:
|
| 200 |
+
from multiprocessing import Pool
|
| 201 |
+
|
| 202 |
+
with Pool(workers) as pool:
|
| 203 |
+
out = list(pool.imap(fn, jobs, chunksize=8))
|
| 204 |
+
Xs, Ys = zip(*out)
|
| 205 |
+
return (np.stack(Xs), np.stack(Ys))
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def make_split(n_train, workers):
|
| 209 |
+
Xtr, Ytr = run(gen_uniform, [SEED_TRAIN + i for i in range(n_train)], workers)
|
| 210 |
+
n = Xtr.shape[0]
|
| 211 |
+
perm = np.random.default_rng(0).permutation(n)
|
| 212 |
+
ntr = int(0.8 * n)
|
| 213 |
+
nva = int(0.1 * n)
|
| 214 |
+
sp_idx = {
|
| 215 |
+
"train": perm[:ntr],
|
| 216 |
+
"val": perm[ntr : ntr + nva],
|
| 217 |
+
"test": perm[ntr + nva :],
|
| 218 |
+
}
|
| 219 |
+
return (Xtr, Ytr, sp_idx)
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def main():
|
| 223 |
+
ap = argparse.ArgumentParser()
|
| 224 |
+
ap.add_argument(
|
| 225 |
+
"--data", default="/workspace/pde_surrogate/battery/battery_thermal_v3.h5"
|
| 226 |
+
)
|
| 227 |
+
ap.add_argument(
|
| 228 |
+
"--hot", default="/workspace/pde_surrogate/battery/hot_enrich_v3.h5"
|
| 229 |
+
)
|
| 230 |
+
ap.add_argument(
|
| 231 |
+
"--out", default="/workspace/pde_surrogate/battery/fno_ckpt_v3_final.pt"
|
| 232 |
+
)
|
| 233 |
+
ap.add_argument("--n_train", type=int, default=4000)
|
| 234 |
+
ap.add_argument("--n_hot", type=int, default=5000)
|
| 235 |
+
ap.add_argument("--workers", type=int, default=32)
|
| 236 |
+
ap.add_argument("--epochs", type=int, default=300)
|
| 237 |
+
ap.add_argument("--bs", type=int, default=128)
|
| 238 |
+
ap.add_argument("--lr", type=float, default=0.002)
|
| 239 |
+
ap.add_argument("--modes", type=int, default=32)
|
| 240 |
+
ap.add_argument("--width", type=int, default=80)
|
| 241 |
+
ap.add_argument("--layers", type=int, default=4)
|
| 242 |
+
ap.add_argument("--scale_floor", type=float, default=2.0)
|
| 243 |
+
ap.add_argument("--grad_w", type=float, default=3.0)
|
| 244 |
+
ap.add_argument("--rise_w", type=float, default=1.5)
|
| 245 |
+
ap.add_argument("--scale_w", type=float, default=3.0)
|
| 246 |
+
ap.add_argument("--smoke", action="store_true")
|
| 247 |
+
args = ap.parse_args()
|
| 248 |
+
if args.smoke:
|
| 249 |
+
Xtr, Ytr, sp_idx = make_split(args.n_train, args.workers)
|
| 250 |
+
n = Xtr.shape[0]
|
| 251 |
+
assert Xtr.shape[1] == 8 and Xtr.shape[2] == N and (Xtr.shape[3] == N)
|
| 252 |
+
assert (
|
| 253 |
+
len(sp_idx["train"]) == 3200
|
| 254 |
+
and len(sp_idx["val"]) == 400
|
| 255 |
+
and (len(sp_idx["test"]) == 400)
|
| 256 |
+
)
|
| 257 |
+
xu, yu = gen_uniform(SEED_HOLDOUT)
|
| 258 |
+
assert xu.shape == (8, N, N) and np.isfinite(yu).all() and (yu.max() > yu.min())
|
| 259 |
+
print(
|
| 260 |
+
f"[Smoke] n={n} train={len(sp_idx['train'])} val={len(sp_idx['val'])} test={len(sp_idx['test'])} ch={len(CH8)} holdoutT[{yu.min():.2f},{yu.max():.2f}]",
|
| 261 |
+
flush=True,
|
| 262 |
+
)
|
| 263 |
+
return
|
| 264 |
+
import h5py, torch, torch.nn as nn, torch.nn.functional as F
|
| 265 |
+
|
| 266 |
+
sys.path.insert(0, "/workspace/pde_surrogate/scripts")
|
| 267 |
+
from train_fno_v32 import FNO2dV32, phys_prior, spatial_grad_mag
|
| 268 |
+
|
| 269 |
+
AMBCH = 4
|
| 270 |
+
|
| 271 |
+
def load_h5(path, split):
|
| 272 |
+
with h5py.File(path, "r") as f:
|
| 273 |
+
return (
|
| 274 |
+
torch.from_numpy(f[split]["X"][:].astype(np.float32)),
|
| 275 |
+
torch.from_numpy(f[split]["Y"][:].astype(np.float32)),
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
dev = "cuda"
|
| 279 |
+
with h5py.File(args.data, "r") as f:
|
| 280 |
+
x_mean = np.array(f.attrs["x_mean"], np.float32)
|
| 281 |
+
x_std = np.array(f.attrs["x_std"], np.float32)
|
| 282 |
+
y_mean = float(f.attrs["y_mean"])
|
| 283 |
+
y_std = float(f.attrs["y_std"])
|
| 284 |
+
channels = json.loads(f.attrs["channels"])
|
| 285 |
+
base_in = f["train"]["X"].shape[1]
|
| 286 |
+
Xtr, Ytr = load_h5(args.data, "train")
|
| 287 |
+
Xva, Yva = load_h5(args.data, "val")
|
| 288 |
+
with h5py.File(args.hot, "r") as f:
|
| 289 |
+
Xh = torch.from_numpy(f["hot"]["X"][:].astype(np.float32))
|
| 290 |
+
Yh = torch.from_numpy(f["hot"]["Y"][:].astype(np.float32))
|
| 291 |
+
Xtr = torch.cat([Xtr, Xh], 0)
|
| 292 |
+
Ytr = torch.cat([Ytr, Yh], 0)
|
| 293 |
+
xm_ = Xtr.reshape(Xtr.shape[0], base_in, -1).mean((0, 2))
|
| 294 |
+
xs_ = Xtr.reshape(Xtr.shape[0], base_in, -1).std((0, 2)) + 1e-06
|
| 295 |
+
x_mean = xm_.numpy()
|
| 296 |
+
x_std = xs_.numpy()
|
| 297 |
+
Xtr = Xtr.to(dev)
|
| 298 |
+
Ytr = Ytr.to(dev)
|
| 299 |
+
Xva = Xva.to(dev)
|
| 300 |
+
Yva = Yva.to(dev)
|
| 301 |
+
ntr = Xtr.shape[0]
|
| 302 |
+
xm = torch.tensor(x_mean, device=dev)[None, :, None, None]
|
| 303 |
+
xs = torch.tensor(x_std, device=dev)[None, :, None, None]
|
| 304 |
+
model = FNO2dV32(
|
| 305 |
+
in_ch=base_in, modes=args.modes, width=args.width, L=args.layers
|
| 306 |
+
).to(dev)
|
| 307 |
+
opt = torch.optim.AdamW(model.parameters(), lr=args.lr, weight_decay=1e-05)
|
| 308 |
+
sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, args.epochs)
|
| 309 |
+
print(
|
| 310 |
+
f"[V32] params={sum((p.numel() for p in model.parameters())) / 1000000.0:.2f}M ntr={ntr}",
|
| 311 |
+
flush=True,
|
| 312 |
+
)
|
| 313 |
+
|
| 314 |
+
def prep(Xb):
|
| 315 |
+
dTc, dTd = phys_prior(Xb)
|
| 316 |
+
return ((Xb - xm) / xs, dTc, dTd)
|
| 317 |
+
|
| 318 |
+
def val_binned():
|
| 319 |
+
model.eval()
|
| 320 |
+
rls = []
|
| 321 |
+
rises = []
|
| 322 |
+
mae = 0.0
|
| 323 |
+
vb = 0
|
| 324 |
+
with torch.no_grad():
|
| 325 |
+
for s in range(0, Xva.shape[0], args.bs):
|
| 326 |
+
Xb = Xva[s : s + args.bs]
|
| 327 |
+
Yb = Yva[s : s + args.bs][:, None]
|
| 328 |
+
Xin, dTc, dTd = prep(Xb)
|
| 329 |
+
field_ps, log_scale, _ = model(Xin, dTc, dTd)
|
| 330 |
+
amb = Xb[:, AMBCH].flatten(1).mean(1)
|
| 331 |
+
log_scale = log_scale.clamp(
|
| 332 |
+
float(np.log(args.scale_floor)), float(np.log(2000.0))
|
| 333 |
+
)
|
| 334 |
+
pp = (
|
| 335 |
+
amb[:, None, None, None]
|
| 336 |
+
+ torch.exp(log_scale)[:, None, None, None] * field_ps
|
| 337 |
+
)
|
| 338 |
+
num = torch.linalg.norm((pp - Yb).flatten(1), dim=1)
|
| 339 |
+
den = torch.linalg.norm(Yb.flatten(1), dim=1).clamp_min(1e-08)
|
| 340 |
+
rls.append((num / den).cpu())
|
| 341 |
+
rises.append((Yb.flatten(1).amax(1) - amb).cpu())
|
| 342 |
+
mae += torch.abs(pp - Yb).mean().item()
|
| 343 |
+
vb += 1
|
| 344 |
+
rls = torch.cat(rls)
|
| 345 |
+
rises = torch.cat(rises)
|
| 346 |
+
mae /= vb
|
| 347 |
+
hot = rls[rises > 10]
|
| 348 |
+
return (
|
| 349 |
+
float(rls.median()),
|
| 350 |
+
float(hot.median()) if hot.numel() else float("nan"),
|
| 351 |
+
mae,
|
| 352 |
+
int((rises > 10).sum()),
|
| 353 |
+
)
|
| 354 |
+
|
| 355 |
+
best = 1000000000.0
|
| 356 |
+
for ep in range(args.epochs):
|
| 357 |
+
model.train()
|
| 358 |
+
perm = torch.randperm(ntr, device=dev)
|
| 359 |
+
tl = 0.0
|
| 360 |
+
nb = 0
|
| 361 |
+
for s in range(0, ntr, args.bs):
|
| 362 |
+
idx = perm[s : s + args.bs]
|
| 363 |
+
Xb = Xtr[idx]
|
| 364 |
+
Yb = Ytr[idx][:, None]
|
| 365 |
+
amb = Xb[:, AMBCH].flatten(1).mean(1)
|
| 366 |
+
flat = Yb.flatten(1)
|
| 367 |
+
scale = torch.quantile(flat - amb[:, None], 0.95, dim=1).clamp_min(
|
| 368 |
+
args.scale_floor
|
| 369 |
+
)
|
| 370 |
+
Yps = (Yb - amb.view(-1, 1, 1, 1)) / scale.view(-1, 1, 1, 1)
|
| 371 |
+
Xin, dTc, dTd = prep(Xb)
|
| 372 |
+
opt.zero_grad()
|
| 373 |
+
field_ps, log_scale, prior = model(Xin, dTc, dTd)
|
| 374 |
+
gm = spatial_grad_mag(Yps)
|
| 375 |
+
gw = 1.0 + args.grad_w * gm / (
|
| 376 |
+
gm.flatten(1).mean(1)[:, None, None, None] + 1e-06
|
| 377 |
+
)
|
| 378 |
+
rise = (flat.amax(1) - amb).clamp_min(0.0)
|
| 379 |
+
sw = ((rise + 1.0) ** args.rise_w)[:, None, None, None]
|
| 380 |
+
sw = sw / sw.mean()
|
| 381 |
+
field_loss = (gw * sw * (field_ps - Yps) ** 2).mean()
|
| 382 |
+
scale_loss = F.smooth_l1_loss(log_scale, torch.log(scale), beta=0.1)
|
| 383 |
+
loss = field_loss + args.scale_w * scale_loss
|
| 384 |
+
loss.backward()
|
| 385 |
+
opt.step()
|
| 386 |
+
tl += loss.item()
|
| 387 |
+
nb += 1
|
| 388 |
+
sched.step()
|
| 389 |
+
overall, hotm, mae, nhot = val_binned()
|
| 390 |
+
score = hotm if hotm == hotm else overall
|
| 391 |
+
if score < best:
|
| 392 |
+
best = score
|
| 393 |
+
torch.save(
|
| 394 |
+
{
|
| 395 |
+
"model": model.state_dict(),
|
| 396 |
+
"args": vars(args),
|
| 397 |
+
"x_mean": x_mean,
|
| 398 |
+
"x_std": x_std,
|
| 399 |
+
"y_mean": y_mean,
|
| 400 |
+
"y_std": y_std,
|
| 401 |
+
"channels": channels,
|
| 402 |
+
"scale_floor": args.scale_floor,
|
| 403 |
+
"amb_channel": AMBCH,
|
| 404 |
+
"in_ch": base_in,
|
| 405 |
+
},
|
| 406 |
+
args.out,
|
| 407 |
+
)
|
| 408 |
+
if ep % 10 == 0 or ep == args.epochs - 1:
|
| 409 |
+
print(
|
| 410 |
+
f"Ep{ep:3d} loss={tl / nb:.4e} overall={overall:.4f} HOTSPOT(>10C,n={nhot})={hotm:.4f} MAE={mae:.3f}C (best {best:.4f})",
|
| 411 |
+
flush=True,
|
| 412 |
+
)
|
| 413 |
+
print(f"[V32] DONE best HOTSPOT {best:.4f} -> {args.out}", flush=True)
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
if __name__ == "__main__":
|
| 417 |
+
main()
|
verify.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
sys.path.insert(0, os.path.dirname(__file__))
|
| 7 |
+
from modeling import load_model
|
| 8 |
+
|
| 9 |
+
SHAPE = (1, 8, 96, 96)
|
| 10 |
+
VERIFICATION = 0.566771
|
| 11 |
+
TOL = 0.001
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def fixed_input():
|
| 15 |
+
torch.manual_seed(0)
|
| 16 |
+
return torch.randn(*SHAPE)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def main():
|
| 20 |
+
ap = argparse.ArgumentParser()
|
| 21 |
+
ap.add_argument("--weights", default=".")
|
| 22 |
+
args = ap.parse_args()
|
| 23 |
+
model = load_model(args.weights)
|
| 24 |
+
x = fixed_input()
|
| 25 |
+
with torch.no_grad():
|
| 26 |
+
out = model(x)
|
| 27 |
+
if tuple(out.shape) != (1, 1, 96, 96):
|
| 28 |
+
print(
|
| 29 |
+
f"Fail: unexpected output shape {tuple(out.shape)}, expected (1, 1, 96, 96)"
|
| 30 |
+
)
|
| 31 |
+
sys.exit(1)
|
| 32 |
+
center = out[0, 0, 48, 48].item()
|
| 33 |
+
print(f"Captured center value: {center:.6f}")
|
| 34 |
+
if abs(center - VERIFICATION) > TOL:
|
| 35 |
+
print(
|
| 36 |
+
f"Fail: center {center:.6f} differs from verification {VERIFICATION:.6f} by more than {TOL}"
|
| 37 |
+
)
|
| 38 |
+
sys.exit(1)
|
| 39 |
+
print(f"Verification check OK (center within {TOL} of {VERIFICATION:.6f})")
|
| 40 |
+
sys.exit(0)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
if __name__ == "__main__":
|
| 44 |
+
main()
|