Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a4e88a6
added data_gen_script and adapted it to the project's architecture
XanderBys Jan 17, 2026
ced344f
Updated basics tutorial with data importation
XanderBys Jan 17, 2026
7929083
Included argument for filename in data_gen_script
XanderBys Jan 18, 2026
40e02da
Wrote data download script
XanderBys Jan 21, 2026
e371b21
Included venv in gitignore
XanderBys Jan 21, 2026
c74402a
Fixed syntax error that stopped doc compilation in Python 3.10
XanderBys Jan 21, 2026
e4b06d2
Specified jupyter-book<2.0 to properly manage the dependency
XanderBys Jan 22, 2026
a9c1fdb
Fixed mistakes in the tutorial and made the download data button pret…
XanderBys Jan 22, 2026
b51cbb4
Created .python-version
XanderBys Jan 22, 2026
7db79f5
Updated link in download data button to connect to the data file
XanderBys Jan 24, 2026
41e8168
Updated tutorial to reflect not using a FOM
XanderBys Jan 24, 2026
9f6edd2
Added support for models with external inputs
XanderBys Jan 24, 2026
47d5df9
Added macro definitions to the tutorials to help rendering in VSCode
XanderBys Jan 24, 2026
8b523fb
Fixed two minor typos in contributor guidelines:
XanderBys Jan 25, 2026
5053363
Changed tutorial data file naming convention to be "[tutorial name]_d…
XanderBys Jan 26, 2026
08069d6
Removed docs/data directory and restructured data download bash scrip…
XanderBys Jan 29, 2026
d49cec4
Moved data_gen_script.py
XanderBys Jan 29, 2026
6a8a41e
Set default to generate all data instead of only 'basics' data
XanderBys Jan 29, 2026
546e114
Changed data_gen_script.py to generate data for various initial condi…
XanderBys Jan 30, 2026
5f50575
Changed wording in notebook to reflect new data treatment
XanderBys Jan 30, 2026
33cce5f
Added "U" dataset to inputs data
XanderBys Jan 30, 2026
e8fa532
Updated inputs tutorial to load external inputs data in the first part
XanderBys Jan 30, 2026
c3f6359
Fixed minor bugs in data for inputs tutorial
XanderBys Jan 30, 2026
ef91128
Wrote help message
XanderBys Jan 30, 2026
61176d1
MInor wording changes in tutorials
XanderBys Jan 31, 2026
4e8e559
Fixed formatting in "Problem Statement"
XanderBys Feb 2, 2026
541d029
Updated data download bash script with better directory organization
XanderBys Feb 2, 2026
0a3bd3b
Minor wording changes
XanderBys Feb 4, 2026
352aab2
Changed data format to include titles as metadata
XanderBys Feb 4, 2026
e2eeaaa
Removed mu from discretization details (unnecessary for input data)
XanderBys Feb 4, 2026
2b2063b
Removed unnecessary str casting in f-string
XanderBys Feb 4, 2026
f1cf263
Deleted .python-version
XanderBys Feb 4, 2026
519aa86
Changed data download link to direct to main OpInf repo instead of Xa…
XanderBys Feb 5, 2026
e909710
Updated to clone from main OpInf repo instead of Xander's fork
XanderBys Feb 5, 2026
44a1a63
Minor changes to indexing and titles
XanderBys Feb 5, 2026
0a90c19
Added documentation, changed docs to follow NumpyDoc, used'with' inst…
XanderBys Feb 11, 2026
de27e88
Renamed script to `data_generation.py`
XanderBys Feb 11, 2026
7fe8bfe
Added documentation, used variables to save directories
XanderBys Feb 11, 2026
98d1859
Minor updates to contributor guidelines
XanderBys Feb 12, 2026
3a64706
Removed comparison to Galerkin intrusive ROM
XanderBys Feb 12, 2026
fbbf982
Added test data generation for inputs tutorial
XanderBys Feb 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ html/
Notes/
tests/add_endclause.py
docs/use_mathjax_shortcuts.py
*.code-workspace
.venv/
4 changes: 2 additions & 2 deletions docs/bib2md.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,12 @@ def linkedname(author):
if names[-1] == "Jr":
lastname = clean_name(names[-2])
initials = " ".join([name[0] + "." for name in names[:-2]])
key = f"{firstname}_{lastname.replace(" ", "").lower()}"
key = f"{firstname}_{lastname.replace(' ', '').lower()}"
lastname = f"{lastname} Jr."
else:
lastname = clean_name(names[-1])
initials = " ".join([name[0] + "." for name in names[:-1]])
key = f"{firstname}_{lastname.replace(" ", "").lower()}"
key = f"{firstname}_{lastname.replace(' ', '').lower()}"

