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, dtype="auto") - Notebooks
- Google Colab
- Kaggle
| license: other | |
| license_name: lowdown-labs-lovely-license-1.0 | |
| license_link: LICENSE | |
| tags: | |
| - fela | |
| - fourier-neural-operator | |
| - fno | |
| - cpu | |
| - on-device | |
| - pde-surrogate | |
| - thermal-simulation | |
| - battery | |
| library_name: transformers | |
| pipeline_tag: image-to-image | |
| # DISCLAIMER | |
| This model is a research preview. Lowdown Labs has put together | |
| this model in the interest of advancing public science. | |
| # FELA PDE: on device 2D thermal field surrogate for battery packs | |
| Give FELA PDE the layout of a battery pack, its heat load, and how it is being cooled, and it | |
| tells you where the pack runs hot. It returns the full steady state temperature map in one fast | |
| pass, standing in for a slower finite volume solve. It runs on a plain CPU with no GPU, so it can | |
| sit inside a battery management tool, a design loop, or an on premises engineering app and flag | |
| hot spots without a cloud round trip. | |
| What ships in this repo is the small web lite version: 892,545 parameters, about 7.1 MB in fp32. | |
| The larger validated teacher is a separate line and is not shipped here. | |
| # What goes in, what comes out | |
| - Input: an 8 channel physics field on a 96x96 grid, shape `(1, 8, 96, 96)`. The channels, in | |
| order, are `mask` (pack solid region), `q_source` (volumetric heat source, the hot spot), | |
| `k_field` (thermal conductivity), `h_conv` (convective heat transfer coefficient), `T_amb` | |
| (ambient temperature), `x_coord`, `y_coord` (normalized 0..1 coordinates), and | |
| `log_domain_L` (log of the physical domain size). Each channel is standardized with the | |
| training statistics that ship in `config.json`; `modeling.preprocess` does this for you. | |
| - Output: a 1 channel normalized temperature field, shape `(1, 1, 96, 96)`. | |
| `modeling.denormalize` converts it to degrees Celsius using the training y statistics | |
| (`Y_degC = Ynorm * y_std + y_mean`). | |
| - In plain terms: give it the pack geometry, the heat load, and the cooling conditions, and it | |
| returns the predicted temperature map so an engineer can see where the pack runs hot. | |
| # Building an input (for battery and BMS engineers) | |
| You do not hand build the 8 channel tensor. `input_builder.py` (an add on shipped in this repo) | |
| builds it from ordinary pack parameters, matching the exact encoding the model was trained on | |
| (the coordinate planes, `log_domain_L`, and the per channel standardization from `config.json`). | |
| Two of the channels and `log_domain_L` are model conventions, not physics you supply. The | |
| physical channels and their units are: | |
| | Channel | Meaning | Units | Typical range | | |
| |---|---|---|---| | |
| | mask | 1 inside a cell, 0 in the coolant | none | 0 or 1 | | |
| | q_source | heat source density in the cells | W/m3 | derived from current, SoC, R0 | | |
| | k_field | thermal conductivity | W/(m K) | cell 1 to 30, coolant 0.1 to 1.5 | | |
| | h_conv | convective heat transfer coefficient | W/(m2 K) | 5 to 200 | | |
| | T_amb | ambient temperature | degC | 15 to 40 | | |
| | x_coord | normalized column position (the builder sets this) | none | 0 to 1 | | |
| | y_coord | normalized row position (the builder sets this) | none | 0 to 1 | | |
| | log_domain_L | natural log of the physical pack size (the builder sets this) | ln(m) | pack 0.02 to 0.12 m | | |
| The model was trained on this distribution; inputs well outside these ranges are not characterized. | |
| ## The BMS path: from_pack | |
| Give it a cell layout and pack parameters. It computes the heat source | |
| (`P = current^2 * R0 * (1 + beta * (1 - SoC)^2)`, spread over the cell area), the conductivity map, | |
| and the rest, then returns a ready to run `(1, 8, 96, 96)` tensor: | |
| ```python | |
| import torch | |
| from input_builder import from_pack, cylinder_mask | |
| from modeling import load_model, denormalize | |
| model = load_model(".") | |
| mask = cylinder_mask(rows=3, cols=4, radius_frac=0.4) # a 3 by 4 cylindrical cell pack | |
| x = from_pack( | |
| mask, | |
| current_A=40.0, soc=0.3, R0_ohm=0.02, | |
| k_cell_W_mK=20.0, k_coolant_W_mK=0.6, | |
| h_conv_W_m2K=80.0, T_amb_degC=25.0, domain_L_m=0.08, | |
| ) | |
| with torch.no_grad(): | |
| T = denormalize(model(x))[0, 0] # a 96 by 96 temperature map in degC | |
| print("peak", float(T.max()), "degC") | |
| ``` | |
| `cylinder_mask(rows, cols, radius_frac)` and `rect_mask(aspect, fill)` build the geometry mask. | |
| `example.py` runs this end to end and prints the peak temperature and hottest cell. | |
| ## The field path: from_fields | |
| If you already have physical field maps (say from your own thermal model), pass them directly | |
| instead of pack parameters: | |
| ```python | |
| from input_builder import from_fields | |
| x = from_fields(mask, q_source_W_m3, k_field_W_mK, h_conv_W_m2K, T_amb_degC, domain_L_m) | |
| ``` | |
| Each argument is a 96 by 96 array or a scalar (scalars are broadcast). The builder grids each to | |
| 96 by 96, adds the coordinate and size channels, standardizes, and returns the model ready tensor. | |
| It is verified to reproduce the training encoding exactly. | |
| ## From a file: from_csv and from_json | |
| If your pack parameters live in a file, point the builder at it. `pack.csv` (a header row plus one | |
| values row) or `pack.json` (a flat object) use the same field names as `from_pack`, plus a geometry | |
| (`rows`, `cols`, `radius_frac` for a cylindrical pack, or `aspect`, `fill` for a prismatic block): | |
| ```python | |
| import torch | |
| from input_builder import from_csv | |
| from modeling import load_model, denormalize | |
| x = from_csv("pack.csv") # from_json("pack.json") works the same way | |
| with torch.no_grad(): | |
| T = denormalize(load_model(".")(x))[0, 0] | |
| ``` | |
| Example `pack.csv` and `pack.json` ship in this repo. | |
| NB - real battery data comes in many shapes this repo does not read yet, such as | |
| vendor spreadsheets with their own columns, CAD geometry like STEP or STL, and simulation exports | |
| from tools like COMSOL or ANSYS. `from_csv` and `from_json` handle the flat parameter case, which | |
| is the common one. If you already have a geometry or a field as numbers, load it into a 96 by 96 | |
| array yourself and pass it to `from_fields`. | |
| # Why we built it this way | |
| A temperature field is smooth and slowly varying, so we mix information in the frequency domain | |
| rather than pixel by pixel. That is what a Fourier Neural Operator does, it learns filters that | |
| act on the field's frequencies (an FFT, a learned filter, an inverse FFT), which suits a smooth | |
| solution field well. The model is small and has no all pairs attention. One forward pass produces | |
| the whole 96x96 map on a plain CPU, far faster than solving the same field directly with a | |
| finite volume method. | |
| # Architecture | |
| - 2D FNO: a lifting `Conv2d(8 -> 32, 1x1)`, then 3 spectral plus pointwise residual blocks | |
| (`SpectralConv2d` keeping 12x12 low and high Fourier modes plus a `Conv2d(32,32,1x1)` skip, GELU | |
| residual), then a projection head `Conv2d(32 -> 128, 1x1) -> GELU -> Conv2d(128 -> 1, 1x1)`. | |
| - 892,545 parameters. The full architecture is in `modeling.py`; the arch dims and | |
| normalization statistics are in `config.json`. | |
| # Training data | |
| - Self generated. Every training, validation, and test sample is produced on CPU by a | |
| deterministic steady state heat equation finite volume solver (pure NumPy and SciPy). No external | |
| dataset is downloaded, scraped, or redistributed. The PDE, the geometry parameterization, and | |
| the input and target encoding are reproduced in `train.py` (`--smoke` regenerates the split and | |
| asserts the sizes). Full details, seeds, and the split are in `train.py`. | |
| - License: none required. The generator is our own code; the finite volume method and the | |
| Fourier Neural Operator (Li et al., ICLR 2021) are published methods, not licensed data. | |
| Commercially clean. | |
| The shipped `train.py` reproduces the primary battery 2D training recipe (a larger `FNO2dV32` | |
| teacher with a length scale channel and an energy balance peak prior). The weights shipped here | |
| are the distilled web lite student described above. | |
| # How to run it | |
| ```python | |
| from huggingface_hub import hf_hub_download | |
| import modeling | |
| path = hf_hub_download("lowdown-labs/fela-pde", "model.safetensors") | |
| model = modeling.load_model(path) # or load_model("/path/to/weights_dir") | |
| # raw_field: an (8, 96, 96) physics field in physical units | |
| x = modeling.preprocess(raw_field) # standardizes and validates shape | |
| import torch | |
| with torch.no_grad(): | |
| y_norm = model(x) # (1, 1, 96, 96) normalized temperature | |
| y_degc = modeling.denormalize(y_norm) # degrees Celsius | |
| ``` | |
| `verify.py` runs a fixed sample input and checks the output shape and a verification value. | |
| # Serving artifacts | |
| - `model.safetensors` plus `config.json` for the safetensors load path (fp32). | |
| - `verify.py` runs a fixed sample input and checks the output shape and a verification value. | |
| # Intended use, limitations, and safety | |
| - This is a surrogate, not a certified thermal safety tool. Use its output as a fast screening | |
| and design aid, not as the sole basis for a safety critical decision. Validate against a full | |
| solver on your own configurations before relying on it. | |
| - Trained and evaluated only on the self generated battery thermal distribution described above. | |
| Geometries, materials, and boundary conditions outside that distribution are not characterized | |
| here. | |
| - This is the distilled web lite student. The larger validated checkpoints (battery and heatsink, | |
| 2D and 3D) are a separate line. | |
| # Acknowledgements and references | |
| - Fourier Neural Operator: Li, Z., Kovachki, N., Azizzadenesheli, K., et al. (2021). Fourier | |
| Neural Operator for Parametric Partial Differential Equations. ICLR. | |
| https://arxiv.org/abs/2010.08895 | |
| - Finite volume heat transfer: Patankar, S. V. (1980). Numerical Heat Transfer and Fluid Flow. | |
| - SciPy: Virtanen, P., et al. (2020). Nature Methods 17, 261-272. | |
| - PyTorch: Paszke, A., et al. (2019). NeurIPS. https://arxiv.org/abs/1912.01703 | |
| # Model family | |
| This is part of the FELA family from Lowdown Labs: one Fourier Neural Operator architecture | |
| across many modalities, all CPU native and subquadratic. Sibling repos are independently | |
| trained per modality and share no weights, so none carries a `base_model` link. | |
| # License | |
| 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. | |