Downloads · 30 days
14
2% of all-time downloads
lowdown-labs/fela-pde
fela-pde is a image-to-image model from lowdown-labs. Use it when you need one image transformed into another. It is set up for transformers. The card lists the license as other.
This model is a research preview. Lowdown Labs has put together this model in the interest of advancing public science.
Downloads · 30 days
14
2% of all-time downloads
All-time downloads
644
Public
Parameters
1.8M
10.7 MB on disk
Likes
0
Public
Click a slice to open those files.
.safetensors10.7 MB · 99%
From the Hugging Face model README
This model is a research preview. Lowdown Labs has put together this model in the interest of advancing public science.
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.
(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.(1, 1, 96, 96).
modeling.denormalize converts it to degrees Celsius using the training y statistics
(Y_degC = Ynorm * y_std + y_mean).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.
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:
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.
If you already have physical field maps (say from your own thermal model), pass them directly instead of pack parameters:
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.
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):
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.
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.
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).modeling.py; the arch dims and
normalization statistics are in config.json.train.py (--smoke regenerates the split and
asserts the sizes). Full details, seeds, and the split are in train.py.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.
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.
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.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.
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.