# Get the Google Scholar link if possible.
if key in scholarIDS:
Expand Down
39 changes: 39 additions & 0 deletions docs/data_download.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash

# Many of the tutorials and examples in this function rely on pre-generated data.
# This script is meant to be run automatically as a GitHub Action to download
# necessary data, but it can also be run manually if you need to regenerate the data.

# NOTE: this script should be run from the root of the opinf directory
Comment thread
XanderBys marked this conversation as resolved.
# Example usage: bash docs/data_download.sh

# remove the temp_data directory if it already exists
if [ -d "temp_data" ]; then
echo "Removing existing temp_data/ directory..."
rm -rf temp_data/
fi

# Clone into the repository and download only the data branch
DATA_URL="https://github.com/operator-inference/opinf.git"
echo "Cloning into the data branch of ${DATA_URL}..."
git clone -b data --single-branch --depth=1 ${DATA_URL} temp_data/

# move docs files into docs/source/api/ directory
DOC_DIR="docs/source/"
API_DIR="${DOC_DIR}api/"
echo "Moving basis_example.npy, pre_example.npy, and lstsq_example.npz into ${API_DIR} directory..."
mv temp_data/basis_example.npy ${API_DIR}
mv temp_data/lstsq_example.npz ${API_DIR}
mv temp_data/pre_example.npy ${API_DIR}

# move tutorial data files into docs/source/tutorials/ directory
TUTORIALS_DIR="${DOC_DIR}tutorials/"
echo "Moving basics_data.h5, inputs_data.h5, and parametric_data.h5 into ${TUTORIALS_DIR} directory..."
mv temp_data/basics_data.h5 ${TUTORIALS_DIR}
mv temp_data/inputs_data.h5 ${TUTORIALS_DIR}
mv temp_data/parametric_data.h5 ${TUTORIALS_DIR}

echo "Removing empty temp_data/ directory..."
rm -rf temp_data/

echo "Done!"
298 changes: 298 additions & 0 deletions docs/data_generation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
from collections.abc import Callable
import argparse
import pathlib
import h5py

import numpy as np
import scipy
import opinf


def generate_training_data(
n_samples: int,
n_timesteps: int,
q_0: Callable[[np.ndarray], np.ndarray],
u: Callable[[int], np.ndarray] | None = None,
):
"""Generate training data using a continuous dynamical system model.

Solves a parametrized advection-diffusion equation subject to external
inputs and saves the time-evolved state snapshots. Uses the Operator
Inference library to construct and evaluate a continuous model.

Parameters
----------
n_samples : int
Number of spatial grid points (excluding boundary points).
n_timesteps : int
Number of time steps at which to evaluate the solution.
q_0 : Callable[[np.ndarray], np.ndarray]
Initial condition function. Takes an array of spatial locations
and returns initial values at those locations.
u : callable or None, optional
External input function. Takes a time value (float) and returns
the input values at that time. If None, the system has no external
inputs and defaults to a zero function. Default is None.

Returns
-------
t : np.ndarray
Time points, shape (n_timesteps,).
Q : np.ndarray
Snapshots of the state solution, shape (n_samples, n_timesteps).

Notes
-----
The model is based on an advection-diffusion equation with a discrete
Laplacian operator on the domain [0, 1] with homogeneous Dirichlet
boundary conditions. The solution is computed using the BDF method.
"""
external_inputs = True
if u is None:
external_inputs = False

def u(t):
return 0

# construct the spatial and temporal domains
x = np.linspace(0, 1, n_samples + 2)[1:-1]
dx = x[1] - x[0]
t = np.linspace(0, 1, n_timesteps)

# construct the matrix of linear operators
diags = np.array([1, -2, 1]) / dx**2
A = scipy.sparse.diags(diags, [-1, 0, 1], (n_samples, n_samples))

# construct the matrix of external input operators
B = np.zeros_like(x)
B[0], B[-1] = 1 / dx**2, 1 / dx**2

fom = opinf.models.ContinuousModel(
operators=[
opinf.operators.LinearOperator(A),
opinf.operators.InputOperator(B),
]
)

initial_values = q_0(x)
initial_values = (
initial_values if not external_inputs else initial_values * u(0)
)

return t, fom.predict(initial_values, t, input_func=u, method="BDF")


