|
|
A comprehensive Python library Built on scikit-fem to perform projection-based model order reduction of finite element models, enabling fast and accurate computational simulations through dimensionality reduction techniques.
- Affine parameter decomposition for efficient online evaluation
- Hyperreduction techniques (ECSW/DEIM/SOPT/ECM) for further computational speedup
- Comprehensive error estimation and validation tools
- Visualization tools for ROM analysis and diagnostics
# Windows
git clone https://github.com/suparnob100/scikit-rom.git
cd scikit-rom
conda create -n scikitrom python==3.11 (we recommend 3.11 so that you can use prebuilt petsc whl)
conda activate scikitrom
# without petsc
pip install -e .
# with petsc (optional)
conda install -c conda-forge -y mkl intel-openmp
conda install -c conda-forge -y msmpi mpi4py
pip install -e .[petsc311] (optional)# Mac
git clone https://github.com/suparnob100/scikit-rom.git
cd scikit-rom
conda create -n scikitrom python==3.11 (we recommend 3.11 so that you can use prebuilt petsc whl)
conda activate scikitrom
# without petsc
pip install -e .
# with petsc (optional)
Apple Silicon (M1/M2/M3) β recommended
conda install -c conda-forge -y libblas liblapack openblas llvm-openmp
Intel Mac β two options
**Option A (OpenBLAS + OpenMP, safest on conda-forge):**
conda install -c conda-forge -y libblas liblapack openblas llvm-openmp
**Option B (MKL, only if you specifically want MKL on Intel Mac):**
conda install -c conda-forge -y mkl intel-openmp
conda install -c conda-forge -y mpich mpi4py
pip install -e .[petsc311]# Linux
git clone https://github.com/suparnob100/scikit-rom.git
cd scikit-rom
# Create env (3.11 recommended for petsc4py wheels where available)
conda create -n scikitrom -y python=3.11
conda activate scikitrom
# without petsc
pip install -e .
# with petsc (optional)
# BLAS/OpenMP (Linux equivalent of MKL+intel-openmp)
# Option A (recommended on conda-forge): OpenBLAS + OpenMP runtime
conda install -c conda-forge -y libblas liblapack openblas llvm-openmp
# MPI (Linux equivalent of MS-MPI)
# Option A (recommended): MPICH
conda install -c conda-forge -y mpich mpi4py
# Option B: OpenMPI (use this instead of MPICH if you prefer)
# conda install -c conda-forge -y openmpi mpi4py
pip install -e .[petsc311] # optional- Online Documentation - Complete API reference and tutorials
- Getting Started Guide - Step-by-step introduction with examples
scikit-rom/
βββ docs/
βββ examples/
β βββ computational_mechanics/
β β βββ dynamic/
β β βββ static/
β β βββ linear/
β β βββ non_linear/
β βββ heat_transfer/
β βββ dynamic/
β βββ static/
β βββ linear/
β βββ non_linear/
βββ src/
β βββ skrom/
β βββ fom/
β β βββ fem_utils.py
β βββ problem_classes/
β β βββ masterclass.py
β β βββ masterclass_parallel.py
β βββ rom/
β β βββ bilinear_form_rom.py
β β βββ linear_form_rom.py
β β βββ rom_utils.py
β β βββ rom_error_est.py
β β βββ rom_error_est_t.py
β β βββ deim/
β β β βββ deim.py
β β β βββ bilinear_form_hyperrom_deim.py
β β β βββ linear_form_hyperrom_deim.py
β β βββ ecsw/
β β βββ hyperreduce.py
β β βββ custom_nnls.py
β β βββ bilinear_form_hyperrom_ecsw.py
β β βββ linear_form_hyperrom_ecsw.py
β βββ templates/
β β βββ problem_template/
β β βββ domain.py
β β βββ bilinear_forms.py
β β βββ linear_forms.py
β β βββ properties.py
β β βββ params.py
β β βββ problem_def.py
β β βββ problem.ipynb
β βββ utils/
β βββ imports.py
β βββ hdf5_store.py
β βββ save_h5.py
β βββ data_io/
β β βββ save_h5.py
β βββ dynamics/
β β βββ integrators.py
β βββ reduced_basis/
β β βββ svd.py
β βββ visualization/
β βββ color_palette.py
β βββ generate_vtk.py
β βββ generate_vtu.py
β βββ plot_utils.py
β βββ vtuwriter.py
βββ tests/
Here's a simple example of building a ROM for 1D heat conduction:
FOM and ROMVirtually identical solutions from the full order model (FOM) and reduced order model (ROM)Error and speedup associated with the reduced order model# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Imports & Setup
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
from pathlib import Path
notebook_path = Path().resolve()
from skrom.utils.imports import *
from skrom.rom.rom_utils import *
from skrom.rom.rom_error_est import *
from skrom.utils.visualization.color_palette import set_color_palette
from skrom.utils.reduced_basis.svd import svd_mode_selector
from skrom.rom.bilinear_form_rom import BilinearFormROM
from skrom.rom.linear_form_rom import LinearFormROM
from skfem.helpers import grad, dot
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import qmc # for Sobol
import time
set_color_palette()
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Mesh & BC
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
nx, x_end = 2**17, 0.5
mesh = MeshLine(np.linspace(0, x_end, nx+1))
basis = Basis(mesh, ElementLineP1())
bc_val = 573.15
D = np.where(np.isclose(basis.doflocs[0], x_end))[0]
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Material & Source
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def conductivity(mu: float=0) -> float:
"""$k(ΞΌ)=16+ΞΌ$."""
return 16 + mu
def heat_source(beta: float=0) -> float:
"""$Q(Ξ²)=35000+Ξ²$."""
return 35000 + beta
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Forms & Assembly
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@LinearForm
def l(v,p):
"""$l(v;p)=β«Q(Ξ²)\,v\,dx$."""
return heat_source(p['beta'])*v
@BilinearForm
def a(u,v,p):
"""$a(u,v;p)=β«k(ΞΌ)\,\nabla uΒ·\nabla v\,dx$."""
return conductivity(p['mu'])*dot(grad(u),grad(v))
def assemble_system(p):
"""Return stiffness, load for params p."""
return asm(a,basis,mu=p[0]), asm(l,basis,beta=p[1])
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Sobol Sampling
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def generate_sobol(d,n,bounds):
"""Sobol in $[β_i,u_i]$, n=2^m."""
sampler = qmc.Sobol(d)
S = sampler.random_base2(m=int(np.log2(n)))
X = np.empty_like(S)
for i,(β,u) in enumerate(bounds):
X[:,i] = β + S[:,i]*(u-β)
return X
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Data Generation & Split
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
param_ranges = [(-4,4),(-1000,1000)]
N_snap = 32
P_train = generate_sobol(2,N_snap,param_ranges)
P_test = generate_sobol(2,N_snap,param_ranges)
P = np.vstack((P_train,P_test))
mask = np.zeros(2*N_snap,bool); mask[:N_snap]=True
train_mask,test_mask = mask,~mask
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Full-Order Solve (Affine)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
M0,b0 = assemble_system([-15,-34999]) # k=1,Q=1
fos_sols, fos_times = [], []
for ΞΌ,Ξ² in P:
t0 = time.perf_counter()
A = conductivity(ΞΌ)*M0
f = heat_source(Ξ²)*b0
u = basis.zeros(); u[D]=bc_val
sol = solve(*condense(A,f,x=u,D=D))
fos_times.append(time.perf_counter()-t0)
fos_sols.append(sol.copy())
LS = np.array(fos_sols)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Training/Test Solutions & Centering
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
LS_train, LS_test = LS[train_mask], LS[test_mask]
mean_train = LS_train.mean(0)
MS = LS_train - mean_train
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# POD Mode Selection
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
n_sel, U = svd_mode_selector(MS, tolerance=1e-10, modes=True)
V = U[:,:n_sel]
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# ROM Form Construction
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
free = np.setdiff1d(np.arange(basis.N),D)
Br = BilinearFormROM(a,basis,V,V,free_dofs=free,mean=mean_train)
Lr = LinearFormROM(l,basis,V,free_dofs=free,mean=mean_train)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Offline ROM Affine Assembly
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Mr0 = Br.assemble(basis,mu=-15)
br0 = Lr.assemble(beta=-34999)
mean_red = V.T@(M0@mean_train)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Online ROM Solve & Metrics
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
speed, error, LS_rom = [], [], []
fos_test_time = np.array(fos_times)[test_mask]
i = 0
for (ΞΌ,Ξ²),fos_time in zip(P_test,fos_test_time):
t0 = time.perf_counter()
Mr = conductivity(ΞΌ)*Mr0
br = heat_source(Ξ²)*br0 - conductivity(ΞΌ)*mean_red
ur = np.linalg.solve(Mr,br)
uR = reconstruct_solution(ur,V,mean_train)
dt = time.perf_counter()-t0
speed.append(fos_time/dt)
error.append(100*np.linalg.norm(LS_test[i]-uR)/np.linalg.norm(LS_test[i])+1e-15)
LS_rom.append(uR.copy())
i = i + 1
LS_rom = np.array(LS_rom)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Error Analysis & Reporting
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
matrix = compute_rom_error_metrics_flat(LS_test,LS_rom)
generate_rom_error_report(matrix)
# plot_rom_error_diagnostics_flat(
# LS_test,LS_rom,error,speed,
# sim_axis=['True','ROM'],metrics=matrix
# )
LS_rom = np.asarray(LS_rom)
# Assign the list of speedβup ratios (FOM time / ROM time) to a variable:
# speed_up[i] = t_fos_test[i] / t_rom[i]
ROM_speed_up = speed
# Optional: drop the first entry if it's skewed by startup overhead
# (e.g., JIT, memory allocation). Now ROM_speed_up.shape == (N_test - 1,).
ROM_speed_up = ROM_speed_up[1:]
# Assign the list of relative errors (in %) for each test sample:
# ROM_relative_error[i]
# = 100 Β· βu_fos β u_romββ / βu_fosββ
ROM_relative_error = error
plot_rom_error_diagnostics_flat(
LS_test, # fullβorder solution snapshots u_fos^(i)
LS_rom, # hyperβROM solution snapshots u_rom^(i)
ROM_relative_error, # list [e_1, β¦, e_N]
ROM_speed_up, # list [s_1, β¦, s_N]
sim_axis=['True','ROM'], # axis labels for true vs. ROM scatter
metrics=matrix # the computed metrics matrix
)===================
ROM Accuracy Report
===================
Global Errors:
L2 Error: 8.2505e-06
Relative L2 Error: 5.2750e-12
Lβ Error: 2.0845e-08
Relative Lβ Error: 2.2554e-11
RMSE: 4.0286e-09
MAE: 1.6009e-09
Statistical Fit:
RΒ² Score: 1.0000
Explained Variance: 1.0000
Error Distribution:
Median Error: -9.4133e-11
95th Percentile Error: 1.1765e-08
Time/Parameter-Dependent Errors:
Average Rel L2 Error over time/parameter: 2.3908e-12
Max Rel L2 Error over time/parameter: 1.5451e-11
Min Rel L2 Error over time/parameter: 6.5942e-14
Python Version
- Requires Python 3.11 or higher
Core Dependencies The project depends on the following libraries:
numpyβ Numerical computingscipyβ Scientific computingsympyβ Symbolic mathematicsmatplotlib==3.8.4β Visualizationscikit-fem[all]β Finite element method framework with all optional extrassci-mplstyle-packageβ Custom scientific matplotlib stylespyamgβ Algebraic multigrid solverspyDOEβ Design of experiments toolkitptitprinceβ Statistical visualization (e.g., raincloud plots)
scikit-rom can be useful for scenarios requiring many-query simulations:
- Parameter Studies: Exploring system response across parameter ranges
- Optimization: Design optimization without repeated full-scale simulations
- Real-time Applications: Control systems and interactive simulations
- Uncertainty Quantification: Monte Carlo studies with many parameter samples
- Digital Twins: Efficient real-time model updates
- scikit-fem - Finite element library that scikit-rom builds upon
- pyMOR - Model order reduction library
- libROM - C++ library for ROM
- SROMPy - Stochastic reduced order models
- Documentation: https://scikitrom.github.io/




