Quick Start#
This page shows a complete, self-contained Bayesian sensor-placement OED workflow using PyOED’s toy linear model. After working through this example you will understand the four building blocks of every PyOED application: forward model, error models, inverse problem, and OED problem.
Note
Make sure PyOED is installed before running this example. See Download & Install PyOED for instructions. No FEniCSx installation is needed for this example.
Overview#
We want to identify the k best sensors out of n candidate locations for estimating an unknown model parameter. The optimality criterion is Bayesian A-optimality: minimise the trace of the posterior covariance (i.e., minimise total posterior uncertainty).
The five steps are:
Build a toy linear forward model.
Define prior and observation-noise distributions.
Define an observation operator that maps model state to sensors.
Wrap everything in a 3D-Var inverse problem.
Run the sensor-placement OED solver.
Step 1 — Forward Model#
ToyLinearTimeIndependent
provides a randomly generated linear map
\(y = A\theta + \epsilon\) that is convenient for testing.
import numpy as np
from pyoed.models.simulation_models.toy_linear import ToyLinearTimeIndependent
# 8-dimensional state space, 8-dimensional parameter space
model = ToyLinearTimeIndependent(configs=dict(nx=8, np=8, random_seed=42))
print("State size :", model.state_size) # 8
print("Parameter size :", model.parameter_size) # 8
Step 2 — Error Models (Prior and Observation Noise)#
All probability distributions in PyOED derive from
ErrorModel.
For Bayesian inversion the prior is \(\theta \sim \mathcal{N}(0, I)\).
For sensor-placement OED the observation noise must be a
PrePostWeightedGaussianErrorModel
so that the design vector is woven into the covariance during optimisation:
from pyoed.models.error_models.Gaussian import (
GaussianErrorModel,
PrePostWeightedGaussianErrorModel,
)
# Prior: N(0, I_8)
prior = GaussianErrorModel(configs=dict(size=8, mean=0.0, variance=1.0))
# Observation noise: design-weighted Gaussian (required for relaxed OED)
obs_noise = PrePostWeightedGaussianErrorModel(
configs=dict(size=8, mean=0.0, variance=0.01)
)
Step 3 — Observation Operator#
An Identity operator
observes all nx grid points. When the OED solver activates a subset of
sensors it toggles entries of the operator’s design vector.
from pyoed.models.observation_operators.identity import Identity
obs_op = Identity(configs=dict(model=model))
print("Observation size :", obs_op.observation_size) # 8
Step 4 — Inverse Problem (3D-Var)#
VanillaThreeDVar combines
the model, prior, observation operator, and noise model into a Bayesian
inverse problem that the OED criterion will query during optimisation.
from pyoed.assimilation.filtering.threeDVar import VanillaThreeDVar
da = VanillaThreeDVar(configs=dict(
model=model,
invert_for='parameter', # 'parameter' or 'state'
# 'parameter' — infer model parameters θ
# 'state' — infer model state x
prior=prior,
observation_operator=obs_op,
observation_error_model=obs_noise,
))
# Generate synthetic observations from a known "true" parameter
rng = np.random.default_rng(42)
true_param = rng.standard_normal(model.parameter_size)
true_state = model.solve_forward(true_param)
y_obs = obs_op.apply(true_state) + obs_noise.generate_noise()
da.register_observations(y_obs)
Step 5 — Sensor-Placement OED#
SensorPlacementBayesianInversionOED
optimises the continuous relaxation of the binary design vector
\(\zeta \in [0,1]^{n_y}\). Each entry of \(\zeta\) represents the
“weight” assigned to the corresponding sensor; the optimal design concentrates
weight on the most informative locations.
from pyoed.oed.sensor_placement import SensorPlacementBayesianInversionOED
oed = SensorPlacementBayesianInversionOED(configs=dict(
inverse_problem=da,
problem_is_linear=True, # exploit linearity for faster solves
optimizer='ScipyOptimizer',
))
# Register the optimality criterion
oed.register_optimality_criterion('Bayesian A-opt')
# Run the optimisation
results = oed.solve()
print("Optimal design :", np.round(oed.design, 3))
print("Criterion value :", results.fun)
Complete Example#
Copy-paste the snippet below to run the full workflow end-to-end:
import numpy as np
from pyoed.models.simulation_models.toy_linear import ToyLinearTimeIndependent
from pyoed.models.error_models.Gaussian import (
GaussianErrorModel,
PrePostWeightedGaussianErrorModel,
)
from pyoed.models.observation_operators.identity import Identity
from pyoed.assimilation.filtering.threeDVar import VanillaThreeDVar
from pyoed.oed.sensor_placement import SensorPlacementBayesianInversionOED
# ── 1. Forward model ────────────────────────────────────────────────────
model = ToyLinearTimeIndependent(configs=dict(nx=8, np=8, random_seed=42))
# ── 2. Error models ──────────────────────────────────────────────────────
prior = GaussianErrorModel(configs=dict(size=8, mean=0.0, variance=1.0))
obs_noise = PrePostWeightedGaussianErrorModel(
configs=dict(size=8, mean=0.0, variance=0.01)
)
# ── 3. Observation operator ──────────────────────────────────────────────
obs_op = Identity(configs=dict(model=model))
# ── 4. Inverse problem ───────────────────────────────────────────────────
da = VanillaThreeDVar(configs=dict(
model=model,
invert_for='parameter',
prior=prior,
observation_operator=obs_op,
observation_error_model=obs_noise,
))
rng = np.random.default_rng(42)
true_param = rng.standard_normal(model.parameter_size)
true_state = model.solve_forward(true_param)
y_obs = obs_op.apply(true_state) + obs_noise.generate_noise()
da.register_observations(y_obs)
# ── 5. Sensor-placement OED ──────────────────────────────────────────────
oed = SensorPlacementBayesianInversionOED(configs=dict(
inverse_problem=da,
problem_is_linear=True,
optimizer='ScipyOptimizer',
))
oed.register_optimality_criterion('Bayesian A-opt')
results = oed.solve()
print("Optimal design :", np.round(oed.design, 3))
print("Criterion value :", results.fun)
What Next?#
Deeper walk-through of every PyOED component with annotated examples.
Mathematical background: Bayesian inversion, A/D/E-optimality, EIG, and design-space projection.
Jupyter notebooks for time-dependent inversion, robust OED, reinforcement-learning-based design, and more.
Full class and method documentation for all subpackages.