def generate_basics_data(filepath: str = "basics_data.h5"):
"""Generate training data with various initial conditions
for the basics tutorial.

Creates datasets with solutions to the advection-diffusion equation using
six different initial condition functions. Results are saved to an HDF5
file with separate datasets for each initial condition.

Parameters
----------
filepath : str, optional
Path where the generated HDF5 file will be saved.
Default is "basics_data.h5".

Returns
-------
None

Notes
-----
The generated file contains:
- "t": time points for all simulations
- "default": snapshots for the default initial condition q_0(x) = x(1-x)
- "Experiment 1" through "Experiment 6": snapshots for alternative
initial conditions, each with a "title" attribute for reference

The spatial domain is [0, 1] with 512 interior grid points, and the
temporal domain extends from t=0 to t=1 with 401 time steps.
"""
# set up basic parameters
n_samples = 512
n_timesteps = 401

# define the various initial conditions used in the tutorial
def q_0_default(x):
return x * (1 - x)

initial_conditions = [
(r"$q_{0}(x) = 10 x (1 - x)$", lambda x: 10 * x * (1 - x)),
(
r"$q_{0}(x) = 5 x^{2} (1 - x)^{2}$",
lambda x: 5 * x**2 * (1 - x) ** 2,
),
(
r"$q_{0}(x) = 50 x^{4} (1 - x)^{4}$",
lambda x: 50 * x**4 * (1 - x) ** 4,
),
(
r"$q_{0}(x) = \frac{1}{2}\sqrt{x (1 - x)}$",
lambda x: 0.5 * np.sqrt(x * (1 - x)),
),
(
r"$q_{0}(x) = \frac{1}{4}\sqrt[4]{x (1 - x)}$",
lambda x: 0.25 * np.sqrt(np.sqrt(x * (1 - x))),
),
(
r"$q_{0}(x) = \frac{1}{3}\sin(\pi x) + \frac{1}{5}\sin(5\pi x)$",
lambda x: np.sin(np.pi * x) / 3 + np.sin(5 * np.pi * x) / 5,
),
]

# initialize the file we will write the data to
with h5py.File(filepath, "w") as f:
# generate and save data for the default initial condition
# also save the data for the time dimension
t, Q_default = generate_training_data(
n_samples, n_timesteps, q_0_default
)
f.create_dataset("t", data=t)
f.create_dataset("default", data=Q_default)

f.attrs["num_experiments"] = len(initial_conditions)
for idx, (title, func) in enumerate(initial_conditions):
# for each initial condition,
# generate the data for that condition
# and save it as a new dataset
_, Q = generate_training_data(n_samples, n_timesteps, func)
dset = f.create_dataset(f"Experiment {idx+1}", data=Q)
dset.attrs["title"] = title

print(f"Training data saved to {filepath}")


def generate_external_inputs_data(filepath: str = "inputs_data.h5"):
"""Generate training data with external input functions for
the inputs tutorial.

Creates datasets with solutions to the advection-diffusion equation subject
to three different external input functions. Each input function has both
training and test variants to facilitate learning external input operators.
Results are saved to an HDF5 file organized in training and test groups.

Parameters
----------
filepath : str, optional
Path where the generated HDF5 file will be saved.
Default is "inputs_data.h5".

Returns
-------
None

Notes
-----
The generated file contains:
- "t": time points for all simulations
- "Q": snapshots for the default external input function
- "U": the default external input values
- "train" group: contains Q_0, U_0, Q_1, U_1, Q_2, U_2 datasets
- "test" group: contains Q_0, U_0, Q_1, U_1, Q_2, U_2 datasets

The spatial domain is [0, 1] with 1023 interior grid points, and the
temporal domain extends from t=0 to t=1 with 1001 time steps. The initial
condition depends on an exponential-based function with
parameter alpha=100.
"""
n_samples = 1023
n_timesteps = 1001

alpha = 100

# the part of the initial condition independent of u(t)
def q_0(x):
return np.exp(alpha * (x - 1)) + np.exp(-alpha * x) - np.exp(-alpha)

# the define the external inputs functions
def u(t):
return np.ones_like(t) + np.sin(4 * np.pi * t) / 4

def u_test(t):
return 1 + t * (1 - t)

train_inputs = [
lambda t: np.exp(-t),
lambda t: 1 + t**2 / 2,
lambda t: 1 - np.sin(np.pi * t) / 2,
]
test_inputs = [
lambda t: 1 - np.sin(3 * np.pi * t) / 3,
lambda t: 1 + 25 * (t * (t - 1)) ** 3,
lambda t: 1 + np.exp(-2 * t) * np.sin(np.pi * t),
]

