Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
130 commits
Select commit Hold shift + click to select a range
29310d4
removed unused file
hmonroedd Mar 25, 2026
6a6be41
started adding pint
hmonroedd Mar 25, 2026
545c82c
Merge pull request #2 from LIBRA-project/Hippolyte
hmonroedd Mar 25, 2026
50f1bf0
Merge branch 'main' into pint
hmonroedd Mar 25, 2026
1cd9713
fixed circular import
hmonroedd Mar 25, 2026
bcb6417
I/O file names less confusing
hmonroedd Mar 26, 2026
a3f11de
implemented pint, structured input as a class
hmonroedd Apr 2, 2026
4f221f5
some cleaning
hmonroedd Apr 2, 2026
9353422
output files in dedicated folder
hmonroedd Apr 2, 2026
c889c4b
separate output and restart files
hmonroedd Apr 2, 2026
6d3e5dd
SimulationResults dataclass has attribute SimulationInput
hmonroedd Apr 2, 2026
0bad060
can initialize simulation from restart.yaml file, multiple key names …
hmonroedd Apr 2, 2026
7be9b79
tritium and neutron are separate dimensions + units consistency check
hmonroedd Apr 2, 2026
5585d9c
moved correlations to dedicated .py file, added config.py for shared …
hmonroedd Apr 2, 2026
0cb9ff4
fixed problem with celsius
hmonroedd Apr 2, 2026
da98234
sparging and irradiation signals being put in input
hmonroedd Apr 3, 2026
ef5fc05
add pint to environment
RemDelaporteMathurin Apr 3, 2026
24f8630
removed bp
RemDelaporteMathurin Apr 3, 2026
78c06c5
test instructions
RemDelaporteMathurin Apr 3, 2026
721cdaf
added first test
RemDelaporteMathurin Apr 3, 2026
56fad3a
added new test
RemDelaporteMathurin Apr 3, 2026
cccdfcb
added workflow
RemDelaporteMathurin Apr 3, 2026
4aa0826
modify conda
RemDelaporteMathurin Apr 3, 2026
520cd05
test if fails
RemDelaporteMathurin Apr 3, 2026
57c64a3
remove whitespace
RemDelaporteMathurin Apr 3, 2026
a56e649
removed assert False
RemDelaporteMathurin Apr 3, 2026
6ef0943
module structure
RemDelaporteMathurin Apr 3, 2026
c106849
update instructions + CI
RemDelaporteMathurin Apr 3, 2026
6728972
fixed import
RemDelaporteMathurin Apr 3, 2026
64938ba
fixed circular import
RemDelaporteMathurin Apr 3, 2026
7b964a1
Merge pull request #5 from LIBRA-project/ci
RemDelaporteMathurin Apr 3, 2026
c0c670b
small refactoring + added tests
RemDelaporteMathurin Apr 3, 2026
c92bef9
more refactoring
RemDelaporteMathurin Apr 3, 2026
01755c5
updated docs
RemDelaporteMathurin Apr 3, 2026
bfc5edb
type hinting + make sure units are stripped
RemDelaporteMathurin Apr 3, 2026
9320817
python>3.12
RemDelaporteMathurin Apr 3, 2026
4723344
removed classifiers
RemDelaporteMathurin Apr 3, 2026
8234a1a
python requires
RemDelaporteMathurin Apr 3, 2026
93b9913
Merge pull request #6 from LIBRA-project/pint_review
RemDelaporteMathurin Apr 3, 2026
eb2a429
Merge pull request #4 from LIBRA-project/pint
RemDelaporteMathurin Apr 3, 2026
f85c4a2
example correlation
RemDelaporteMathurin Apr 3, 2026
59797b3
removed correlation dict
RemDelaporteMathurin Apr 3, 2026
63ad812
removed example fiel
RemDelaporteMathurin Apr 3, 2026
948ffce
good progress!
RemDelaporteMathurin Apr 3, 2026
6c68a4e
fixed units
RemDelaporteMathurin Apr 3, 2026
0649414
"working" version
RemDelaporteMathurin Apr 6, 2026
9ec7856
removed old function
RemDelaporteMathurin Apr 6, 2026
48e0778
removed resolve
RemDelaporteMathurin Apr 6, 2026
3008b96
source_T from tbr, flow_g_vol from flow_g_mol, more realistic input
hmonroedd Apr 6, 2026
96c8a2a
to solve: use correlation.__call__
hmonroedd Apr 6, 2026
00607bb
more realistic input
hmonroedd Apr 7, 2026
7957b4d
avoid bug in pint.Quantity.check with "dimensionless", check output u…
hmonroedd Apr 7, 2026
ca16d0f
remove print
hmonroedd Apr 7, 2026
535be29
simplify from_parameters(), break down graph search algo in several f…
hmonroedd Apr 7, 2026
25b7a67
working input corresponding to Libra Pi
hmonroedd Apr 7, 2026
4ba8521
fixed Re and added nu_l correlation
hmonroedd Apr 7, 2026
7fa5658
working
hmonroedd Apr 7, 2026
83d4424
check all_correlations contains wanted correlation before fetching it
hmonroedd Apr 7, 2026
ab45b81
removed unnecessary code/imports
hmonroedd Apr 7, 2026
b6e2890
added __str__ to SimulationInput
hmonroedd Apr 7, 2026
861d590
use logger instead of VERBOSE, removed unnecessary code/imports
hmonroedd Apr 7, 2026
71fe9ed
test for graph search algo (uses inputs.find_in_graph(), input.check_…
hmonroedd Apr 7, 2026
9892f83
added .log files used in tests, commented broken tests
hmonroedd Apr 7, 2026
4224d50
tmp_path + remove log from vc
hmonroedd Apr 7, 2026
034f2f2
simplifiy test
hmonroedd Apr 7, 2026
6a05d71
added test
hmonroedd Apr 7, 2026
fa46fa6
Merge branch 'refactoring' of https://github.com/LIBRA-project/libra_…
hmonroedd Apr 7, 2026
b70a25b
added coverage
hmonroedd Apr 7, 2026
7580d32
added tests for inputs.check_input() and input.find_in_graph()
hmonroedd Apr 7, 2026
3ef61e2
returns in graph search algo are less condusing
hmonroedd Apr 7, 2026
48e11d5
made flow_g_mol and P_top mandatory quantities and fixed tests
hmonroedd Apr 7, 2026
60543ab
fixed circular dependency of flow_g_mol and flow_g_vol + adapted tests
hmonroedd Apr 7, 2026
30111ea
removed unused code
hmonroedd Apr 7, 2026
10867c6
tests for model.solve() and exclude animation.py from coverage
hmonroedd Apr 7, 2026
9e0ed4f
test building whole SimulationInput using from_parameters()
hmonroedd Apr 7, 2026
da8e654
test successful exports to yaml, json and csv
hmonroedd Apr 7, 2026
2ef1d31
made specifying input_units mandatory + tests for Correlation and Cor…
hmonroedd Apr 8, 2026
3aa8cbf
create Simulation object + adapted tests
hmonroedd Apr 8, 2026
ba31a4e
use @pytest.fixture to avoid modifications propagating to different t…
hmonroedd Apr 8, 2026
921d95e
can specify profile for source term
hmonroedd Apr 8, 2026
cab6436
added to_json() method to SimulationInput to be able to compare built…
hmonroedd Apr 8, 2026
01ed676
added to vc needed .json (for tests) + delete unused file
hmonroedd Apr 8, 2026
f02586e
optimized source term profile update (no interpolation at every time …
hmonroedd Apr 9, 2026
853fd0f
added hydrostatic pressure profile
hmonroedd Apr 9, 2026
aab8a24
Added J_T2 in SimulationOutput
hmonroedd Apr 13, 2026
a7f442c
Added liquid phase dispersion E_l + updated test -> got a dispersion …
hmonroedd Apr 13, 2026
42ea13b
fixed inventory dependance on tank height (missing factor when integr…
hmonroedd Apr 13, 2026
d6d6130
fixed inventory dependance on tank height (truly this time) by making…
hmonroedd Apr 13, 2026
5c45de6
LIBRA 1L model
hmonroedd Apr 13, 2026
901c095
re-introduced dispersion terms, with neumann BC, mass conservation sh…
hmonroedd Apr 15, 2026
6bdae66
added quantities of the analytical model in SimulationInput
hmonroedd Apr 15, 2026
2850460
source normalization now based on integral source + added associated …
hmonroedd Apr 16, 2026
c3ea741
refactoring with postprocess function
hmonroedd Apr 16, 2026
5edb024
cleaner J_T2 post processing + retrocompatibility with python 3.12 to…
hmonroedd Apr 16, 2026
8be55c6
refactoring: put signals in SimulationInput
hmonroedd Apr 17, 2026
74e1342
standard, LIBRA 1L and LIBRA Pi inputs are now available from the spa…
hmonroedd Apr 17, 2026
5e58598
added a get_sim_input function and experiment specific params diction…
hmonroedd Apr 17, 2026
ff32a89
VERBOSE log level to have cleaner logs
hmonroedd Apr 17, 2026
57ff35b
added postprocessing.py + implemented pint in SimulationResults
hmonroedd Apr 17, 2026
7258384
few tweaks
hmonroedd Apr 17, 2026
b66ec05
removed unused file
hmonroedd Apr 17, 2026
b5b2366
Merge branch 'enriched_model' into sensitivity_analysis
hmonroedd Apr 17, 2026
f605cd8
added env file
hmonroedd Apr 17, 2026
8e727de
better inputs
hmonroedd Apr 17, 2026
e29103e
Merge branch 'sensitivity_analysis' into modifs-pp-rem
hmonroedd Apr 17, 2026
79141cb
serialize and deserialize SimulationResult to .JSON and .pkl
hmonroedd Apr 17, 2026
3ef0d66
test for sim results serialization
hmonroedd Apr 17, 2026
81c1ae2
sensitivity analysis scripts
hmonroedd Apr 23, 2026
d1f8bdb
post processing functions
hmonroedd Apr 23, 2026
df1fc37
added characteristic time as a metric
hmonroedd Apr 27, 2026
9a17003
Sensitivity analysis on low level parameters
hmonroedd Apr 27, 2026
00bdaac
more postprocessing for low level sensitivity analysis
hmonroedd Apr 27, 2026
71bbdde
environment
hmonroedd Apr 27, 2026
2e7af31
implemented networkx graph in input.py (graph search algo)
hmonroedd Apr 27, 2026
46c20b4
2nd order sobol study
hmonroedd Apr 28, 2026
2ceab2f
Merge branch 'pyvis_graph' into sensitivity_analysis
hmonroedd Apr 28, 2026
f212ce8
merged with networkx branch: retrieve intermediate parameters from graph
hmonroedd Apr 28, 2026
cb9764c
added "examples" and "data processing" folder
hmonroedd May 12, 2026
c8b2e0e
moved to "data processing" folder
hmonroedd May 12, 2026
999d9cd
molT2 to Bq unit conversion
hmonroedd May 12, 2026
52e8544
added Oishi diffusivity and set default to Calderoni
hmonroedd May 12, 2026
d8f5b72
changed values, more comments
hmonroedd May 12, 2026
af79120
updated environment yml
hmonroedd May 12, 2026
458a517
fixed test_from_parameters_success() + json_dump() doesn't dump lambd…
hmonroedd May 12, 2026
f1c107b
fixed test_simulation_input()
hmonroedd May 12, 2026
e6f1328
fixed test_simulation_results() so that it fails when it should fail
hmonroedd May 12, 2026
3435139
fixed test_solve()
hmonroedd May 12, 2026
5c5f950
exposed objects in API
hmonroedd May 12, 2026
b321998
moved experiment specific inputs to input_examples.py
hmonroedd May 12, 2026
7483c37
adapted external files to the API + add visualize_graph.py to the exa…
hmonroedd May 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI
on: [pull_request, push]

jobs:
run-tests:
name: run-tests
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v5

- name: Set up Conda
uses: conda-incubator/setup-miniconda@v3
with:
activate-environment: libra_sparging
environment-file: environment.yml
miniforge-version: latest
use-mamba: false
channels: conda-forge

- name: Install package
shell: bash -l {0}
run: |
python -m pip install -e .[dev]

- name: Run tests
shell: bash -l {0}
run: |
python -m pytest test --cov src/sparging --cov-report xml

- name: Upload to codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
13 changes: 11 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ cover/
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
Expand Down Expand Up @@ -207,6 +206,16 @@ marimo/_lsp/
__marimo__/

.vscode/
misc_scripts/
datasets/
figures/
*.yaml
*.csv
mwe.py
*.json
!standard_input.json
*.ipynb
!sensitivity.ipynb
!LL_sensitivity.ipynb
*.code-workspace
*.pkl
*.html
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,24 @@ To run the model:
> [!NOTE]
> This uses `dolfinx` which doesn't run on Linux. For windows users, consider using Windows Subsystem for Linux (WSL)

To simply use the library, use the libra_sparging environment:
```
conda env create -f environment.yml
conda activate libra_sparging
```

To also train a surrogate model (for sensitivity analysis, for example), you need autoemulate, use the autoemulate environment:
```
conda env create -f autoemulate_env.yml
conda activate autoemulate_env
```

```
python -m pip install -e .[dev]
```

## How to run tests

```
python model.py
python -m pytest test
```
17 changes: 17 additions & 0 deletions autoemulate_env.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: autoemulate_env
channels:
- conda-forge
dependencies:
- python=3.12
- fenics-dolfinx
- matplotlib
- pyvista
- pyyaml
- numpy
- scipy
- pandas
- pint
- networkx
- pip:
- autoemulate
- pyvis
989 changes: 989 additions & 0 deletions data_processing/LL_sensitivity.ipynb

Large diffs are not rendered by default.

468 changes: 468 additions & 0 deletions data_processing/sensitivity.ipynb

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ name: libra_sparging
channels:
- conda-forge
dependencies:
- python>=3.12
- fenics-dolfinx
- matplotlib
- pyvista
- pyyaml
- numpy
- scipy
- pandas
- pandas
- pint
- networkx
156 changes: 156 additions & 0 deletions examples/LL_sensitivity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
from datetime import datetime
from pathlib import Path
from sparging import (
LIBRA_PI_GEOM,
LIBRA_PI_MAT,
LIBRA_PI_OPERATING_PARAMS,
LIBRA_PI_SPARGING_PARAMS,
SimulationInput,
ureg,
all_correlations,
CorrelationType,
)
import logging
from autoemulate.simulations.base import Simulator
from autoemulate import AutoEmulate
from autoemulate.core.sensitivity_analysis import SensitivityAnalysis
import torch
import pandas as pd
import json
import networkx as nx

COMPUTE_SOBOL = False

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.WARNING)

FOLDER = Path("datasets") / datetime.now().strftime("%Y%m%d_%H%M%S")
FOLDER.mkdir(exist_ok=True, parents=True)
FOLDER_SAMPLES = FOLDER / "samples"
FOLDER_SAMPLES.mkdir(exist_ok=True, parents=True)
FOLDER_PP = FOLDER / "postprocessing"
FOLDER_PP.mkdir(exist_ok=True)

outputs = []
h_l_corrs = all_correlations.get_list(CorrelationType.MASS_TRANSFER_COEFF)
D_l_corrs = all_correlations.get_list(CorrelationType.DIFFUSIVITY)


class SpargingProblem(Simulator):
def __init__(self, parameters_range, output_names):
self.counter = 0
super().__init__(parameters_range, output_names)

def _forward(self, x: torch.Tensor) -> torch.Tensor:
# construct simulation input
LIBRA_PI_OPERATING_PARAMS.temperature = x[0, 0].item() * ureg.celsius
LIBRA_PI_OPERATING_PARAMS.P_top = x[0, 1].item() * ureg.bar
LIBRA_PI_OPERATING_PARAMS.flow_g_mol = x[0, 2].item() * ureg.sccm
LIBRA_PI_GEOM.nozzle_diameter = x[0, 3].item() * ureg.m
# LIBRA_PI_SPARGING_PARAMS.h_l = h_l_corrs[int(x[0, 4].item())]
LIBRA_PI_MAT.D_l = D_l_corrs[int(x[0, 4].item())]
# breakpoint()

graph = nx.Graph()
sim_input = SimulationInput.from_parameters(
LIBRA_PI_GEOM,
LIBRA_PI_MAT,
LIBRA_PI_OPERATING_PARAMS,
LIBRA_PI_SPARGING_PARAMS,
graph=graph,
)
tau = sim_input.get_tau()
h_l = sim_input.h_l
a = sim_input.a
eps_g = sim_input.eps_g
sim_input.to_json(
FOLDER_SAMPLES / f"sample_{self.counter}.json"
) # for debugging and postprocessing
self.counter += 1

# for post processing
Pi = sim_input.get_Pi_number().to("dimensionless").magnitude
PP_numbers.append(
[
Pi,
tau.to("s").magnitude,
h_l.to("m/s").magnitude,
a.to("1/m").magnitude,
eps_g.to("dimensionless").magnitude,
graph.nodes["d_b"]["value"].to("m").magnitude,
graph.nodes["u_g0"]["value"].to("m/s").magnitude,
graph.nodes["h_l"]["origin"],
graph.nodes["D_l"]["origin"],
]
)
y = torch.tensor(
[[tau.to("s").magnitude]],
dtype=torch.float64,
)
return y


simulator = SpargingProblem(
parameters_range={ # realistic (wide) parameters range for LIBRA
"temperature": (500, 600), # celsius
"P_top": (1, 2), # bar
"flow_g_mol": (100, 500), # sccm
"nozzle_diameter": (1e-3, 5e-3), # m
"D_l_corr": (0, 0), # index for selecting diffusivity correlation
},
output_names=["tau"],
)

n_samples = 5000

X = simulator.sample_inputs(n_samples)

PP_numbers = []
Y, _ = simulator.forward_batch(X, allow_failures=False)

# save training data
pd.DataFrame(Y, columns=simulator.output_names).to_csv(
FOLDER / "simulator_outputs.csv", index=False
)
pd.DataFrame(X, columns=simulator.param_names).to_csv(
FOLDER / "simulator_inputs.csv", index=False
)
pd.DataFrame(
PP_numbers,
columns=["Pi", "tau", "h_l", "a", "eps_g", "d_b", "u_g0", "h_l_corr", "D_l_corr"],
).to_csv(FOLDER / "PP_data.csv", index=False)

# sensitivity analysis problem
problem = {
"num_vars": simulator.in_dim,
"names": simulator.param_names,
"bounds": simulator.param_bounds,
"output_names": simulator.output_names,
}

with open(FOLDER / "problem.json", "w") as f:
json.dump(problem, f, indent=4)

if COMPUTE_SOBOL:
# Run AutoEmulate with default settings
ae = AutoEmulate(X, Y, log_level="WARNING", models=["GaussianProcessRBF"])
ae.summarise()

# pick best model
emulator = ae.best_result()
print(f"Selected model: {emulator.model_name} with id: {emulator.id}")

# The use_timestamp paramater ensures a new result is saved each time the save method is called
best_result_filepath = ae.save(emulator, FOLDER, use_timestamp=False)
print("Model and metadata saved to: ", best_result_filepath)

ae.plot_preds(
emulator,
output_names=simulator.output_names,
fname=FOLDER_PP / "predictions.png",
)

# === Sensitivity analysis ===
sa = SensitivityAnalysis(emulator.model, problem=problem)
sobol_df = sa.run("sobol")
sa.plot_sobol(sobol_df, index="ST", fname=FOLDER_PP / "sobolTot.png")
116 changes: 116 additions & 0 deletions examples/Sparging_UQ.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from datetime import datetime
from pathlib import Path

from sparging import (
LIBRA_PI_GEOM,
LIBRA_PI_MAT,
LIBRA_PI_OPERATING_PARAMS,
LIBRA_PI_SPARGING_PARAMS,
SimulationInput,
ureg,
all_correlations,
CorrelationType,
)

import logging
import pandas as pd
import json
import networkx as nx
import numpy as np

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.WARNING)

FOLDER = Path("datasets") / datetime.now().strftime("%Y%m%d_%H%M%S")
FOLDER.mkdir(exist_ok=True, parents=True)
FOLDER_SAMPLES = FOLDER / "samples"
FOLDER_SAMPLES.mkdir(exist_ok=True, parents=True)
FOLDER_PP = FOLDER / "postprocessing"
FOLDER_PP.mkdir(exist_ok=True)

outputs = []
h_l_corrs = all_correlations.get_list(CorrelationType.MASS_TRANSFER_COEFF)
D_l_corrs = all_correlations.get_list(CorrelationType.DIFFUSIVITY)


def forward(count, x: dict) -> list:
"""return list of values for post processing"""
# construct simulation input
LIBRA_PI_OPERATING_PARAMS.temperature = x["temperature"] * ureg.celsius
LIBRA_PI_OPERATING_PARAMS.P_top = x["P_top"] * ureg.bar
LIBRA_PI_OPERATING_PARAMS.flow_g_mol = x["flow_g_mol"] * ureg.sccm
LIBRA_PI_GEOM.nozzle_diameter = x["nozzle_diameter"] * ureg.m
# LIBRA_PI_SPARGING_PARAMS.h_l = x["h_l_corr"]
LIBRA_PI_MAT.D_l = D_l_corrs[x["D_l_corr_number"]]
# breakpoint()

graph = nx.Graph()
sim_input = SimulationInput.from_parameters(
LIBRA_PI_GEOM,
LIBRA_PI_MAT,
LIBRA_PI_OPERATING_PARAMS,
LIBRA_PI_SPARGING_PARAMS,
graph=graph,
)
tau = sim_input.get_tau()
h_l = sim_input.h_l
a = sim_input.a
eps_g = sim_input.eps_g
# sim_input.to_json(
# FOLDER_SAMPLES / f"sample_{count}.json"
# ) # for debugging and postprocessing

# for post processing
Pi = sim_input.get_Pi_number().to("dimensionless").magnitude
return [
Pi,
tau.to("s").magnitude,
h_l.to("m/s").magnitude,
a.to("1/m").magnitude,
eps_g.to("dimensionless").magnitude,
graph.nodes["d_b"]["value"].to("m").magnitude,
graph.nodes["u_g0"]["value"].to("m/s").magnitude,
graph.nodes["h_l"]["origin"],
graph.nodes["D_l"]["origin"],
]


n_samples = 1000
inputs = []
postprocess = []

for i in range(n_samples):
input = {
"temperature": np.random.normal(550, 25), # celsius
"P_top": 1.2, # bar
"flow_g_mol": 400, # sccm
"nozzle_diameter": 1.5e-3, # m
"D_l_corr_number": np.random.randint(
0, 2
), # index for selecting diffusivity correlation
}
inputs.append(input)
print(f"Running simulation {i + 1}/{n_samples}")
postprocess.append(forward(i, input))

# save training data
PP_frame = pd.DataFrame(
postprocess,
columns=["Pi", "tau", "h_l", "a", "eps_g", "d_b", "u_g0", "h_l_corr", "D_l_corr"],
)
PP_frame.to_csv(FOLDER / "PP_data.csv", index=False)

# for compatibility with postprocessing code
pd.DataFrame(PP_frame, columns=["tau"]).to_csv(
FOLDER / "simulator_outputs.csv", index=False
)
input_frame = pd.DataFrame(inputs)
input_frame.to_csv(FOLDER / "simulator_inputs.csv", index=False)

problem = {
"names": [x for x in inputs[0].keys()],
"bounds": ["550 +- 25", "1.2", "400", "1.5e-3", "oishi or calderoni"],
}

with open(FOLDER / "problem.json", "w") as f:
json.dump(problem, f, indent=4)
Loading
Loading