# initialize the h5 file to write to
with h5py.File(filepath, "w") as f:
# generate training data for the beginning of the tutorial
t, Q = generate_training_data(n_samples, n_timesteps, q_0, u)
U = u(t)

_, Q_test = generate_training_data(n_samples, n_timesteps, q_0, u_test)
U_test = u_test(t)

f.create_dataset("t", data=t)
f.create_dataset("Q", data=Q)
f.create_dataset("U", data=U)
f.create_dataset("Q_test", data=Q_test)
f.create_dataset("U_test", data=U_test)

train_grp = f.create_group("train")
test_grp = f.create_group("test")

train_grp.attrs["num_input_functions"] = len(train_inputs)
test_grp.attrs["num_input_functions"] = len(test_inputs)

# for each input function, generate data for the inputs and snapshots
# then, save that data to a new dataset in the file
for idx, [train_input, test_input] in enumerate(
zip(train_inputs, test_inputs)
):
_, Q_train = generate_training_data(
n_samples, n_timesteps, q_0, train_input
)
U_train = train_input(t)
_, Q_test = generate_training_data(
n_samples, n_timesteps, q_0, test_input
)
U_test = test_input(t)

train_grp.create_dataset(f"Q_{idx}", data=Q_train)
train_grp.create_dataset(f"U_{idx}", data=U_train)
test_grp.create_dataset(f"Q_{idx}", data=Q_test)
test_grp.create_dataset(f"U_{idx}", data=U_test)

print(f"Training data saved to {filepath}")


if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate training data for Operator Inference tutorials."
)
parser.add_argument(
"dataset",
nargs="?",
default="all",
choices=["basics", "inputs", "parametric", "all"],
help="Dataset to generate (default: all)",
)

args = parser.parse_args()

BASE_DIR = pathlib.Path(__file__).resolve().parent

if args.dataset == "basics" or args.dataset == "all":
generate_basics_data(
str(BASE_DIR / "source" / "tutorials" / "basics_data.h5")
)
if args.dataset == "inputs" or args.dataset == "all":
generate_external_inputs_data(
str(BASE_DIR / "source" / "tutorials" / "inputs_data.h5")
)
if args.dataset == "parametric" or args.dataset == "all":
raise NotImplementedError(
"The parametric dataset has not been implemented yet."
)
3 changes: 2 additions & 1 deletion docs/source/contributing/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,13 @@ Jupyter Book is essentially [an opinionated wrapper](https://jupyterbook.org/en/
This project uses [Jupyter Book with Sphinx Autodoc](https://jupyterbook.org/en/stable/advanced/developers.html) to automatically generate documentation straight from code docstrings.
Because of our settings for the automatic documentation generation, please follow these guidelines.

- Follow [NumPyDoc style guide](https://numpydoc.readthedocs.io/en/latest/format.html#short-summary) when writing docstrings
- Class docstrings should _not_ have a "Methods" section.
- [Properties](https://docs.python.org/3/library/functions.html#property) show up automatically in the documentation, but attributes created at runtime do not.
- Use `:math:` environments to write actual math.

Note that docstrings must follow Sphinx syntax, not Jupyter notebook syntax.
For example, use `:math:\`i^2 = -1\`` instead of `$i^2 = -1$`.
For example, use `` :math:`i^2 = -1` `` instead of `$i^2 = -1$`.

## Example: Contributing a New Tutorial

Expand Down
8 changes: 7 additions & 1 deletion docs/source/contributing/how_to_contribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ brew install python@3.12

:::

Some of the tutorials and examples in this documentation rely on pre-generated data. This data is stored in the orphan `data` branch of the GitHub respository. For Mac/Unix users, there is a script titled `data_download.sh` that can be run from the root directory that will automatically download all of the data files and put them in their proper places. The script can be run as follows:

```shell
bash docs/data_download.sh
```

Finally, to ensure that new additions follow code standards and conventions, install the [git pre-commit hook](https://pre-commit.com/) with the following command.

```shell
Expand Down Expand Up @@ -119,7 +125,7 @@ The GitHub repository is organized as follows.

## Acceptance Standards

Changes are not usually be accepted until the following tests pass.
Changes are not usually accepted until the following tests pass.

1. `tox`: write or update tests to validate your additions or changes, preferably with full line coverage.
2. `tox -e style`: write readable code that conforms to our style guide.
Expand Down
Loading