pygeoinf package

Subpackages

Submodules

pygeoinf.affine_operators module

Provides the AffineOperator class for affine mappings between Hilbert spaces.

class pygeoinf.affine_operators.AffineOperator(linear_part: LinearOperator, translation: Any)[source]

Bases: AffineOperatorAxiomChecks, NonLinearOperator

Represents an affine transformation between two Hilbert spaces.

An affine operator is a mapping F(x) = A(x) + b, where ‘A’ is a bounded linear operator and ‘b’ is a fixed translation vector in the codomain.

property linear_part: LinearOperator

The underlying linear mapping ‘A’.

property translation_part: Any

The translation vector ‘b’.

pygeoinf.auxiliary module

pygeoinf.auxiliary.empirical_data_error_measure(model_measure, forward_operator, n_samples=10, scale_factor=1.0)[source]

Generate an empirical data error measure based on samples from a measure on the model space. Useful for when you need to define a reasonable data error measure for synthetic testing, and need the covariance matrix to be easily accessible.

Parameters:
  • model_measure – The measure on the model space used as a basis for the error measure (e.g., the model prior measure)

  • forward_operator – Linear operator mapping from model space to data space (e.g., operator B)

  • n_samples – Number of samples to generate for computing statistics (default: 10)

  • scale_factor – Scaling factor for the standard deviations (default: 1.0)

Returns:

Data error measure with empirically determined covariance

Return type:

inf.GaussianMeasure

pygeoinf.backus_gilbert module

Backus-Gilbert style dual master cost function.

Provides DualMasterCostFunction — the oracle $varphi(lambda; q)$ minimised over $lambda$ in convex Backus-Gilbert / dual-level-set inversion.

class pygeoinf.backus_gilbert.BackusInference(forward_problem: LinearForwardProblem, property_operator: LinearOperator, prior_norm_bound: float, significance_level: float, /, *, constraint_solver=None, constraint_preconditioner=None)[source]

Bases: LinearInference

Solves a linear inference problem using Backus’ method.

property critical_chi_squared: float

Returns the critical Chi squared.

property prior_norm_bound: float

Returns the prior norm bound.

property significance_level: float

Returns the significance level.

test_data_compatibility(data: Vector, solver: LinearSolver, /, *, preconditioner: LinearOperator | LinearSolver | None = None, minimum_damping: float = 0.0, maxiter: int = 100, rtol: float = 1e-06, atol: float = 0.0) bool[source]

Returns true if there exists a model that is compatible with both the data and the norm bound.

class pygeoinf.backus_gilbert.DualMasterCostFunction(data_space: HilbertSpace, property_space: HilbertSpace, model_space: HilbertSpace, G: LinearOperator, T: LinearOperator, model_prior_support: SupportFunction, data_error_support: SupportFunction, observed_data: Vector, q_direction: Vector)[source]

Bases: NonLinearForm

Cost function for the master dual equation (Hilbert form):

h_U(q) = inf_{λ ∈ D}

{ (λ, d̃)_D + σ_B(T* q - G* λ) + σ_V(-λ) }

i.e.

φ(λ; q) = (λ, d̃)_D + σ_B(T* q - G* λ) + σ_V(-λ)

where:
  • σ_B is the support function of the model prior convex set B ⊆ M

  • σ_V is the support function of the data error convex set V ⊆ D

Minimizing φ(λ; q) over λ ∈ D yields h_U(q).

property direction: Vector

Current property direction q ∈ P.

property observed_data: Vector

Observed data vector d̃ ∈ D.

set_direction(q: Vector) None[source]

Update the property direction q and recompute T* q.

value_and_subgradient(lam: Vector) tuple[float, Vector][source]

Compute the value and a subgradient of $varphi(lambda; q)$ in one pass.

Shares the computation of $G^* lambda$ and the support points $v$, $w$ between the value and subgradient evaluations, avoiding redundant work.

The dual master cost function is

\[\varphi(\lambda; q) = \langle \lambda, \tilde{d} \rangle_D + \sigma_B(T^* q - G^* \lambda) + \sigma_V(-\lambda)\]

and a subgradient at $lambda$ is

\[g = \tilde{d} - G v - w,\]

where $v in partial sigma_B(T^* q - G^* lambda)$ and $w in partial sigma_V(-lambda)$.

Parameters:

lam – Dual variable $lambda in D$.

Returns:

A tuple (f, g) where f = φ(λ; q) is the scalar value and g ∂φ(λ; q) is a subgradient vector in $D$.

pygeoinf.config module

pygeoinf/config.py Shared configuration constants for the pygeoinf library.

pygeoinf.convex_analysis module

class pygeoinf.convex_analysis.BallSupportFunction(primal_domain: HilbertSpace, center: Vector, radius: float)[source]

Bases: SupportFunction

Support function of a closed ball B(c, r) = {x : ||x - c|| ≤ r}:

h(q) = ⟨q, c⟩ + r ||q||

support_point(q: Vector) 'Vector' | None[source]

Return x* = c + r * (q / ||q||) achieving the supremum.

value_and_support_point(q: Vector) tuple[float, Vector | None][source]

Return (h(q), x*(q)) computing $|q|$ once.

Both $h(q) = langle q, c rangle + r|q|$ and $x^*(q) = c + r , q / |q|$ depend on $|q|$, so computing it once halves the number of norm evaluations compared to the default two-call fallback.

Parameters:

q – A vector in the primal domain $H$.

Returns:

(value, point) where value $= h(q)$ and point $= x^*(q)$, or the center $c$ when $q approx 0$.

class pygeoinf.convex_analysis.CallableSupportFunction(primal_domain: HilbertSpace, fn: Callable[[Vector], float], support_point_fn: Callable[[Vector], Vector] | None = None)[source]

Bases: SupportFunction

Support function defined by a user-provided callable.

Wraps an arbitrary callable $q mapsto h(q)$ as a SupportFunction. Optionally accepts a second callable $q mapsto x^*(q)$ that returns the support point (subgradient of $h$ at $q$).

Parameters:
  • primal_domain – The Hilbert space $H$ on which $h$ is defined.

  • fn – A callable fn(q) -> float computing the support value $h(q)$.

  • support_point_fn – An optional callable support_point_fn(q) -> Vector returning $x^*(q) in argmax_{x in C} langle q, x rangle$. When provided, support_point() delegates to it and subgradient() returns the result. When None, support_point() returns None and subgradient() raises NotImplementedError.

Example:

fn = lambda q: float(np.linalg.norm(q))          # L2-ball support
sp = lambda q: q / np.linalg.norm(q)             # support point
h = CallableSupportFunction(space, fn, support_point_fn=sp)
support_point(q: Vector) Vector | None[source]

Return $x^*(q)$ via the user-supplied callback, or None.

class pygeoinf.convex_analysis.EllipsoidSupportFunction(primal_domain: HilbertSpace, center: Vector, radius: float, shape_operator: LinearOperator, inverse_operator: LinearOperator | None = None, inverse_sqrt_operator: LinearOperator | None = None)[source]

Bases: SupportFunction

Support function of an ellipsoid E(c, r, A) defined by:

E = {x : ⟨A(x-c), (x-c)⟩ ≤ r²} with A SPD

Then:

h(q) = ⟨q, c⟩ + r ||A^{-1/2} q||

Parameters:
  • primal_domain – The Hilbert space H.

  • center – The center c of the ellipsoid.

  • radius – The radius r.

  • shape_operator – The SPD operator A.

  • inverse_operator – A^{-1}. Required for support_point and sufficient for computing h(q) through $sqrt{langle q, A^{-1}qrangle}$.

  • inverse_sqrt_operator – A^{-1/2}. Optional direct square-root inverse used for computing h(q).

Note

If neither inverse operator is provided, the support function cannot be evaluated. If inverse_operator is omitted, support_point returns None.

support_point(q: Vector) 'Vector' | None[source]

Return x* = c + r * (A^{-1} q) / ||A^{-1/2} q|| achieving the supremum.

For an ellipsoid E(c, r, A), the extreme point in direction q is found by transforming q through the inverse metric A^{-1} and normalizing.

Returns None if inverse_operator was not provided.

value_and_support_point(q: Vector) tuple[float, Vector | None][source]

Return (h(q), x*(q)) computing $A^{-1} q$ once.

When $A^{-1}$ is available, both the value

\[h(q) = \langle q, c \rangle + r\,\sqrt{\langle q,\, A^{-1} q \rangle}\]

and the support point

\[x^*(q) = c + \frac{r}{\sqrt{\langle q, A^{-1} q \rangle}} A^{-1} q\]

share the intermediate $A^{-1} q$ and the norm $|A^{-1/2} q| = sqrt{langle q, A^{-1} q rangle}$, so only one operator application is needed instead of two.

When $A^{-1}$ is not available, falls back to calling self(q) (which uses $A^{-1/2}$) and returns None for the support point, matching the behaviour of support_point().

Parameters:

q – A vector in the primal domain $H$.

Returns:

(value, point) where point is None when $A^{-1}$ was not supplied at construction.

class pygeoinf.convex_analysis.HalfSpaceSupportFunction(primal_domain: HilbertSpace, normal_vector: Vector, offset: float, inequality_type: str = '<=', *, parallel_rtol: float = 1e-12, parallel_atol: float = 1e-14, return_min_norm_support_point: bool = True)[source]

Bases: NonLinearForm

Support function of a (closed) half-space in a Hilbert space H.

We support two conventions:

(<=) H = { x ∈ H : ⟨a, x⟩ ≤ b } (>=) H = { x ∈ H : ⟨a, x⟩ ≥ b } which is equivalent to { x : ⟨-a, x⟩ ≤ -b }.

Mathematical facts (extended-real-valued):

For H = {x : ⟨a, x⟩ ≤ b} with a ≠ 0,

σ_H(q) = sup_{x∈H} ⟨q, x⟩
= { α b, if q = α a with α ≥ 0,

+∞, otherwise. }

For H = {x : ⟨a, x⟩ ≥ b},

σ_H(q) = { α b, if q = α a with α ≤ 0,

+∞, otherwise. }

Notes

  • Half-spaces are unbounded, so σ_H is typically +∞.

  • This implementation returns float(‘inf’) for unbounded directions.

  • A support point is not unique when σ_H(q) is finite; the maximizers form the boundary hyperplane ⟨a, x⟩ = b. We optionally return the minimum-norm boundary point as a canonical representative.

property inequality_type: str
property normal_vector: Vector
property offset: float
support_point(q: Vector) 'Vector' | None[source]

Return a canonical maximizer when σ_H(q) is finite.

When finite, the maximizers are all x with ⟨a,x⟩ = b (boundary hyperplane). If return_min_norm_support_point=True, we return the minimum-norm boundary point:

x_min = (b / ||a||^2) a.

Otherwise return None (non-unique support set).

class pygeoinf.convex_analysis.LinearImageSupportFunction(base: SupportFunction, operator: LinearOperator)[source]

Bases: SupportFunction

Support function of the linear image $A(C)$ of a convex set $C$.

For a convex set $C subseteq H$ with support function $h_C$ and a bounded linear operator $A: H to K$, the support function of the image $A(C) subseteq K$ is

\[h_{A(C)}(q) = h_C(A^* q), \quad q \in K,\]

where $A^*: K to H$ is the Hilbert-space adjoint of $A$.

Parameters:
  • base – The support function $h_C$ of the base set $C subseteq H$. Its primal_domain must equal operator.domain.

  • operator – A bounded linear operator $A: H to K$. operator.domain must equal base.primal_domain.

Raises:

ValueError – If operator.domain does not equal base.primal_domain.

Note

The primal_domain of the returned object is operator.codomain (the space $K$ where the image $A(C)$ lives).

Note

Phase 3: support_point() propagates support points from the base, returning $x_C^*(A^* q)$ when available, or None if the base has no support point available.

support_point(q: Vector) 'Vector' | None[source]

Return the support point of the image set $A(C)$ at direction $q in K$.

For the image support function $h_{A(C)}(q) = h_C(A^* q)$, the support point in direction $q$ is obtained by computing the base support point $x_C^*(A^* q)$ and then applying the operator: $x_{A(C)}^*(q) = A(x_C^*(A^* q))$.

Parameters:

q – A vector in the codomain $K$.

Returns:

The support point $A(x_C^*(A^* q)) in K$ if available, or None.

class pygeoinf.convex_analysis.MinkowskiSumSupportFunction(left: SupportFunction, right: SupportFunction)[source]

Bases: SupportFunction

Support function of the Minkowski sum $C oplus D$.

For two convex sets $C, D subseteq H$ with support functions $h_C$ and $h_D$ on the same Hilbert space $H$, the support function of their Minkowski sum $C oplus D = {c + d : c in C,, d in D}$ is

\[h_{C \oplus D}(q) = h_C(q) + h_D(q), \quad q \in H.\]
Parameters:
  • left – Support function $h_C$.

  • right – Support function $h_D$. right.primal_domain must equal left.primal_domain.

Raises:

ValueError – If left.primal_domain and right.primal_domain differ.

Note

Phase 3: support_point() conservatively returns the sum of support points only when both operands have support points available. If either operand has no support point, returns None (unavailable).

support_point(q: Vector) 'Vector' | None[source]

Return the support point of the Minkowski sum $C oplus D$ at direction $q$.

If both the left and right operands have support points $x_L^*(q)$ and $x_R^*(q)$ available, return their sum $x_L^*(q) + x_R^*(q)$ (a support point of the sum set). Otherwise return None (conservative: neither is available or computing the sum is unsafe).

Parameters:

q – A vector in the shared primal space $H$.

Returns:

The sum of support points $x_L^*(q) + x_R^*(q)$ if both are available, or None.

class pygeoinf.convex_analysis.PointSupportFunction(primal_domain: HilbertSpace, point: Vector)[source]

Bases: SupportFunction

Support function of the singleton set ${p}$.

For a fixed point $p in H$, the support function of the singleton set ${p}$ is

\[h_{\{p\}}(q) = \langle q, p \rangle, \quad q \in H.\]

The support point is always $p$ (the unique element of the set), so subgradient() is available for all query directions.

Parameters:
  • primal_domain – The Hilbert space $H$ containing the point $p$.

  • point – The fixed point $p$.

Example:

p = np.array([1.0, 2.0])
h = PointSupportFunction(space, p)
h(np.array([3.0, -1.0]))   # returns 3*1 + (-1)*2 = 1.0
support_point(q: Vector) Vector[source]

Return $p$ — the unique maximiser for any query direction.

class pygeoinf.convex_analysis.ScaledSupportFunction(base: SupportFunction, alpha: float)[source]

Bases: SupportFunction

Support function of a nonnegatively scaled convex set $alpha C$.

For a convex set $C subseteq H$ with support function $h_C$ and a scalar $alpha geq 0$, the support function of the scaled set is

\[h_{\alpha C}(q) = \alpha\, h_C(q), \quad q \in H.\]

When $alpha = 0$ the set $0 cdot C = {0}$ and $h(q) = 0$ for all $q$.

Parameters:
  • base – The support function $h_C$.

  • alpha – A nonnegative scalar.

Raises:

ValueError – If alpha < 0.

Note

Phase 3: support_point() propagates support points by scaling them. For $alpha > 0$: returns $alpha cdot x_C^*(q)$ if available; For $alpha = 0$: returns the zero vector (support point of ${0}$); If base has no support_point, returns None.

support_point(q: Vector) 'Vector' | None[source]

Return the support point of the scaled set $alpha C$ at direction $q$.

For $alpha > 0$: Returns $alpha cdot x_C^*(q)$ if the base has a support point. For $alpha = 0$: Returns the zero vector (the unique support point of the singleton ${0}$). If the base has no support point and $alpha > 0$, returns None.

Parameters:

q – A vector in the primal space $H$.

Returns:

The scaled support point $alpha cdot x_C^*(q)$, the zero vector for $alpha=0$, or None.

class pygeoinf.convex_analysis.SupportFunction(primal_domain: HilbertSpace)[source]

Bases: NonLinearForm, ABC

Support function of a closed convex set S ⊆ H:

h_S(q) = sup_{x ∈ S} ⟨q, x⟩, q ∈ H

In a Hilbert space, we identify H ≅ H* via the Riesz map, so the functional is defined directly on the primal space H.

The set S is uniquely recovered as:

S = {x : ⟨q, x⟩ ≤ h_S(q) for all q ∈ H}

classmethod callable(primal_domain: HilbertSpace, mapping: Callable[[Vector], float], support_point: Callable[[Vector], Vector] | None = None) CallableSupportFunction[source]

Construct a support function from a user-supplied callable.

Parameters:
  • primal_domain – The Hilbert space H.

  • mapping – A callable q -> float that evaluates $h(q)$.

  • support_point – An optional callable q -> Vector returning $x^*(q) in argmax_{x in C} langle q, x rangle$. When provided, subgradient(q) delegates to it.

Returns:

A CallableSupportFunction instance.

image(operator: LinearOperator) LinearImageSupportFunction[source]

Return the support function of the linear image $A(C)$.

For a bounded linear operator $A$ with A.domain == self.primal_domain, returns the support function of the image set $A(C)$, which lives in A.codomain. Its value is $h_{A(C)}(q) = h_C(A^* q)$.

Parameters:

operator – A bounded linear operator $A: H to K$ with operator.domain equal to self.primal_domain.

Returns:

A LinearImageSupportFunction on operator.codomain.

Raises:

ValueError – If operator.domain != self.primal_domain.

classmethod point(primal_domain: HilbertSpace, point: Vector) PointSupportFunction[source]

Construct the support function of the singleton set ${p}$.

For a fixed point $p in H$, the support function is $h(q) = langle q, p rangle$.

Parameters:
  • primal_domain – The Hilbert space H containing $p$.

  • point – The fixed point $p$.

Returns:

A PointSupportFunction instance.

property primal_domain: HilbertSpace

The Hilbert space H in which the underlying convex set lives.

scale(alpha: float) ScaledSupportFunction[source]

Return the support function of the scaled set $alpha C$.

Scaling satisfies $h_{alpha C}(q) = alpha, h_C(q)$ for $alpha geq 0$.

Parameters:

alpha – A nonnegative scalar.

Returns:

A ScaledSupportFunction on the same space.

Raises:

ValueError – If alpha < 0.

subgradient(q: Vector) Vector[source]

Return a subgradient of the support function at q.

support_point(q: Vector) 'Vector' | None[source]

Optional: return x*(q) ∈ argmax_{x∈S} ⟨q, x⟩ if available/computable. Default: not provided (returns None). This is the subgradient of h_S at q.

translate(point: Vector) MinkowskiSumSupportFunction[source]

Return the support function of the translated set $C + p$.

Translation by $p in H$ satisfies $h_{C+p}(q) = h_C(q) + langle q, p rangle$.

Parameters:

point – The translation vector $p in H$ (same space as primal_domain).

Returns:

A MinkowskiSumSupportFunction on the same space.

value_and_support_point(q: Vector) tuple[float, Vector | None][source]

Return (h(q), x*(q)) sharing intermediate work where possible.

For a support function $h_S(q) = sup_{x in S} langle q, x rangle$, the scalar value and the maximiser $x^*(q)$ often share intermediate computations (e.g. a norm or an operator application). This method provides a single entry point so that overriding subclasses can exploit that sharing.

The default implementation simply calls self(q) and self.support_point(q) separately and is always correct. Concrete subclasses should override this when a fused computation is cheaper.

Parameters:

q – A vector in the primal domain $H$.

Returns:

A tuple (value, point) where

  • value is the float $h(q)$, always present;

  • point is $x^*(q) in argmax_{x in S} langle q, x rangle$ as a Vector, or None when a support point is unavailable.

pygeoinf.convex_optimisation module

Convex optimisation utilities for non-smooth problems.

This module provides a minimal subgradient descent implementation suitable for learning and experimentation. It assumes the objective is a NonLinearForm that can provide a subgradient oracle via form.subgradient(x).

class pygeoinf.convex_optimisation.Bundle[source]

Bases: object

Collection of cutting-plane linearisations of a convex function.

A bundle stores a list of Cut objects and provides utilities for building the piecewise-linear epigraph model:

hat_phi(lambda) = max_j [ f_j + <g_j, lambda - x_j> ]

used by proximal and level bundle methods.

Examples

>>> space = EuclideanSpace(3)
>>> bundle = Bundle()
>>> bundle.add_cut(Cut(x=np.zeros(3), f=1.0, g=np.ones(3), iteration=0))
>>> len(bundle)
1
add_cut(cut: Cut) None[source]

Append cut to the bundle.

Parameters:

cut – The Cut to add.

best_point() Vector[source]

Return the evaluation point with the lowest recorded function value.

Returns:

The vector x_j achieving min_j f_j.

Raises:

ValueError – If the bundle is empty.

compress(max_size: int) None[source]

Discard all but the max_size most recent cuts.

Parameters:

max_size – Maximum number of cuts to retain. If the bundle already has fewer cuts than max_size, nothing is changed.

linearization_matrix(stability_center: Vector, domain: HilbertSpace) Tuple[np.ndarray, np.ndarray][source]

Build the inequality constraint matrix for the epigraph QP.

For each cut (x_j, f_j, g_j) the cutting-plane constraint is:

f_j + <g_j, lambda - x_j> <= t

which is equivalent to:

g_j^T @ lambda - t <= g_j^T @ x_j - f_j

Stacking all cuts gives the system A_ineq @ [lambda; t] <= b where row i is [g_i^T, -1] and b_i = g_i^T @ x_i - f_i.

Parameters:
  • stability_center – Current stability/proximal centre (not used to build the constraint data, but kept for API consistency with Phase 2).

  • domain – The Hilbert space whose to_components converts vectors to flat numpy arrays.

Returns:

A tuple (A_ineq, b_ineq) where

  • A_ineq has shape (n_cuts, dim + 1) — columns are [lambda_1, …, lambda_d, t].

  • b_ineq has shape (n_cuts,).

Raises:

ValueError – If the bundle is empty.

lower_bound() float[source]

Return a placeholder lower bound (-infinity).

The true lower bound min_lambda hat_phi(lambda) is computed as the master QP/LP objective value by the outer bundle solver (Phase 2–3). This placeholder allows BundleResult to report f_low before any master problem has been solved.

Returns:

-np.inf

upper_bound() float[source]

Return the best (lowest) function value seen so far.

This is a valid upper bound on the optimum since f* <= f(x_j) for all recorded points x_j.

Returns:

min_j f_j.

Raises:

ValueError – If the bundle is empty.

class pygeoinf.convex_optimisation.BundleResult(x_best: Vector, f_best: float, f_low: float, gap: float, converged: bool, num_iterations: int, num_serious_steps: int, function_values: List[float], iterates: List['Vector'] | None = None)[source]

Bases: object

Result from a bundle method optimisation run.

x_best

Best primal iterate found (lowest function value).

Type:

Vector

f_best

Function value at x_best; upper bound on the optimum.

Type:

float

f_low

Lower bound on the optimum from the cutting-plane model.

Type:

float

gap

Optimality gap f_best - f_low (non-negative for a valid lower bound).

Type:

float

converged

Whether the gap tolerance was satisfied.

Type:

bool

num_iterations

Number of bundle loop iterations (master solves), excluding the initial oracle evaluation before the loop.

Type:

int

num_serious_steps

Number of serious (descent) steps taken.

Type:

int

function_values

History of function values at each iteration.

Type:

List[float]

iterates

Optional history of all iterates (memory intensive).

Type:

Optional[List[‘Vector’]]

converged: bool
f_best: float
f_low: float
function_values: List[float]
gap: float
iterates: List['Vector'] | None = None
num_iterations: int
num_serious_steps: int
x_best: Vector
class pygeoinf.convex_optimisation.ChambollePockResult(m: Vector, v: Vector, mu: Vector, primal_dual_gap: float, converged: bool, num_iterations: int)[source]

Bases: object

Result from ChambollePockSolver.

m

Primal variable m* in B (model space).

Type:

Vector

v

Primal variable v* in V (data space).

Type:

Vector

mu

Dual variable mu* in D (data space). Approximates the optimal Lagrange multiplier for the equality constraint G @ m + v = d_tilde.

Type:

Vector

primal_dual_gap

Feasibility residual ||G @ m* + v* - d_tilde|| at termination.

Type:

float

converged

True if primal_dual_gap < tolerance.

Type:

bool

num_iterations

Number of iterations performed.

Type:

int

converged: bool
m: Vector
mu: Vector
num_iterations: int
primal_dual_gap: float
v: Vector
class pygeoinf.convex_optimisation.ChambollePockSolver(B: SupportFunction, V: SupportFunction, G: LinearOperator, d_tilde: Vector, /, *, sigma: float | None = None, tau: float | None = None, theta: float = 1.0, max_iterations: int = 1000, tolerance: float = 1e-06)[source]

Bases: object

Solve the primal feasibility form of the dual master via Chambolle-Pock.

Solves the constrained maximisation:
h_U(q) = max_{m in B, v in V} <c, m>

subject to: G @ m + v = d_tilde

where c = T* @ q is the linear objective and the feasible set (B, V, G, d_tilde) is fixed, using the first-order primal-dual algorithm of Chambolle & Pock (2011).

The saddle-point reformulation (with dual variable mu in D) is:
min_{m in B, v in V} max_{mu}

<G @ m + v - d_tilde, mu> - <c, m>

with operator K = [G; I_D] : M x D -> D.

Convergence rate: O(1/N) in the primal-dual gap when tau * sigma * ||K||^2 <= 1 (where ||K||^2 <= ||G||^2 + 1).

This is particularly efficient when the objective c = T* @ q changes while the feasible set (B, V, G, d_tilde) remains fixed.

Parameters:
  • B – Support function for the model prior (B subset of M).

  • V – Support function for the data error set (V subset of D).

  • G – Forward operator G: M -> D.

  • d_tilde – Observed data vector d_tilde in D.

  • sigma – Dual step size. If None, auto-selected from power iteration.

  • tau – Primal step size. If None, auto-selected from power iteration.

  • theta – Over-relaxation parameter (default 1.0).

  • max_iterations – Maximum number of iterations.

  • tolerance – Convergence tolerance on feasibility residual ||G @ m + v - d_tilde||.

References

Chambolle A. & Pock T. (2011). A First-Order Primal-Dual Algorithm for Convex Problems with Applications to Imaging. Journal of Mathematical Imaging and Vision, 40(1), 120–145. https://doi.org/10.1007/s10851-010-0251-1

solve(c: Vector, m0: Vector | None = None) ChambollePockResult[source]

Run the Chambolle-Pock iterations for objective direction $c$.

Solves

\[h_U = \max_{m \in B,\, v \in V}\; \langle c, m \rangle \quad\text{s.t.}\quad Gm + v = \tilde{d}\]

Iteration (with $θ = 1$, $K = [G;\ I]$):

  1. $μ^{n+1} = μ^n + σ(G\bar{m}^n + \bar{v}^n - \tilde{d})$

  2. $m^{n+1} = \operatorname{proj}_B\bigl(m^n - τ G^* μ^{n+1} + τ c\bigr)$

  3. $v^{n+1} = \operatorname{proj}_V\bigl(v^n - τ μ^{n+1}\bigr)$

  4. $\bar{m}^{n+1} = m^{n+1} + θ(m^{n+1} - m^n)$, v_bar^{n+1} = v^{n+1} + theta * (v^{n+1} - v^n)

Convergence is declared when the feasibility residual ||G @ m + v - d_tilde|| < tolerance.

Parameters:
  • c – Linear objective coefficient in model space (typically c = T* @ q).

  • m0 – Initial model vector. Defaults to zero.

Returns:

ChambollePockResult containing the primal optimisers $(m^*, v^*)$, dual variable $μ^*$, feasibility gap, and convergence diagnostics.

class pygeoinf.convex_optimisation.ClarabelQPSolver(*, verbose: bool = False, max_iter: int = 200, eps_abs: float = 1e-08, eps_rel: float = 1e-08)[source]

Bases: object

QP solver using Clarabel (interior-point). Requires pip install clarabel.

Converts the OSQP standard form $l ≤ Ax ≤ u$ to Clarabel’s cone form internally. Equality constraints ($l_i = u_i$) are handled via clarabel.ZeroConeT; inequality constraints via clarabel.NonnegativeConeT.

Parameters:
  • verbose – Whether Clarabel prints solver output (default False).

  • max_iter – Maximum interior-point iterations (default 200).

  • eps_abs – Absolute convergence tolerance (default 1e-8).

  • eps_rel – Relative convergence tolerance (default 1e-8).

Raises:

ImportError – If the clarabel package is not installed.

solve(P: ndarray, q: ndarray, A: ndarray, l: ndarray, u: ndarray, x0: ndarray | None = None) QPResult[source]

Solve the QP using Clarabel and return a QPResult.

Parameters:
  • P – Symmetric PSD Hessian of shape (n, n).

  • q – Linear cost vector of shape (n,).

  • A – Constraint matrix of shape (m, n).

  • l – Lower-bound vector of shape (m,); -np.inf for one-sided.

  • u – Upper-bound vector of shape (m,); +np.inf for one-sided.

  • x0 – Warm-start hint (ignored if API unavailable).

Returns:

QPResult with status='solved' on success.

Notes

Clarabel’s constraint form is $Ax + s = b$, $s ∈ K$. For NonnegativeConeT this means $b - Ax ≥ 0$, i.e. $Ax ≤ b$. Each OSQP row is therefore expanded into at most two Clarabel rows.

class pygeoinf.convex_optimisation.Cut(x: Vector, f: float, g: Vector, iteration: int)[source]

Bases: object

A linearisation cut for a convex function at a point.

A cut records the function value and a subgradient at an evaluation point, defining the affine lower bound: f_j + <g_j, lambda - x_j> <= f(lambda) for all lambda.

x

Evaluation point (a Hilbert-space vector).

Type:

Vector

f

Function value f(x).

Type:

float

g

Subgradient g in partial_f(x).

Type:

Vector

iteration

Iteration index at which this cut was generated.

Type:

int

f: float
g: Vector
iteration: int
x: Vector
class pygeoinf.convex_optimisation.KKTResult(m: Vector, multipliers: tuple[float, float], converged: bool, num_iterations: int)[source]

Bases: object

Result from PrimalKKTSolver.

m

Optimal primal model vector $u^*$ in the model space.

Type:

Vector

multipliers

KKT multipliers $(lambda^*, mu^*)$. $lambda^*$ enforces the model prior constraint and $mu^*$ enforces the data-fit constraint. Both are non-negative.

Type:

tuple[float, float]

converged

True if the root-finder converged to the required tolerance.

Type:

bool

num_iterations

Number of function evaluations used by the root-finder (or 1 for the closed-form branch).

Type:

int

converged: bool
m: Vector
multipliers: tuple[float, float]
num_iterations: int
class pygeoinf.convex_optimisation.LevelBundleMethod(oracle: NonLinearForm, /, *, alpha: float = 0.1, tolerance: float = 1e-06, max_iterations: int = 500, bundle_size: int = 100, store_iterates: bool = False, qp_solver: QPSolver | None = None)[source]

Bases: object

Level bundle method for minimising a non-smooth convex function.

Solves:

min_{lambda in D} f(lambda)

where f is a convex function accessible through a value + subgradient oracle (a NonLinearForm with subgradient).

At each iteration the level master QP is:

min_{lambda, t} (1/2) ||lambda - lambda_hat||^2 subject to: f_j + <g_j, lambda - x_j> <= t for all j

t <= f_lev

where the level is: f_lev = alpha * f_low + (1 - alpha) * f_up, alpha in (0,1).

The lower bound f_low is maintained as the LP optimal value of the cutting-plane model:

f_LP = min_{lambda} hat_phi(lambda)

= min_{lambda, t} t

subject to: f_j + <g_j, lambda - x_j> <= t

Infeasibility handling. If the level QP is infeasible (which can happen when f_lev < f_LP for a tight alpha), alpha is widened by a factor of 1.5 (capped at 0.9) for up to three attempts. If all attempts fail an emergency proximal step is taken (minimize t + (1/2) ||lambda - lambda_hat||^2 over the current bundle) so the stability centre and bundle are always updated.

Parameters:
  • oracle – Non-smooth convex functional with subgradient oracle.

  • alpha – Level parameter alpha in (0, 1) controlling how aggressively the level is set towards the lower bound. Smaller values are more aggressive (risk infeasibility); larger are more conservative. Defaults to 0.1.

  • tolerance – Convergence tolerance; terminates when the duality gap f_up - f_low <= tolerance.

  • max_iterations – Maximum number of oracle calls.

  • bundle_size – Maximum number of cuts retained in the bundle.

  • store_iterates – If True, all iterates are stored in BundleResult.iterates.

  • qp_solver – QP solver implementing QPSolver. Defaults to SciPyQPSolver if None.

Examples

>>> from pygeoinf.hilbert_space import EuclideanSpace
>>> from pygeoinf.nonlinear_forms import NonLinearForm
>>> import numpy as np
>>> domain = EuclideanSpace(1)
>>> f = lambda x: float(x[0]**2 + 2*x[0])
>>> g = lambda x: np.array([2*x[0] + 2.0])
>>> oracle = NonLinearForm(domain, f, subgradient=g)
>>> solver = LevelBundleMethod(oracle, tolerance=1e-5)
>>> result = solver.solve(domain.from_components(np.array([2.0])))
>>> np.testing.assert_allclose(domain.to_components(result.x_best), [-1.0], atol=1e-3)
solve(x0: Vector) BundleResult[source]

Run the level bundle method starting from x0.

Parameters:

x0 – Initial point in the domain of the oracle.

Returns:

A BundleResult summarising the optimisation run.

class pygeoinf.convex_optimisation.OSQPQPSolver(*, eps_abs: float = 1e-06, eps_rel: float = 1e-06, verbose: bool = False, polish: bool = True, max_iter: int = 10000)[source]

Bases: object

QP solver using OSQP (ADMM-based). Requires pip install osqp.

Supports warm-starting via the x0 parameter. A fresh OSQP instance is created for every solve() call to avoid stale state.

Parameters:
  • eps_abs – Absolute feasibility tolerance (default 1e-6).

  • eps_rel – Relative feasibility tolerance (default 1e-6).

  • verbose – Whether OSQP prints solver output (default False).

  • polish – Whether to apply polishing step for higher accuracy (default True).

  • max_iter – Maximum number of ADMM iterations (default 10000).

Raises:

ImportError – If the osqp package is not installed.

solve(P: ndarray, q: ndarray, A: ndarray, l: ndarray, u: ndarray, x0: ndarray | None = None) QPResult[source]

Solve the QP using OSQP and return a QPResult.

Parameters:
  • P – Symmetric PSD Hessian of shape (n, n).

  • q – Linear cost vector of shape (n,).

  • A – Constraint matrix of shape (m, n).

  • l – Lower-bound vector of shape (m,); -np.inf for one-sided.

  • u – Upper-bound vector of shape (m,); +np.inf for one-sided.

  • x0 – Optional warm-start primal solution of shape (n,).

Returns:

QPResult with status='solved' on success.

class pygeoinf.convex_optimisation.PrimalKKTSolver(B: SupportFunction, V: SupportFunction, G: LinearOperator, d_tilde: Vector, /, *, fsolve_tol: float = 1e-10, fsolve_maxfev: int = 200)[source]

Bases: object

Primal KKT solver via Woodbury identity in abstract Hilbert spaces.

The solver operates on abstract Hilbert-space vectors throughout: the model space $H$ is never discretised. Only the data space $D$ (which is explicitly finite-dimensional) is discretised — and only inside _woodbury_solve(), which builds the $M times M$ Woodbury system.

The Woodbury reduction:

\[u^*(\lambda,\mu) = \frac{1}{\lambda} A_B^{-1} r_H - \frac{1}{\lambda} A_B^{-1} G^* K^{-1} G A_B^{-1} r_H\]

where

\[r_H = c + \lambda A_B u_0 + \mu G^* A_V \tilde{d}, \qquad K = \tfrac{1}{\mu} A_V^{-1} + \tfrac{1}{\lambda} G A_B^{-1} G^*\]

The $M times M$ matrix $P = G A_B^{-1} G^*$ is precomputed once via .matrix(dense=True) which probes the data space only. No model-space to_components or from_components is ever called.

Ball/ball simplification ($A_B = I_H$, $A_V = I_D$):

\[r_H = c + \lambda u_0 + \mu G^* \tilde{d}, \qquad P = G G^*, \qquad K = \tfrac{1}{\mu} I_D + \tfrac{1}{\lambda} G G^*\]

so the only M×M solve is $K z = G w$ and no model-space matrix is ever formed.

Supports BallSupportFunction and EllipsoidSupportFunction for both $B$ and $V$ (four combinations).

Parameters:
  • B – Model prior support function.

  • V – Data error support function (ball or ellipsoid, centered at origin).

  • G – Forward operator $G : H to D$.

  • d_tilde – Observed data vector in $D$.

  • fsolve_tol – Tolerance for scipy.optimize.fsolve.

  • fsolve_maxfev – Maximum function evaluations for fsolve.

solve(c: Vector) KKTResult[source]

Solve for $u^*$ maximising $langle c, u rangle$ over the feasible set.

Uses a two-branch strategy:

  1. Compute the support point $u_{rm ball}$ of $B$ in direction $c$. If the data constraint is satisfied, return immediately.

  2. Otherwise both constraints are active; solve the $2 times 2$ root-finding problem in log-space via scipy.optimize.fsolve, warm-started from $(lambda_{rm prev}, mu_{rm prev})$.

The model space is never discretised during this method.

Parameters:

c – Linear objective in model space $H$.

Returns:

KKTResult with the optimal model vector $u^*$, KKT multipliers, convergence flag, and iteration count.

class pygeoinf.convex_optimisation.ProximalBundleMethod(oracle: NonLinearForm, /, *, rho0: float = 1.0, rho_factor: float = 2.0, tolerance: float = 1e-06, max_iterations: int = 500, bundle_size: int = 100, store_iterates: bool = False, qp_solver: QPSolver | None = None)[source]

Bases: object

Proximal bundle method for minimising a non-smooth convex function.

Solves:

min_{lambda in D} f(lambda)

where f is a convex function accessible through a value + subgradient oracle (a NonLinearForm with subgradient).

At each iteration the master QP is:

min_{lambda, t} t + (rho / 2) ||lambda - lambda_hat||^2 subject to: f_j + <g_j, lambda - x_j> <= t for all j in bundle

where lambda_hat is the current stability centre and rho > 0 is the proximal weight.

A serious step is taken whenever the new oracle value f(lambda_+) < f(lambda_hat); otherwise a null step occurs and rho is increased to tighten the proximal term.

Parameters:
  • oracle – Non-smooth convex functional with subgradient oracle.

  • rho0 – Initial proximal weight rho > 0.

  • rho_factor – Multiplicative factor applied to rho on null steps (divide on serious steps).

  • tolerance – Convergence tolerance; terminates when the duality gap f_up - f_low <= tolerance.

  • max_iterations – Maximum number of oracle calls.

  • bundle_size – Maximum number of cuts retained in the bundle.

  • store_iterates – If True, all iterates are stored in BundleResult.iterates.

  • qp_solver – QP solver implementing QPSolver. Defaults to SciPyQPSolver if None.

Examples

>>> from pygeoinf.hilbert_space import EuclideanSpace
>>> from pygeoinf.nonlinear_forms import NonLinearForm
>>> import numpy as np
>>> domain = EuclideanSpace(1)
>>> f = lambda x: float(x[0]**2 + 2*x[0])
>>> g = lambda x: np.array([2*x[0] + 2.0])
>>> oracle = NonLinearForm(domain, f, subgradient=g)
>>> solver = ProximalBundleMethod(oracle, tolerance=1e-5)
>>> result = solver.solve(domain.from_components(np.array([2.0])))
>>> np.testing.assert_allclose(domain.to_components(result.x_best), [-1.0], atol=1e-3)
solve(x0: Vector) BundleResult[source]

Run the proximal bundle method starting from x0.

Parameters:

x0 – Initial point in the domain of the oracle.

Returns:

A BundleResult summarising the optimisation run.

class pygeoinf.convex_optimisation.QPResult(x: ndarray, obj: float, status: str)[source]

Bases: object

Result from a quadratic programme solve.

x

Solution vector (component array).

Type:

numpy.ndarray

obj

Objective value at x.

Type:

float

status

'solved' on success; a descriptive failure message otherwise.

Type:

str

obj: float
status: str
x: ndarray
class pygeoinf.convex_optimisation.QPSolver(*args, **kwargs)[source]

Bases: Protocol

Protocol for QP solvers used by bundle methods.

Solvers must implement the OSQP standard form:

min_x (1/2) x^T @ P @ x + q^T @ x subject to: l <= A @ x <= u

Parameters:
  • P – Symmetric positive-semi-definite Hessian, shape (n, n).

  • q – Linear cost, shape (n,).

  • A – Constraint matrix, shape (m, n).

  • l – Lower bounds, shape (m,); use -np.inf for one-sided.

  • u – Upper bounds, shape (m,); use +np.inf for one-sided.

  • x0 – Optional warm-start primal solution.

Returns:

A QPResult.

solve(P: ndarray, q: ndarray, A: ndarray, l: ndarray, u: ndarray, x0: ndarray | None = None) QPResult[source]
class pygeoinf.convex_optimisation.SciPyQPSolver[source]

Bases: object

QP solver backed by scipy.optimize.minimize() with method='SLSQP'.

Implements the QPSolver protocol. Converts the OSQP standard-form bounds $l ≤ Ax ≤ u$ to SLSQP inequality/equality constraints.

Notes

SLSQP is a gradient-based method suitable for small to medium problems (up to a few hundred variables). For large-scale bundle QPs use an OSQP- or Clarabel-backed solver (Phase 4).

solve(P: ndarray, q: ndarray, A: ndarray, l: ndarray, u: ndarray, x0: ndarray | None = None) QPResult[source]

Solve the QP and return a QPResult.

Parameters:
  • P – Symmetric PSD Hessian of shape (n, n).

  • q – Linear cost vector of shape (n,).

  • A – Constraint matrix of shape (m, n).

  • l – Lower-bound vector of shape (m,).

  • u – Upper-bound vector of shape (m,).

  • x0 – Optional warm-start point of shape (n,).

Returns:

QPResult with status='solved' on success.

class pygeoinf.convex_optimisation.SmoothedDualMaster(cost: object, epsilon: float)[source]

Bases: object

Smooth approximation of DualMasterCostFunction using Moreau-Yosida smoothing.

Smooths the norm-type support functions with parameter epsilon, making the objective differentiable. Only supports BallSupportFunction and EllipsoidSupportFunction.

The smoothed ball support is

\[σ_{B,\varepsilon}(z) = ⟨ z, c ⟩ + r\,\sqrt{‖z‖^2 + \varepsilon^2}\]

and its gradient w.r.t. $z$ is

\[\nabla_z σ_{B,\varepsilon}(z) = c + r\,\frac{z}{\sqrt{‖z‖^2 + \varepsilon^2}}\]

The smoothed ellipsoid support is

\[σ_{E,\varepsilon}(z) = ⟨ z, c ⟩ + r\,\sqrt{⟨ z,\, A^{-1}z ⟩ + \varepsilon^2}\]

and its gradient w.r.t. $z$ is

\[\nabla_z σ_{E,\varepsilon}(z) = c + r\,\frac{A^{-1}z}{\sqrt{⟨ z,\, A^{-1}z ⟩ + \varepsilon^2}}\]

The full smoothed objective and its gradient are

\[\varphi_\varepsilon(λ) = ⟨λ, \tilde{d}⟩ + σ_{B,\varepsilon}(T^*q - G^*λ) + σ_{V,\varepsilon}(-λ)\]
\[\nabla_λ \varphi_\varepsilon(λ) = \tilde{d} - G\,\nabla_{z_1}σ_{B,\varepsilon}(z_1) - \nabla_{z_2}σ_{V,\varepsilon}(z_2)\]

where $z_1 = T^*q - G^*λ$ and $z_2 = -λ$.

Parameters:
  • costDualMasterCostFunction instance.

  • epsilon – Smoothing parameter ($> 0$). Smaller values give a better approximation but a larger Lipschitz constant $L = r‖G‖^2 / varepsilon$.

Raises:

NotImplementedError – If either support function is not a BallSupportFunction or EllipsoidSupportFunction.

gradient(lam: Vector) Vector[source]

Compute the gradient $nabla_λ varphi_varepsilon(λ)$.

Uses the chain rule through $z_1 = T^*q - G^*λ$ and $z_2 = -λ$:

\[\nabla_λ \varphi_\varepsilon = \tilde{d} - G\,\nabla_{z_1}σ_{B,\varepsilon}(z_1) - \nabla_{z_2}σ_{V,\varepsilon}(z_2)\]
Parameters:

lam – Dual variable $λ ∈ D$.

Returns:

Gradient vector in $D$.

Raises:

NotImplementedError – If either support function is unsupported.

class pygeoinf.convex_optimisation.SmoothedLBFGSSolver(cost: object, /, *, epsilon0: float = 0.01, n_levels: int = 5, tolerance: float = 1e-06, max_iter_per_level: int = 500)[source]

Bases: object

L-BFGS-B optimiser with smoothing continuation for DualMasterCostFunction.

Uses Moreau-Yosida smoothing with a geometric continuation schedule:

epsilon_0 >> epsilon_1 >> … >> epsilon_{L-1} ≈ tol

where epsilon_i = epsilon_0 × 10^{-i}. Each level is solved with L-BFGS-B, warm-starting from the previous solution.

Note

Returns BundleResult with gap=np.nan and f_low=np.nan — no gap certificate is available for smoothed methods.

Parameters:
  • costDualMasterCostFunction instance.

  • epsilon0 – Initial smoothing parameter (default 1e-2).

  • n_levels – Number of continuation levels (default 5).

  • tolerance – Target accuracy; last epsilon is $varepsilon_0 × 10^{-(n_levels - 1)}$.

  • max_iter_per_level – Maximum L-BFGS-B iterations per level (default 500).

solve(lam0: Vector) BundleResult[source]

Run the smoothed L-BFGS-B continuation and return the result.

Parameters:

lam0 – Starting point $λ_0 ∈ D$.

Returns:

BundleResult with gap and f_low set to np.nan (no subgradient-based lower bound is maintained).

class pygeoinf.convex_optimisation.SubgradientDescent(oracle: NonLinearForm, /, *, step_size: float, max_iterations: int = 500, store_iterates: bool = False, stagnation_window: int | None = None)[source]

Bases: object

Basic subgradient descent for minimising non-smooth convex functions.

Algorithm:

x_{k+1} = x_k - α * g_k

where g_k ∈ ∂f(x_k) is a subgradient (obtained via oracle.subgradient(x_k)).

This implementation uses CONSTANT step size α for all k. Convergence is not guaranteed with constant step size; use for learning/testing only.

Parameters:
  • oracle – A NonLinearForm with subgradient() method returning subgradients.

  • step_size – Constant step size α > 0.

  • max_iterations – Maximum number of iterations.

  • store_iterates – Whether to store full history (memory intensive).

  • stagnation_window – Optional number of iterations without improvement to declare convergence.

property domain: HilbertSpace
property oracle: NonLinearForm
solve(x0: Vector) SubgradientResult[source]

Run subgradient descent from initial point x0.

class pygeoinf.convex_optimisation.SubgradientResult(x_best: Vector, f_best: float, x_final: Vector, f_final: float, num_iterations: int, converged: bool, function_values: List[float], iterates: List['Vector'] | None = None)[source]

Bases: object

Result from subgradient descent optimisation.

x_best

Best point found (lowest function value).

Type:

Vector

f_best

Best function value found.

Type:

float

x_final

Final iterate (may differ from x_best).

Type:

Vector

f_final

Final function value.

Type:

float

num_iterations

Number of iterations performed.

Type:

int

converged

Whether convergence criterion was met.

Type:

bool

function_values

History of function values at each iteration.

Type:

List[float]

iterates

Optional history of all iterates (memory intensive).

Type:

Optional[List[‘Vector’]]

converged: bool
f_best: float
f_final: float
function_values: List[float]
iterates: List['Vector'] | None = None
num_iterations: int
x_best: Vector
x_final: Vector
pygeoinf.convex_optimisation.best_available_qp_solver() QPSolver[source]

Return the best available QP solver (OSQP > Clarabel > SciPy).

Tries solvers in order of preference: OSQP (ADMM, fast for large-scale), then Clarabel (interior-point, high accuracy), then the SciPy SLSQP fallback.

Returns:

A QPSolver instance backed by the best installed package.

pygeoinf.convex_optimisation.solve_primal_feasibility(cost, qs: list[Vector] | np.ndarray, cp_solver: ChambollePockSolver) np.ndarray[source]

Compute support values h_U(q_i) using the primal feasibility form.

Solves one Chambolle-Pock problem for each direction q_i (using c = T* @ q_i), exploiting that the feasible set (B, V, G, d_tilde) is independent of q.

The support value for direction q is:
h_U(q) = max_{m in B, v in V} <T* @ q, m>

subject to: G @ m + v = d_tilde

= <T* @ q, m*(q)>

where m*(q) is returned by ChambollePockSolver.solve().

Parameters:
  • costDualMasterCostFunction holding references to T, G, and the model space.

  • qs – Directions to evaluate; a list of Vectors (in the property space) or an np.ndarray of shape (p, prop_dim).

  • cp_solver – Pre-configured ChambollePockSolver for the problem.

Returns:

np.ndarray of shape (p,) with h_U(q_i) for each direction.

pygeoinf.convex_optimisation.solve_support_values(cost, qs, solver, lambda0, *, warm_start: bool = True, n_jobs: int = 1)[source]

Compute support function values for multiple directions.

Solves the dual master minimisation for each direction q_i in qs, optionally warm-starting from the previous direction’s solution.

The support function of a set U evaluated at direction q is:

h_U(q) = min_{lambda} f(q, lambda)

where f(q, ·) is the dual master cost with direction q.

Parameters:
  • cost – DualMasterCostFunction instance with a set_direction method.

  • qs – Directions to evaluate; either a list of Vectors or an np.ndarray of shape (p, prop_dim).

  • solver – Bundle method solver (ProximalBundleMethod or LevelBundleMethod).

  • lambda0 – Initial lambda for the first direction (a Vector in the data space).

  • warm_start – If True (default), each direction starts from the previous direction’s optimal lambda. If False, always start from lambda0.

  • n_jobs – Number of parallel jobs. 1 = fully sequential (warm starting works). >1 = joblib Parallel (warm-starting across workers is disabled; each worker starts from lambda0).

Returns:

np.ndarray of shape (p,), support values

$h_U(q_i)$ for each direction.

lambdas: list of length p, optimal lambda for each

direction.

diagnostics: list of BundleResult for each direction.

Return type:

values

Raises:

ImportError – If n_jobs > 1 and joblib is not installed (falls back to sequential with a warning instead of raising).

pygeoinf.datasets module

pygeoinf/datasets.py

Provides access to built-in datasets for testing, benchmarking, and visualization across the pygeoinf package.

pygeoinf.datasets.download_gsn_stations(force: bool = False) None[source]

Fetches the Global Seismograph Network (GSN) stations from the IRIS FDSN API and saves them to a local CSV file in the data/ directory.

pygeoinf.datasets.download_usgs_earthquakes(min_magnitude: float = 5.0, start_time: str = None, end_time: str = None, min_depth: float = None, max_depth: float = None, bbox: Tuple[float, float, float, float] = None, limit: int = 2000, force: bool = False, filename: str = 'usgs_events_filtered.csv') None[source]

Fetches a filtered catalog of earthquakes from the USGS API and saves it to a CSV in the centralized DATADIR.

pygeoinf.datasets.load_gsn_stations(n_stations: int = None, include_names: bool = False) List[Tuple[float, float]] | List[Tuple[str, float, float]][source]

Loads a representative global set of seismic stations from the GSN.

If the internal CSV file is missing, this function will attempt to automatically download it from IRIS into the pygeoinf/data/ directory.

Parameters:
  • n_stations – If provided, returns a random subsample of this size. If greater than the total available stations, returns all.

  • include_names – If True, returns (Name, Latitude, Longitude). If False, returns pure (Latitude, Longitude) tuples.

Returns:

A list of station tuples in degrees.

pygeoinf.datasets.sample_earthquakes(n_events: int, min_magnitude: float = 5.0) List[Tuple[float, float, float]][source]

Returns a random subsample of real earthquake locations.

If the local cache does not contain enough events to satisfy the request, it automatically fetches a larger catalog from the USGS to rebuild the cache.

Parameters:
  • n_events – The exact number of earthquake locations to return.

  • min_magnitude – The minimum magnitude to use if a new download is required.

Returns:

(Latitude, Longitude, Depth_in_km).

Return type:

A list of tuples

pygeoinf.direct_sum module

Implements direct sums of Hilbert spaces and corresponding block operators.

This module provides tools for constructing larger, composite Hilbert spaces and operators from smaller ones. This is essential for problems involving multiple coupled fields or joint inversions where a single model is constrained by data from different experiments.

Key Classes

  • HilbertSpaceDirectSum: A HilbertSpace formed by the direct sum of a list of other spaces. Vectors in this space are lists of vectors from the component subspaces.

  • BlockLinearOperator: A LinearOperator acting between direct sum spaces, represented as a 2D grid (matrix) of sub-operators.

  • ColumnLinearOperator: A specialized block operator mapping from a single space to a direct sum space.

  • RowLinearOperator: A specialized block operator mapping from a direct sum space to a single space.

  • BlockDiagonalLinearOperator: An efficient representation for block operators with zero off-diagonal blocks.

class pygeoinf.direct_sum.BlockDiagonalLinearOperator(operators: List[LinearOperator])[source]

Bases: LinearOperator, BlockStructure

A block operator where all off-diagonal blocks are zero operators.

block(i: int, j: int) LinearOperator[source]

Returns the operator in the (i, j)-th sub-block.

If i equals j, this is one of the diagonal operators. Otherwise, it is a zero operator.

class pygeoinf.direct_sum.BlockLinearOperator(blocks: List[List[LinearOperator]])[source]

Bases: LinearOperator, BlockStructure

A linear operator between direct sum spaces, defined by a matrix of sub-operators.

This operator acts like a matrix where each entry is itself a LinearOperator. It maps a list of input vectors [x_1, x_2, …] to a list of output vectors [y_1, y_2, …]. The constructor checks for dimensional consistency between the blocks.

block(i: int, j: int) LinearOperator[source]

Returns the operator in the (i, j)-th sub-block.

class pygeoinf.direct_sum.BlockStructure(row_dim: int, col_dim: int)[source]

Bases: ABC

An abstract base class for operators with a block structure.

abstract block(i: int, j: int) LinearOperator[source]

Returns the operator in the (i, j)-th sub-block.

property col_dim: int

Returns the number of columns in the block structure.

property row_dim: int

Returns the number of rows in the block structure.

class pygeoinf.direct_sum.ColumnLinearOperator(operators: List[LinearOperator])[source]

Bases: LinearOperator, BlockStructure

An operator that maps from a single space to a direct sum space.

It can be visualized as a column vector of operators, [A_1, A_2, …]^T. It takes a single input vector x and produces a list of output vectors [A_1(x), A_2(x), …]. This is often used to represent a joint forward operator in an inverse problem.

block(i: int, j: int) LinearOperator[source]

Returns the operator in the (i, 0)-th sub-block.

class pygeoinf.direct_sum.HilbertSpaceDirectSum(spaces: List[HilbertSpace])[source]

Bases: HilbertSpace

A Hilbert space formed from the direct sum of a list of other spaces.

A vector in this space is represented as a list of vectors, where the i-th element of the list is a vector from the i-th component subspace. The inner product is the sum of the inner products of the components.

add(xs: List[Any], ys: List[Any]) List[Any][source]

Computes the sum of two vectors. Defaults to x + y.

ax(a: float, xs: List[Any]) None[source]

Performs in-place scaling x := a*x. Defaults to x *= a.

axpy(a: float, xs: List[Any], ys: List[Any]) None[source]

Performs in-place operation y := y + a*x. Defaults to y += a*x.

canonical_dual_inverse_isomorphism(xp: LinearForm) List[LinearForm][source]

Maps a dual vector on the sum space to a list of dual vectors.

This is the inverse of the canonical isomorphism, projecting the action of a dual vector onto each subspace.

Parameters:

xp (LinearForm) – A dual vector on the direct sum space.

canonical_dual_isomorphism(xps: List[LinearForm]) LinearForm[source]

Maps a list of dual vectors to a single dual vector on the sum space.

This is the canonical isomorphism from the direct sum of the dual spaces to the dual of the direct sum space.

Parameters:

xps (List[LinearForm]) – A list of dual vectors, one for each subspace.

copy(xs: List[Any]) List[Any][source]

Returns a deep copy of a vector. Defaults to x.copy().

property dim: int

Returns the dimension of the direct sum space.

from_components(c: ndarray) List[Any][source]

Maps a NumPy component array back to a vector in the space.

Parameters:

c – The components of the vector as a NumPy array.

Returns:

The corresponding vector in the space.

from_dual(xp: LinearForm) List[Any][source]

Maps a dual vector back to its representative in the primal space.

This is the inverse of the Riesz representation map defined by to_dual.

Parameters:

xp – A vector in the dual space.

Returns:

The corresponding vector in the primal space.

is_element(xs: Any) bool[source]

Checks if a list of vectors is a valid element of the direct sum space.

multiply(a: float, xs: List[Any]) List[Any][source]

Computes scalar multiplication. Defaults to a * x.

property number_of_subspaces: int

Returns the number of subspaces in the direct sum.

subspace(i: int) HilbertSpace[source]

Returns the i-th subspace.

Parameters:

i (int) – The index of the subspace to retrieve.

subspace_inclusion(i: int) LinearOperator[source]

Returns the inclusion operator from the i-th subspace into the sum.

Parameters:

i (int) – The index of the subspace to include from.

subspace_projection(i: int) LinearOperator[source]

Returns the projection operator onto the i-th subspace.

Parameters:

i (int) – The index of the subspace to project onto.

property subspaces: List[HilbertSpace]

Returns the list of subspaces that form the direct sum.

subtract(xs: List[Any], ys: List[Any]) List[Any][source]

Computes the difference of two vectors. Defaults to x - y.

to_components(xs: List[Any]) ndarray[source]

Maps a vector to its representation as a NumPy component array.

Parameters:

x – A vector in the space.

Returns:

The components of the vector as a NumPy array.

to_dual(xs: List[Any]) LinearForm[source]

Maps a vector to its canonical dual vector (a linear functional).

This method, along with from_dual, defines the Riesz representation map and implicitly defines the inner product of the space.

Parameters:

x – A vector in the primal space.

Returns:

The corresponding vector in the dual space.

property zero: List[Any]

each subspace contributes its own zero.

Type:

The zero element

class pygeoinf.direct_sum.RowLinearOperator(operators: List[LinearOperator])[source]

Bases: LinearOperator, BlockStructure

An operator that maps from a direct sum space to a single space.

It can be visualized as a row vector of operators, [A_1, A_2, …]. It takes a list of input vectors [x_1, x_2, …] and produces a single output vector y = A_1(x_1) + A_2(x_2) + …. The adjoint of a ColumnLinearOperator is a RowLinearOperator.

block(i: int, j: int) LinearOperator[source]

Returns the operator in the (0, j)-th sub-block.

pygeoinf.dynamical_system module

A module defining the base architecture for dynamical systems on Hilbert spaces.

class pygeoinf.dynamical_system.AutonomousDynamicalSystem(state_space: HilbertSpace, operator: NonLinearOperator)[source]

Bases: DynamicalSystem

A concrete implementation of a non-linear, autonomous system. Represents du/dt = F(u), where the operator F does not change with time.

dynamical_rule(t: float) NonLinearOperator[source]

Returns the static non-linear operator.

Parameters:

t – The current time (ignored, but kept for interface compliance).

property is_autonomous: bool

Indicates if the system’s governing operator is time-independent. Defaults to False for a general dynamical system.

class pygeoinf.dynamical_system.AutonomousLinearSystem(state_space: HilbertSpace, operator: LinearOperator)[source]

Bases: LinearDynamicalSystem

A concrete implementation of a linear, autonomous system. Represents du/dt = Lu, where the linear operator L is constant.

dynamical_rule(t: float) LinearOperator[source]

Returns the static linear operator.

Parameters:

t – The current time (ignored, but kept for interface compliance).

property is_autonomous: bool

Indicates if the system’s governing operator is time-independent. Defaults to False for a general dynamical system.

class pygeoinf.dynamical_system.DynamicalSystem(state_space: HilbertSpace)[source]

Bases: ABC

Base abstract class for a dynamical system on a Hilbert space. Represents the generally non-linear, non-autonomous system du/dt = F(t, u).

abstract dynamical_rule(t: float) NonLinearOperator[source]

Returns the non-linear operator F(t, .) mapping the state to its derivative.

Parameters:

t – The current time.

Returns:

A pygeoinf NonLinearOperator mapping state_space to state_space.

property is_autonomous: bool

Indicates if the system’s governing operator is time-independent. Defaults to False for a general dynamical system.

class pygeoinf.dynamical_system.LinearDynamicalSystem(state_space: HilbertSpace)[source]

Bases: DynamicalSystem

Abstract base class for a linear, non-autonomous dynamical system. Represents du/dt = L(t)u.

This enforces a stricter return type (LinearOperator) so integrators can optimize Jacobian evaluations and matrix operations.

abstract dynamical_rule(t: float) LinearOperator[source]

Returns the linear operator L(t) mapping the state to its derivative.

Parameters:

t – The current time.

Returns:

A pygeoinf LinearOperator mapping state_space to state_space.

pygeoinf.forward_problem module

Defines the mathematical structure of a forward problem.

This module provides classes that encapsulate the core components of an inverse problem. A forward problem describes the physical or mathematical process that maps a set of unknown model parameters u to a set of observable data d.

The module handles both the deterministic relationship d = A(u) and the more realistic statistical model d = A(u) + e, where e represents random noise.

Key Classes

  • ForwardProblem: A general class representing the link between a model space and a data space via a forward operator, with an optional data error.

  • LinearForwardProblem: A specialization for linear problems where the forward operator is a LinearOperator.

class pygeoinf.forward_problem.ForwardProblem(forward_operator: NonLinearOperator, /, *, data_error_measure: GaussianMeasure | None = None)[source]

Bases: object

Represents a general forward problem.

An instance is defined by a forward operator that maps from a model space to a data space, and an optional Gaussian measure representing the statistical distribution of errors in the data.

property data_error_measure: GaussianMeasure

The measure from which data errors are drawn.

property data_error_measure_set: bool

True if a data error measure has been set.

property data_space: HilbertSpace

The data space (codomain of the forward operator).

property forward_operator: LinearOperator

The forward operator, mapping from model to data space.

property model_space: HilbertSpace

The model space (domain of the forward operator).

class pygeoinf.forward_problem.LinearForwardProblem(forward_operator: LinearOperator, /, *, data_error_measure: GaussianMeasure | None = None)[source]

Bases: ForwardProblem

Represents a linear forward problem of the form d = A(u) + e.

Here, d is the data, A is the linear forward operator, u is the model, and e is a random error drawn from a Gaussian distribution.

chi_squared(model: Vector, data: Vector) float[source]

Calculates the chi-squared statistic for a given model and data.

This measures the misfit between the predicted and observed data.

  • If a data error measure with an inverse covariance C_e^-1 is defined, this is the weighted misfit: (d - A(u))^T * C_e^-1 * (d - A(u)).

  • Otherwise, it is the squared L2 norm of the data residual: ||d - A(u)||^2.

Parameters:
  • model – A vector from the model space.

  • data – An observed data vector from the data space.

Returns:

The chi-squared statistic.

chi_squared_from_residual(residual: Vector) float[source]

Calculates the chi-squared statistic from a residual vector.

Parameters:

residual – The residual vector.

Returns:

The chi-squared statistic.

chi_squared_test(significance_level: float, model: Vector, data: Vector) bool[source]

Performs a chi-squared test for goodness of fit.

Parameters:
  • significance_level – The significance level for the test (e.g., 0.95).

  • model – A vector from the model space.

  • data – An observed data vector from the data space.

Returns:

True if the model is statistically compatible with the data at the specified significance level, False otherwise.

critical_chi_squared(significance_level: float) float[source]

Returns the critical value of the chi-squared statistic.

This value serves as the threshold for the chi-squared test at a given significance level.

Parameters:

significance_level – The desired significance level (e.g., 0.95).

Returns:

The critical chi-squared value.

data_measure_from_model(model: Vector) GaussianMeasure[source]

Returns the Gaussian measure for the data, given a specific model.

The resulting measure has a mean of A(model) and the covariance of the data error.

Parameters:

model – A vector from the model space.

Returns:

The Gaussian measure representing the distribution of possible data.

data_measure_from_model_measure(model_measure: GaussianMeasure) GaussianMeasure[source]

Given a measure for the model space, returns the induced measure on the data space.

data_reduced_problem(reduction_operator: LinearOperator, /, *, reduced_data_error_measure: GaussianMeasure | None = None, dense: bool = False, parallel: bool = False, n_jobs: int = -1) LinearForwardProblem[source]

Creates a new forward problem by applying a reduction (or sketching) operator to the data space.

Parameters:
  • reduction_operator – A LinearOperator mapping from the current data space to the new reduced data space.

  • reduced_data_error_measure – An optional data error measure on the reduced data space. If not provided, the original data error measure is pushed forward automatically.

  • dense – If True, computes and stores operators as dense matrices.

  • parallel – If True, computes the dense matrices in parallel.

  • n_jobs – Number of CPU cores to use. -1 means all available.

Returns:

A new LinearForwardProblem operating in the reduced data space.

static from_direct_sum(forward_problems: List[LinearForwardProblem]) LinearForwardProblem[source]

Forms a joint forward problem from a list of separate problems.

This is a powerful tool for joint inversions, where a single underlying model is observed through multiple, independent measurement systems (e.g., different types of geophysical surveys).

Parameters:

forward_problems – A list of LinearForwardProblem instances that share a common model space.

Returns:

A single LinearForwardProblem where the data space is the direct sum of the individual data spaces.

joint_measure(model_measure: GaussianMeasure) GaussianMeasure[source]

Given a measure for the model space, returns the joint measure for the model and data.

parameterized_problem(parameterization: LinearOperator, /, *, dense: bool = False, parallel: bool = False, n_jobs: int = -1) LinearForwardProblem[source]

Creates a new forward problem based on a model parameterization.

synthetic_data(model: Vector) Vector[source]

Generates a synthetic data vector for a given model.

The data is computed as d = A(model) + e, where e is a random sample from the data error measure.

Parameters:

model – A vector from the model space.

Returns:

A synthetic data vector.

synthetic_model_and_data(prior: GaussianMeasure) Tuple[Vector, Vector][source]

Generates a random model and corresponding synthetic data.

Parameters:

prior – A Gaussian measure on the model space, from which the random model u will be drawn.

Returns:

A tuple (u, d), where u is the random model and d is the corresponding synthetic data.

pygeoinf.functional_calculus module

Functional calculus for abstract linear operators.

This module provides the machinery to evaluate matrix functions of the form f(A)v, where A is a self-adjoint LinearOperator, v is a vector in a Hilbert space, and f is a continuous function defined on the spectrum of A.

The primary mathematical engine is the Lanczos method. Given a self-adjoint LinearOperator C, a vector v in H, and a real-valued analytic function f, it computes the approximation:

f(C)v ~= ||v||_H * V_k * f(T_k) * e_1

where T_k is the k x k symmetric tridiagonal matrix produced by k steps of the Lanczos recurrence, V_k is the orthogonal basis of the Krylov subspace, and e_1 is the first standard basis vector. This approach yields the optimal degree-k polynomial approximation to the true action of the function.

class pygeoinf.functional_calculus.LanczosOperatorFunction(operator: LinearOperator, func: Callable[[ndarray], ndarray], size_estimate: int, *, method: Literal['variable', 'fixed'] = 'variable', max_k: int | None = None, reorth: str = 'full', rtol: float = 0.001, atol: float = 1e-08, check_interval: int = 5)[source]

Bases: LinearOperator

A matrix-free LinearOperator representing the action of a continuous function applied to a self-adjoint positive operator.

Rather than explicitly computing and storing the dense matrix f(A), this class evaluates the matrix-vector product f(A)x dynamically on the fly using the Lanczos process. This allows for highly efficient evaluations in massive or infinite-dimensional Hilbert spaces.

property base_operator: LinearOperator

Returns the underlying base LinearOperator.

pygeoinf.functional_calculus.apply_operator_function(operator: LinearOperator, v: Vector, func: Callable[[ndarray], ndarray], size_estimate: int, *, method: Literal['variable', 'fixed'] = 'variable', max_k: int | None = None, reorth: str = 'full', rtol: float = 0.001, atol: float = 1e-08, check_interval: int = 5) Vector[source]

Computes the action of a matrix function on a vector, f(A)v, using the Lanczos approximation.

This function builds a Krylov subspace from the starting vector v. It projects the large operator onto this subspace to form a small tridiagonal matrix T. The target function f is evaluated on the eigensystem of T, and the result is lifted back to the original full-dimensional space.

Parameters:
  • operator (LinearOperator) – The self-adjoint positive operator A.

  • v (Vector) – The input vector to be multiplied by f(A).

  • func (Callable) – A vectorized scalar function.

  • size_estimate (int) – The initial or fixed number of Krylov basis vectors.

  • method (str) – ‘variable’ to stop dynamically when the relative change in the approximated vector falls below tolerance. ‘fixed’ to run an exact number of iterations.

  • max_k (int, optional) – Hard limit on Krylov dimension.

  • reorth (str) – ‘full’ for complete basis orthogonalization, ‘none’ otherwise.

  • rtol (float) – Relative tolerance for convergence checking.

  • atol (float) – Absolute tolerance for convergence checking.

  • check_interval (int) – Iterations between convergence checks.

Returns:

The result of f(A)v residing in the same Hilbert space as v.

Return type:

Vector

pygeoinf.functional_calculus.iter_lanczos_tridiagonalize(operator: LinearOperator, v: Vector, max_k: int, *, reorth: str = 'full') Iterator[Tuple[List[Vector], ndarray]][source]

Generator that lazily yields the Krylov basis and tridiagonal matrix at each step of the Lanczos process.

This engine progressively builds an orthonormal basis for the Krylov subspace K_k(A, v) while simultaneously projecting the operator into that subspace to form a symmetric tridiagonal matrix T.

It includes an early-stopping mechanism: if the starting vector v is fully contained within an invariant subspace of dimension less than max_k, the residual norm will drop to zero, and the generator will terminate cleanly to prevent numerical breakdown.

Parameters:
  • operator (LinearOperator) – The self-adjoint operator A.

  • v (Vector) – The starting vector.

  • max_k (int) – The maximum number of Krylov subspace dimensions to build.

  • reorth (str, optional) – Reorthogonalization strategy. ‘full’ employs the “twice is enough” modified Gram-Schmidt algorithm against all previous basis vectors, enforcing strict numerical orthogonality. ‘none’ only orthogonalizes against the immediate two predecessors. Defaults to ‘full’.

Yields:

Iterator[Tuple[List[Vector], np.ndarray]]

At each step k (from 1 to max_k),

yields a tuple containing: - The current list of k orthonormal basis vectors. - The current k x k symmetric tridiagonal numpy array T.

Raises:

ValueError – If max_k is less than 1, if an invalid reorth strategy is passed, or if the starting vector v is the zero vector.

pygeoinf.functional_calculus.lanczos_tridiagonalize(operator: LinearOperator, v: Vector, max_k: int, *, reorth: str = 'full') Tuple[List[Vector], ndarray][source]

Executes a fixed number of Lanczos iterations to tridiagonalize the operator.

This is a convenience wrapper around the iter_lanczos_tridiagonalize generator. It runs the process to completion and returns only the final state.

Parameters:
  • operator (LinearOperator) – The self-adjoint operator to tridiagonalize.

  • v (Vector) – The starting vector for the Krylov subspace.

  • max_k (int) – The maximum number of iterations/dimensions to compute.

  • reorth (str, optional) – The reorthogonalization strategy (‘full’ or ‘none’). Defaults to “full”.

Returns:

  • A list of orthonormal basis vectors defining the Krylov subspace.

  • The final symmetric tridiagonal matrix T of size up to (max_k, max_k).

Return type:

Tuple[List[Vector], np.ndarray]

pygeoinf.functional_calculus.operator_function_quadratic_form(operator: LinearOperator, v: Vector, func: Callable[[ndarray], ndarray], size_estimate: int, *, method: Literal['variable', 'fixed'] = 'variable', max_k: int | None = None, reorth: str = 'full', rtol: float = 0.001, atol: float = 1e-08, check_interval: int = 5) float[source]

Computes the quadratic form <v, f(A)v> using the Lanczos approximation.

This function evaluates the quadratic form much more efficiently than explicitly computing the full vector f(A)v first. It relies on the spectral theorem: the quadratic form is equivalent to an integral over the spectral measure of A. The Lanczos process naturally generates the nodes (eigenvalues) and weights (squared first components of eigenvectors) for a highly accurate Gaussian quadrature of this integral.

Parameters:
  • operator (LinearOperator) – The self-adjoint positive operator A.

  • v (Vector) – The input vector.

  • func (Callable) – A vectorized scalar function.

  • size_estimate (int) – The initial or fixed number of Krylov basis vectors.

  • method (str) – ‘variable’ to check convergence dynamically, ‘fixed’ otherwise.

  • max_k (int, optional) – Hard limit on Krylov dimension.

  • reorth (str) – Reorthogonalization strategy (‘full’ or ‘none’).

  • rtol (float) – Relative tolerance for convergence checking.

  • atol (float) – Absolute tolerance for convergence checking.

  • check_interval (int) – Iterations between convergence checks.

Returns:

The scalar evaluation of the quadratic form.

Return type:

float

pygeoinf.gaussian_measure module

Provides a class for representing Gaussian measures on Hilbert spaces.

This module generalizes the concept of a multivariate normal distribution to the setting of abstract Hilbert spaces. A GaussianMeasure is defined by its expectation (a vector in the space) and its covariance (a self-adjoint, positive semi-definite LinearOperator).

This abstraction is fundamental for Bayesian inference, Gaussian processes, and data assimilation in function spaces.

Key Features

  • Multiple factory methods for creating measures from various inputs (matrices, samples, standard deviations).

  • A method for drawing random samples from the measure.

  • Implementation of the affine transformation rule (y = A(x) + b).

  • Support for creating low-rank approximations of the measure for efficiency.

  • Overloaded arithmetic operators for intuitive combination of measures.

class pygeoinf.gaussian_measure.GaussianMeasure(*, covariance: LinearOperator = None, covariance_factor: LinearOperator = None, expectation: Vector = None, sample: Callable[[], Vector] = None, inverse_covariance: LinearOperator = None, inverse_covariance_factor: LinearOperator = None)[source]

Bases: object

Represents a Gaussian measure on a Hilbert space.

This class generalizes the multivariate normal distribution to abstract, potentially infinite-dimensional, Hilbert spaces. A measure is defined by its expectation (mean vector) and its covariance, which is a LinearOperator on the space.

affine_mapping(*, operator: LinearOperator = None, translation: Vector = None, affine_operator: AffineOperator = None, inverse_solver: LinearSolver = None, inverse_preconditioner: LinearOperator = None) GaussianMeasure[source]

Transforms the measure under an affine map y = A(x) + b.

This method calculates the push-forward measure. It can also construct the implied inverse covariance (precision) using a saddle-point (KKT) system.

Parameters:
  • operator – The linear part of the mapping (A).

  • translation – The translation vector (b).

  • affine_operator – An AffineOperator instance (cannot be used with operator or translation).

  • inverse_solver – A solver used to evaluate the KKT inverse covariance.

  • inverse_preconditioner – A preconditioner for the inverse_solver.

Returns:

A new GaussianMeasure representing the push-forward distribution.

Raises:

ValueError – If mutually exclusive arguments are provided, or if an inverse solve is requested but the prior lacks an inverse covariance.

ambient_ball(probability: float, /, **kwargs)[source]

Shortcut for credible_set(..., geometry='ambient_ball', ...).

as_multivariate_normal(*, parallel: bool = False, n_jobs: int = -1) <scipy.stats._multivariate.multivariate_normal_gen object at 0x7d8aa0531940>[source]

Returns the measure as a scipy.stats.multivariate_normal object.

Parameters:
  • parallel – If True, evaluates the dense covariance matrix concurrently.

  • n_jobs – The number of CPU cores to use if parallel=True.

Returns:

A frozen scipy.stats.multivariate_normal object.

Raises:

NotImplementedError – If the measure is not defined on a EuclideanSpace.

property covariance: LinearOperator

The covariance operator of the measure.

property covariance_factor: LinearOperator

The covariance factor L. Raises AttributeError if not set.

property covariance_factor_set: bool

True if a covariance factor L (such that C = L @ L*) is available.

credible_set(probability: float, /, *, geometry: str = 'ellipsoid', rank: int | None = None, open_set: bool = False, theta: float | None = None, spectrum=None, spectrum_size: int | None = None, radius_method: str = 'auto', quantile_method: str = 'auto', quantile_tol: float = 0.01, fractional_apply: str = 'auto', n_samples: int = 10000, lanczos_size_estimate: int = 50, lanczos_method: Literal['variable', 'fixed'] = 'fixed', lanczos_max_k: int | None = None, lanczos_rtol: float = 0.001, lanczos_atol: float = 1e-08, lanczos_check_interval: int = 5, spectrum_low_rank_kwargs: dict | None = None, rng: Generator | None = None)[source]

Return a probability-calibrated Gaussian credible subset.

Five geometries are supported:

"ellipsoid" / "mahalanobis" / "domain"

The classical Mahalanobis ellipsoid.

"cameron_martin" / "cm" / "ball" / "norm_ball"

The ellipsoid expressed as a unit ball in the Cameron-Martin geometry.

"ambient_ball" / "ambient"

The ambient norm ball ${m : |m - m_0|_H le r_p}$.

"weakened_ellipsoid" / "fractional"

The weakened-covariance ellipsoid ${m : |C^{-theta/2}(m-m_0)|_H le r_p}$.

Parameters:
  • probability – Credible probability $p$, strictly between 0 and 1.

  • geometry – Selects the ball/ellipsoid family (see above).

  • rank – Chi-square degrees of freedom (legacy modes only).

  • open_set – If true, return the open version of the set.

  • theta – Fractional exponent in $(0, 1)$, required for weakened_ellipsoid.

  • spectrum – Covariance spectrum specification.

  • spectrum_size – Truncation length when spectrum is callable or None.

  • radius_method"auto", "spectral", or "sampling".

  • quantile_method – Weighted-chi-square quantile method.

  • quantile_tol – Desired relative accuracy of the weighted-chi-square quantile.

  • fractional_apply – How to apply $C^{-theta/2}$ for the weakened ellipsoid.

  • n_samples – Monte Carlo sample count for sampling radius.

  • lanczos_size_estimate – Initial or fixed Krylov dimension for Lanczos fractional evaluation.

  • lanczos_method – ‘fixed’ or ‘variable’ dynamic convergence for Lanczos.

  • lanczos_max_k – Maximum Krylov dimension if ‘variable’ is used.

  • lanczos_rtol – Relative tolerance for Lanczos convergence.

  • lanczos_atol – Absolute tolerance for Lanczos convergence.

  • lanczos_check_interval – Number of iterations between Lanczos convergence checks.

  • spectrum_low_rank_kwargs – Extra kwargs forwarded to LowRankEig.from_randomized.

  • rng – Optional NumPy generator for Monte Carlo paths.

Returns:

An Ellipsoid or Ball defining the credible subset.

deflated_pointwise_std(rank: int, /, *, size_estimate: int = 0, method: str = 'variable', max_samples: int = None, rtol: float = 0.01, block_size: int = 10, parallel: bool = False, n_jobs: int = -1) Vector[source]

Estimates the pointwise standard deviation field using a deflated Hutchinson’s method.

Parameters:
  • rank – The rank of the deterministic SVD deflation.

  • size_estimate – The initial number of stochastic residual samples.

  • method – ‘variable’ to sample until rtol is met, ‘fixed’ otherwise.

  • max_samples – Hard limit on stochastic residual samples.

  • rtol – Relative tolerance for the stochastic residual phase.

  • block_size – Number of samples added per check in the ‘variable’ method.

  • parallel – If True, draws stochastic samples in parallel.

  • n_jobs – The number of CPU cores to use.

Returns:

A vector representing the pointwise standard deviation field.

deflated_pointwise_variance(rank: int, /, *, size_estimate: int = 0, method: str = 'variable', max_samples: int = None, rtol: float = 0.01, block_size: int = 10, parallel: bool = False, n_jobs: int = -1) Vector[source]

Estimates the pointwise variance field using a deflated Hutchinson’s method.

This combines a deterministic low-rank extraction (via SVD deflation) with a stochastic Hutchinson trace estimator for the residual variance.

Parameters:
  • rank – The rank of the deterministic SVD deflation.

  • size_estimate – The initial number of stochastic residual samples.

  • method – ‘variable’ to sample until rtol is met, ‘fixed’ otherwise.

  • max_samples – Hard limit on stochastic residual samples.

  • rtol – Relative tolerance for the stochastic residual phase.

  • block_size – Number of samples added per check in the ‘variable’ method.

  • parallel – If True, draws stochastic samples in parallel.

  • n_jobs – The number of CPU cores to use.

Returns:

A vector representing the pointwise variance field.

Raises:

NotImplementedError – If the domain is not a HilbertModule.

directional_covariance(d1: Vector, d2: Vector, /) float[source]

Returns the covariance between the scalar projections <x, d1> and <x, d2>.

Parameters:
  • d1 – The first test vector.

  • d2 – The second test vector.

Returns:

The covariance scalar.

directional_statistics(direction: Vector, /) Tuple[float, float][source]

Returns the expectation and variance of the scalar Gaussian <x, direction>.

Parameters:

direction – The test vector.

Returns:

A tuple containing (expectation, variance).

directional_variance(d: Vector, /) float[source]

Returns the variance of the scalar projection <x, d>.

Parameters:

d – The test vector.

Returns:

The variance scalar.

property domain: HilbertSpace

The Hilbert space the measure is defined on.

property expectation: Vector

The expectation (mean) vector of the measure.

static from_covariance_matrix(domain: HilbertSpace, covariance_matrix: np.ndarray, /, *, expectation: Vector = None, rtol: float = 1e-10) GaussianMeasure[source]

Creates a Gaussian measure from a dense covariance matrix.

Parameters:
  • domain – The Hilbert space on which the measure is defined.

  • covariance_matrix – A 2D symmetric positive semi-definite NumPy array.

  • expectation – The mean vector. Defaults to the zero vector.

  • rtol – Relative tolerance for clipping small negative eigenvalues caused by floating-point inaccuracies.

Returns:

A new GaussianMeasure instance.

Raises:

ValueError – If the matrix has significantly negative eigenvalues.

static from_direct_sum(measures: List[GaussianMeasure], /) GaussianMeasure[source]

Constructs a product measure from a list of other measures.

The resulting measure resides on the direct sum of the input domains, with block-diagonal covariance and concatenated expectations.

Parameters:

measures – A list of GaussianMeasure instances.

Returns:

A new GaussianMeasure instance defined on the direct sum space.

static from_samples(domain: HilbertSpace, samples: List[Vector], /) GaussianMeasure[source]

Estimates a Gaussian measure from a collection of sample vectors.

Constructs an empirical mean and an unnormalized sample covariance operator using a tensor product expansion.

Parameters:
  • domain – The Hilbert space the samples belong to.

  • samples – A list of sample vectors.

Returns:

A new GaussianMeasure instance.

Raises:

ValueError – If the list of samples is empty.

static from_standard_deviation(domain: HilbertSpace, standard_deviation: float, /, *, expectation: Vector = None) GaussianMeasure[source]

Creates an isotropic Gaussian measure with a scaled identity covariance.

Parameters:
  • domain – The Hilbert space on which the measure is defined.

  • standard_deviation – The uniform standard deviation for all dimensions.

  • expectation – The mean vector. Defaults to the zero vector.

Returns:

A new GaussianMeasure instance.

static from_standard_deviations(domain: HilbertSpace, standard_deviations: np.ndarray, /, *, expectation: Vector = None) GaussianMeasure[source]

Creates a Gaussian measure with a diagonal covariance operator.

Parameters:
  • domain – The Hilbert space on which the measure is defined.

  • standard_deviations – A 1D NumPy array representing the diagonal entries of the covariance factor.

  • expectation – The mean vector. Defaults to the zero vector.

Returns:

A new GaussianMeasure instance.

Raises:

ValueError – If the size of the array does not match the space dimension.

property has_zero_expectation: bool

True if the measure is internally stored with an exactly zero expectation.

property inverse_covariance: LinearOperator

The inverse covariance (precision) operator. Raises AttributeError if not set.

property inverse_covariance_factor: LinearOperator

The inverse covariance factor. Raises AttributeError if not set.

property inverse_covariance_factor_set: bool

True if an inverse covariance factor is available.

property inverse_covariance_set: bool

True if the inverse covariance (precision) operator is available.

kl_divergence(other: GaussianMeasure, /, *, method: Literal['dense', 'randomized'] = 'dense', hutchinson_size_estimate: int = 10, hutchinson_method: Literal['variable', 'fixed'] = 'variable', max_samples: int | None = None, rtol: float = 0.01, block_size: int = 5, lanczos_size_estimate: int = 40, lanczos_method: Literal['variable', 'fixed'] = 'variable', lanczos_max_k: int | None = None, lanczos_rtol: float = 0.001, lanczos_atol: float = 1e-08, lanczos_check_interval: int = 5, parallel: bool = False, n_jobs: int = -1) float[source]

Computes the exact or approximate Kullback-Leibler (KL) divergence D_KL(self || other).

This calculates the divergence of ‘self’ (P) from the prior/reference measure ‘other’ (Q).

Parameters:
  • other – The reference GaussianMeasure (Q).

  • method – ‘dense’ uses exact dense matrix factorizations (O(N^3)). ‘randomized’ uses matrix-free Stochastic Lanczos Quadrature (SLQ).

  • hutchinson_size_estimate – Initial samples for the randomized trace estimator.

  • hutchinson_method – ‘variable’ to sample until rtol is met, ‘fixed’ otherwise.

  • max_samples – Hard limit on Hutchinson samples.

  • rtol – Relative tolerance for the Hutchinson estimator.

  • block_size – Samples added per check in the ‘variable’ Hutchinson method.

  • lanczos_size_estimate – Initial Krylov dimension for fractional evaluations.

  • lanczos_method – ‘variable’ or ‘fixed’ convergence for Lanczos.

  • lanczos_max_k – Maximum Krylov dimension if ‘variable’ is used.

  • lanczos_rtol – Relative tolerance for Lanczos convergence.

  • lanczos_atol – Absolute tolerance for Lanczos convergence.

  • lanczos_check_interval – Iterations between Lanczos convergence checks.

  • parallel – If True, evaluates the stochastic probes concurrently.

  • n_jobs – Number of CPU cores to use if parallel=True.

Returns:

The calculated KL divergence.

Raises:

ValueError – If the measures reside on different domains, or if the ‘randomized’ method is called without an inverse covariance on the reference measure.

low_rank_approximation(size_estimate: int, /, *, method: str = 'variable', max_rank: int = None, power: int = 2, rtol: float = 0.0001, block_size: int = 10, parallel: bool = False, n_jobs: int = -1) GaussianMeasure[source]

Constructs a low-rank approximation of the measure.

Uses randomized matrix-free algorithms to factorize the covariance.

Parameters:
  • size_estimate – Target rank or initial sample size for the algorithm.

  • method – ‘variable’ to sample dynamically, ‘fixed’ otherwise.

  • max_rank – Upper limit on rank for the ‘variable’ method.

  • power – Number of power iterations to enhance spectral decay.

  • rtol – Relative tolerance for the ‘variable’ method.

  • block_size – Samples drawn per iteration in the ‘variable’ method.

  • parallel – If True, parallelizes the evaluations.

  • n_jobs – Number of CPU cores to use if parallel=True.

Returns:

A new GaussianMeasure backed by a LowRankCholesky covariance factor.

rescale_directional_variance(direction: Vector, std: float, /) GaussianMeasure[source]

Returns a new measure where Var[<x, direction>] is scaled to std^2.

Parameters:
  • direction – The test vector to scale against.

  • std – The target standard deviation for the projection.

Returns:

A variance-scaled GaussianMeasure.

Raises:

ValueError – If the current directional variance is zero or negative.

sample() Vector[source]

Returns a single random sample drawn from the measure.

Returns:

A randomly sampled vector.

Raises:

NotImplementedError – If a sample method is not set for this measure.

sample_expectation(n: int, /, *, parallel: bool = False, n_jobs: int = -1) Vector[source]

Estimates the expectation vector by drawing n Monte Carlo samples.

Parameters:
  • n – The number of samples to use for the estimation.

  • parallel – If True, draws samples concurrently.

  • n_jobs – The number of CPU cores to use if parallel=True.

Returns:

The empirical expectation vector.

sample_pointwise_std(n: int, /, *, parallel: bool = False, n_jobs: int = -1) Vector[source]

Estimates the pointwise standard deviation field by drawing n Monte Carlo samples.

Parameters:
  • n – The number of samples to use.

  • parallel – If True, draws samples concurrently.

  • n_jobs – The number of CPU cores to use if parallel=True.

Returns:

A vector representing the pointwise standard deviation field.

sample_pointwise_variance(n: int, /, *, parallel: bool = False, n_jobs: int = -1) Vector[source]

Estimates the pointwise variance field by drawing n Monte Carlo samples.

Parameters:
  • n – The number of samples to use.

  • parallel – If True, draws samples concurrently.

  • n_jobs – The number of CPU cores to use if parallel=True.

Returns:

A vector representing the pointwise variance field.

Raises:

NotImplementedError – If the domain is not a HilbertModule (which provides pointwise multiplication).

property sample_set: bool

True if a method for drawing random samples is available.

samples(n: int, /, *, parallel: bool = False, n_jobs: int = -1) List[Vector][source]

Returns a list of n independent random samples from the measure.

Parameters:
  • n – The number of samples to draw.

  • parallel – If True, draws samples concurrently.

  • n_jobs – The number of CPU cores to use if parallel=True.

Returns:

A list of sampled vectors.

Raises:

ValueError – If n is less than 1.

two_point_covariance(point: Any, /) Vector[source]

Computes the two-point covariance function radiating from a specific point.

Parameters:

point – The spatial coordinate to evaluate from.

Returns:

The covariance field evaluated at the chosen point.

Raises:

NotImplementedError – If the domain lacks a dirac_representation method.

weakened_ellipsoid(probability: float, /, *, theta: float, **kwargs)[source]

Shortcut for the weakened-covariance ellipsoid mode.

with_dense_covariance(*, parallel: bool = False, n_jobs: int = -1) GaussianMeasure[source]

Forms a new Gaussian measure equivalent to the existing one, but with its covariance matrix stored explicitly in dense form.

Parameters:
  • parallel – If True, computes the dense matrix concurrently.

  • n_jobs – Number of CPU cores to use if parallel=True.

Returns:

A new GaussianMeasure instance backed by a dense matrix.

with_regularized_inverse(solver: LinearSolver, /, *, damping: float = 0.0, preconditioner: LinearOperator | None = None) GaussianMeasure[source]

Returns a new GaussianMeasure with a well-defined precision operator (inverse covariance) computed via Tikhonov regularization.

Parameters:
  • solver – The linear solver used to invert the covariance.

  • damping – Tikhonov regularization parameter added to the diagonal.

  • preconditioner – Optional preconditioner for iterative solvers.

Returns:

A new GaussianMeasure instance equipped with an inverse covariance.

Raises:

ValueError – If the damping parameter is negative.

with_sparse_approximation(*, threshold: float = 0.001, max_nnz: int | None = None, diag_rank: int = 0, diag_samples: int = 0, regularization_fraction: float = 0.0001, parallel: bool = False, n_jobs: int = -1) GaussianMeasure[source]

Creates an approximately equivalent measure with a sparse covariance matrix and an exactly factorized sparse inverse, built entirely matrix-free.

Parameters:
  • threshold – Minimum correlation required to keep an off-diagonal element.

  • max_nnz – Maximum number of non-zero elements allowed per column.

  • diag_rank – Rank of deterministic SVD used to estimate the diagonal.

  • diag_samples – Number of stochastic samples used to estimate the diagonal.

  • regularization_fraction – Tikhonov regularization applied before sparse inversion.

  • parallel – If True, computes the sparse approximations concurrently.

  • n_jobs – Number of CPU cores to use if parallel=True.

Returns:

A new GaussianMeasure backed by sparse operators.

zero_expectation() GaussianMeasure[source]

Returns a new measure with the same covariance, but zero expectation.

Returns:

A mean-shifted GaussianMeasure.

pygeoinf.hilbert_space module

Defines the foundational abstractions for working with Hilbert spaces.

This module provides the core HilbertSpace abstract base class (ABC), which serves as a mathematical abstraction for real vector spaces equipped with an inner product. The design separates abstract vector operations from their concrete representations (e.g., as NumPy arrays), allowing for generic and reusable implementations of linear operators and algorithms.

The inner product of a space is defined by its Riesz representation map (to_dual and from_dual methods), which connects the space to its dual. Concrete subclasses must implement the abstract methods to define a specific type of space.

Key Classes

  • HilbertSpace: The primary ABC defining the interface for all Hilbert spaces.

  • DualHilbertSpace: A wrapper class representing the dual of a Hilbert space.

  • HilbertModule: An ABC for Hilbert spaces that also support vector multiplication.

  • EuclideanSpace: A concrete implementation for R^n using NumPy arrays.

  • MassWeightedHilbertSpace: A space whose inner product is weighted by a mass operator relative to an underlying space.

class pygeoinf.hilbert_space.DualHilbertSpace(space: HilbertSpace)[source]

Bases: HilbertSpace

A wrapper class representing the dual of a HilbertSpace.

An element of a dual space is a continuous linear functional, represented in this library by the LinearForm class. This wrapper provides a full HilbertSpace interface for these LinearForm objects, allowing them to be treated as vectors in their own right.

property dim: int

The dimension of the dual space.

property dual: HilbertSpace

The dual of the dual space, which is the original primal space.

final duality_product(xp: LinearForm, x: Vector) float[source]

Computes the duality product <x, xp>.

In this context, x is from the primal space and xp is the dual vector (a LinearForm). This is unconventional but maintains the method signature; it evaluates x(xp).

from_components(c: np.ndarray) LinearForm[source]

Creates a LinearForm from a NumPy component array.

from_dual(xp: Vector) LinearForm[source]

Maps a primal vector to its corresponding dual LinearForm.

is_element(x: Any) bool[source]

Checks if an object is a valid element of the dual space.

to_components(x: LinearForm) np.ndarray[source]

Maps a LinearForm to its NumPy component array.

to_dual(x: LinearForm) Any[source]

Maps a dual vector back to its representative in the primal space.

property underlying_space: HilbertSpace

The primal HilbertSpace of which this is the dual.

class pygeoinf.hilbert_space.EuclideanSpace(dim: int)[source]

Bases: HilbertSpace

An n-dimensional Euclidean space, R^n.

This is a concrete HilbertSpace where vectors are represented directly by NumPy arrays, and the inner product is the standard dot product.

property dim: int

The dimension of the space.

from_components(c: ndarray) ndarray[source]

Returns the component array itself, as it is the vector.

from_dual(xp: LinearForm) np.ndarray[source]

Maps a LinearForm back to a vector via its components.

inner_product(x1: ndarray, x2: ndarray) float[source]

Computes the inner product of two vectors.

Notes

Default implementation overrident for efficiency.

is_element(x: Any) bool[source]

Checks if an object is a valid element of the space.

subspace_projection(indices: int | List[int]) LinearOperator[source]

Returns a projection operator onto specified coordinates.

This creates a linear operator that extracts the components at the given indices, projecting from this space to a lower-dimensional Euclidean space.

Parameters:

indices – Single index or list of indices to project onto (0-indexed).

Returns:

LinearOperator from this space to EuclideanSpace(len(indices)).

Raises:

IndexError – If any index is out of range for this space’s dimension.

to_components(x: ndarray) ndarray[source]

Returns the vector itself, as it is already a component array.

to_dual(x: np.ndarray) LinearForm[source]

Maps a vector x to a LinearForm with the same components.

class pygeoinf.hilbert_space.HilbertModule[source]

Bases: HilbertSpace, ABC

An ABC for a HilbertSpace where vector multiplication is defined.

This acts as a “mixin” interface, adding the vector_multiply requirement to the HilbertSpace contract.

abstract vector_multiply(x1: Vector, x2: Vector) Vector[source]

Computes the product of two vectors.

Parameters:
  • x1 – The first vector.

  • x2 – The second vector.

Returns:

The product of the two vectors.

abstract vector_sqrt(x: Vector) Vector[source]

Returns the square root of a vector.

class pygeoinf.hilbert_space.HilbertSpace[source]

Bases: ABC, HilbertSpaceAxiomChecks

An abstract base class for real Hilbert spaces.

This class provides a mathematical abstraction for a vector space equipped with an inner product. It defines a formal interface that separates abstract vector operations from their concrete representation (e.g., as NumPy arrays). Subclasses must implement all abstract methods to be instantiable.

add(x: Vector, y: Vector) Vector[source]

Computes the sum of two vectors. Defaults to x + y.

ax(a: float, x: Vector) None[source]

Performs in-place scaling x := a*x. Defaults to x *= a.

axpy(a: float, x: Vector, y: Vector) None[source]

Performs in-place operation y := y + a*x. Defaults to y += a*x.

final basis_vector(i: int) Vector[source]

Returns the i-th standard basis vector.

This is the vector whose component array is all zeros except for a one at index i.

Parameters:

i – The index of the basis vector.

Returns:

The i-th basis vector.

property coordinate_inclusion: LinearOperator

The linear operator mapping R^n component vectors into this space.

property coordinate_projection: LinearOperator

The linear operator projecting vectors from this space to R^n.

copy(x: Vector) Vector[source]

Returns a deep copy of a vector. Defaults to x.copy().

abstract property dim: int

The finite dimension of the space.

property dual: HilbertSpace

The dual of this Hilbert space.

The dual space is the space of all continuous linear functionals (i.e., LinearForm objects) that map vectors from this space to real numbers. This implementation returns a DualHilbertSpace wrapper.

duality_product(xp: LinearForm, x: Vector) float[source]

Computes the duality product <xp, x>.

This evaluates the linear functional xp (an element of the dual space) at the vector x (an element of the primal space).

Parameters:
  • xp – The linear functional from the dual space.

  • x – The vector from the primal space.

Returns:

The result of the evaluation xp(x).

abstract from_components(c: ndarray) Vector[source]

Maps a NumPy component array back to a vector in the space.

Parameters:

c – The components of the vector as a NumPy array.

Returns:

The corresponding vector in the space.

abstract from_dual(xp: Any) Vector[source]

Maps a dual vector back to its representative in the primal space.

This is the inverse of the Riesz representation map defined by to_dual.

Parameters:

xp – A vector in the dual space.

Returns:

The corresponding vector in the primal space.

final gram_schmidt(vectors: List[Vector]) List[Vector][source]

Orthonormalizes a list of vectors using the Gram-Schmidt process.

Parameters:

vectors – A list of linearly independent vectors.

Returns:

A list of orthonormalized vectors spanning the same subspace.

Raises:

ValueError – If not all items in the list are elements of the space.

final identity_operator() LinearOperator[source]

Returns the identity operator I on the space.

inner_product(x1: Vector, x2: Vector) float[source]

Computes the inner product of two vectors, (x1, x2).

This is defined via the duality product as <R(x1), x2>, where R is the Riesz map (to_dual).

Parameters:
  • x1 – The first vector.

  • x2 – The second vector.

Returns:

The inner product as a float.

property inverse_riesz: LinearOperator

The inverse Riesz map (primal to dual) as a LinearOperator.

is_element(x: Any) bool[source]

Checks if an object is a valid element of the space.

Note: The default implementation checks the object’s type against the type of the zero vector. This may not be robust for all vector representations and can be overridden if needed.

Parameters:

x – The object to check.

Returns:

True if the object is an element of the space, False otherwise.

multiply(a: float, x: Vector) Vector[source]

Computes scalar multiplication. Defaults to a * x.

negative(x: Vector) Vector[source]

Computes the additive inverse of a vector. Defaults to -1 * x.

final norm(x: Vector) float[source]

Computes the norm of a vector, ||x||.

Parameters:

x – The vector.

Returns:

The norm of the vector.

random() Vector[source]

Generates a random vector from the space.

The vector’s components are drawn from a standard normal distribution.

Returns:

A new random vector.

property riesz: LinearOperator

The Riesz map (dual to primal) as a LinearOperator.

final sample_expectation(vectors: List[Vector]) Vector[source]

Computes the sample mean of a list of vectors.

Parameters:

vectors – A list of vectors from the space.

Returns:

The sample mean (average) vector.

Raises:

TypeError – If not all items in the list are elements of the space.

final squared_norm(x: Vector) float[source]

Computes the squared norm of a vector, ||x||^2.

Parameters:

x – The vector.

Returns:

The squared norm of the vector.

subtract(x: Vector, y: Vector) Vector[source]

Computes the difference of two vectors. Defaults to x - y.

abstract to_components(x: Vector) ndarray[source]

Maps a vector to its representation as a NumPy component array.

Parameters:

x – A vector in the space.

Returns:

The components of the vector as a NumPy array.

abstract to_dual(x: Vector) Any[source]

Maps a vector to its canonical dual vector (a linear functional).

This method, along with from_dual, defines the Riesz representation map and implicitly defines the inner product of the space.

Parameters:

x – A vector in the primal space.

Returns:

The corresponding vector in the dual space.

property zero: Vector

The zero vector (additive identity) of the space.

final zero_operator(codomain: HilbertSpace | None = None) LinearOperator[source]

Returns the zero operator 0 from this space to a codomain.

Parameters:

codomain – The target space of the operator. If None, the operator maps to this space itself.

Returns:

The zero linear operator.

class pygeoinf.hilbert_space.MassWeightedHilbertModule(underlying_space: HilbertModule, mass_operator: LinearOperator, inverse_mass_operator: LinearOperator)[source]

Bases: MassWeightedHilbertSpace, HilbertModule

A mass-weighted Hilbert space that also supports vector multiplication.

This class inherits the mass-weighted inner product structure and mixes in the HilbertModule interface, delegating the multiplication operation to the underlying space.

vector_multiply(x1: Vector, x2: Vector) Vector[source]

Computes vector multiplication by delegating to the underlying space.

Note: This assumes the underlying space provided during initialization is itself an instance of HilbertModule.

vector_sqrt(x: Vector) Vector[source]

Computes vector multiplication by delegating to the underlying space.

Note: This assumes the underlying space provided during initialization is itself an instance of HilbertModule.

class pygeoinf.hilbert_space.MassWeightedHilbertSpace(underlying_space: HilbertSpace, mass_operator: LinearOperator, inverse_mass_operator: LinearOperator)[source]

Bases: HilbertSpace

A Hilbert space with an inner product weighted by a mass operator.

This class wraps an existing HilbertSpace (let’s call it X) and defines a new inner product for a space (Y) as: (u, v)_Y = (M @ u, v)_X, where M is a self-adjoint, positive-definite mass operator defined on X.

This is a common construction in numerical methods like the Finite Element Method, where the basis functions are not orthonormal.

add(x: Vector, y: Vector) Vector[source]

Computes the sum of two vectors. Defaults to x + y.

ax(a: float, x: Vector) None[source]

Performs in-place scaling x := a*x. Defaults to x *= a.

axpy(a: float, x: Vector, y: Vector) None[source]

Performs in-place operation y := y + a*x. Defaults to y += a*x.

copy(x: Vector) Vector[source]

Returns a deep copy of a vector. Defaults to x.copy().

property dim: int

The dimension of the space.

from_components(c: ndarray) Vector[source]

Delegates vector creation to the underlying space.

from_dual(xp: LinearForm) Vector[source]

Computes the inverse dual mapping R_Y^{-1}(xp) = M^{-1} R_X^{-1}(xp).

inner_product(x1: Vector, x2: Vector) float[source]

Computes the inner product of two vectors.

Notes

Default implementation overrident for efficiency.

property inverse_mass_operator: LinearOperator

The inverse of the mass operator.

is_element(x: Any) bool[source]

Checks if an object is a valid element of the space.

property mass_operator: LinearOperator

The mass operator (M) defining the weighted inner product.

multiply(a: float, x: Vector) Vector[source]

Computes scalar multiplication. Defaults to a * x.

negative(x: Vector) Vector[source]

Computes the additive inverse of a vector. Defaults to -1 * x.

subtract(x: Vector, y: Vector) Vector[source]

Computes the difference of two vectors. Defaults to x - y.

to_components(x: Vector) ndarray[source]

Delegates component mapping to the underlying space.

to_dual(x: Vector) LinearForm[source]

Computes the dual mapping R_Y(x) = R_X(M x).

property underlying_space: HilbertSpace

The underlying Hilbert space (X) without mass weighting.

property zero: Vector

The zero vector (additive identity) of the space.

pygeoinf.inversion module

Provides the abstract base class for all inversion algorithms.

This module defines the Inversion class, which serves as a common foundation for various methods that solve an inverse problem. Its primary role is to maintain a reference to the ForwardProblem being solved, providing a consistent interface and convenient access to the problem’s core components like the model space and data space.

It also includes helper methods to assert preconditions required by different inversion techniques, such as the existence of a data error measure.

class pygeoinf.inversion.Inference(forward_problem: ForwardProblem, property_operator: NonLinearOperator)[source]

Bases: Inversion

A base class for inference algorithms. These methods inherit common functionality from the inversion base class, but need not themselves derive from a specific inversion scheme.

Within an inference problem, the aim is to estimate some property of the unknown model, and hence a property operator mapping from the model to a property space must be specified.

property property_operator: NonLinearOperator

Returns the property operator.

property property_space: HilbertSpace

Returns the property space.

class pygeoinf.inversion.Inversion(forward_problem: ForwardProblem, /)[source]

Bases: object

A base class for inversion methods.

This class provides a common structure for different inversion and inference algorithms (e.g., Bayesian, Least Squares). Its main purpose is to hold a reference to the forward problem being solved and provide convenient access to its properties. Subclasses should inherit from this class to implement a specific inversion algorithm.

assert_data_error_measure() None[source]

Checks if a data error measure is set in the forward problem.

This is a precondition for statistical inversion methods.

Raises:

AttributeError – If no data error measure has been set.

assert_inverse_data_covariance() None[source]

Checks if the data error measure has an inverse covariance.

This is a precondition for methods that require the data precision matrix (the inverse of the data error covariance).

Raises:

AttributeError – If no data error measure is set, or if the measure does not have an inverse covariance operator defined.

property data_space: HilbertSpace

The data space (codomain) of the forward problem.

property forward_problem: ForwardProblem

The forward problem associated with this inversion.

property model_space: HilbertSpace

The model space (domain) of the forward problem.

class pygeoinf.inversion.LinearInference(forward_problem: LinearForwardProblem, property_operator: LinearOperator)[source]

Bases: Inference

A base class for linear inference algorithms.

class pygeoinf.inversion.LinearInversion(forward_problem: LinearForwardProblem, /, *, formalism: Literal['model_space', 'data_space'] = 'data_space')[source]

Bases: Inversion

An abstract base class for linear inversion algorithms.

data_measure_from_model(model: Vector) GaussianMeasure[source]

Returns the Gaussian measure for the data, given a specific model.

data_measure_from_model_measure(model_measure: GaussianMeasure) GaussianMeasure[source]

Given a measure for the model space, returns the induced measure on the data space.

data_reduced_inversion(reduction_operator: LinearOperator, /, *, reduced_data_error_measure: GaussianMeasure | None = None, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) LinearInversion[source]

Constructs a surrogate of the linear inversion using a reduced data space.

Parameters:
  • reduction_operator – A LinearOperator mapping from the current data space to the new, reduced data space.

  • reduced_data_error_measure – An optional data error measure defined on the reduced data space.

  • dense – If True, computes and stores operators as dense matrices.

  • parallel – If True, computes the dense matrices in parallel.

  • n_jobs – Number of CPU cores to use. -1 means all available.

  • formalism – An optional override for the formalism of the new inversion.

Returns:

A new instance of the concrete inversion class operating on the reduced data space.

property formalism: Literal['model_space', 'data_space']

The algebraic space in which the normal equations are assembled and solved.

joint_measure(model_measure: GaussianMeasure) GaussianMeasure[source]

Given a measure for the model space, returns the joint measure for the model and data.

parameterized_inversion(parameterization: LinearOperator, /, *, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) LinearInversion[source]

Constructs a parameterized surrogate of the linear inversion.

Parameters:
  • parameterization – A LinearOperator mapping from the parameter space to the full model space.

  • dense – If True, computes and stores the parameterized forward operator as a dense matrix in memory.

  • parallel – If True, computes the dense matrix in parallel.

  • n_jobs – Number of CPU cores to use. -1 means all available.

  • formalism – An optional override for the formalism of the new inversion. If None, inherits the formalism of the parent inversion.

Returns:

A new instance of the concrete inversion class operating on the parameter space.

with_formalism(formalism: Literal['model_space', 'data_space']) LinearInversion[source]

Returns a new instance of the inversion using the specified formalism.

pygeoinf.linear_bayesian module

Implements the Bayesian framework for solving linear inverse problems.

This module treats the inverse problem from a statistical perspective. Rather than seeking a single deterministic “best-fit” solution, it aims to determine the full posterior probability distribution of the unknown model parameters given the observed data, prior knowledge, and noise statistics.

A core feature of this module is its dual algebraic formalism, allowing users to optimize computational efficiency based on the problem geometry:

  • data_space: Assembles the data-space normal operator (size M x M, where M is the data dimension). Normal Operator: N = A Q A* + R Kalman Gain: K = Q A* N^-1 Best suited for underdetermined problems (M << N).

  • model_space: Assembles the model-space normal operator (size N x N, where N is the model dimension). Normal Operator: N = Q^-1 + A* R^-1 A Kalman Gain: K = N^-1 A* R^-1 Best suited for overdetermined problems (N << M).

Key Classes

  • LinearBayesianInversion: Computes the posterior Gaussian measure p(u|d) for the model u given observed data d.

class pygeoinf.linear_bayesian.LinearBayesianInversion(forward_problem: LinearForwardProblem, model_prior_measure: GaussianMeasure, /, *, formalism: Literal['model_space', 'data_space'] = 'data_space')[source]

Bases: LinearInversion

Solves a linear inverse problem using Bayesian methods.

This class applies to problems of the form d = A(u) + e, where u is a Gaussian random variable representing the model prior, and e is a Gaussian random variable representing observation noise.

It computes the exact posterior Gaussian measure p(u|d), providing access to the posterior expectation, the posterior covariance operator, and an efficient exact-sampling mechanism using the randomize-then-optimize technique.

property data_prior_measure: GaussianMeasure

The prior predictive distribution on the data space. This represents the expected distribution of data before observation.

data_reduced_inversion(reduction_operator: LinearOperator, /, *, reduced_data_error_measure: GaussianMeasure | None = None, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) LinearBayesianInversion[source]

Constructs a surrogate of the Bayesian inversion using a reduced data space.

Parameters:
  • reduction_operator – A LinearOperator mapping from the current data space to the new, reduced data space.

  • reduced_data_error_measure – An optional data error measure defined on the reduced data space.

  • dense – If True, computes and stores operators as dense matrices.

  • parallel – If True, computes the dense matrices in parallel.

  • n_jobs – Number of CPU cores to use. -1 means all available.

  • formalism – An optional override for the formalism of the new inversion.

Returns:

A new LinearBayesianInversion instance operating on the reduced data space.

diagonal_normal_preconditioner(*, blocks: List[List[int]] | None = None, parallel: bool = False, n_jobs: int = -1) LinearOperator[source]

Constructs a diagonal preconditioner specifically for the data-space Bayesian normal operator (A Q A* + R).

This exploits the identity <v, A Q A* v> = <A* v, Q A* v>. If blocks of data indices are provided, it acts on the averaged basis vector for each block to compute a robust representative regional variance, requiring only one adjoint action of the forward operator per block.

Parameters:
  • blocks – An optional list of lists, where each sub-list contains indices of data points grouped together. Must perfectly partition the data space.

  • parallel – If True, computes the adjoint actions in parallel.

  • n_jobs – Number of parallel jobs to use. -1 means all available cores.

Returns:

A DiagonalSparseMatrixLinearOperator representing the inverse of the approximated normal operator.

Raises:

ValueError – If the inversion was initialized with formalism=’model_space’, as this preconditioner is mathematically invalid for that normal operator.

estimate_log_determinant(*, operator_type: Literal['data_space', 'model_space'] = 'data_space', size_estimate: int = 10, method: Literal['variable', 'fixed'] = 'variable', max_samples: int | None = None, rtol: float = 0.01, block_size: int = 5, lanczos_degree: int = 40, lanczos_rtol: float | None = 0.001, parallel: bool = False, n_jobs: int = -1) float[source]

Estimates the log-determinant of the Bayesian normal operator using Stochastic Lanczos Quadrature (SLQ).

This acts as a public interface for computing the log-determinant of either the data-space normal operator (A Q A* + R) or the model-space normal operator (Q^-1 + A* R^-1 A). It securely resolves the correct algebraic space and delegates to the internal matrix-free SLQ engine.

Parameters:
  • operator_type – The target normal operator (‘data_space’ or ‘model_space’).

  • size_estimate – Initial number of Hutchinson samples (probe vectors).

  • method – ‘variable’ to sample until ‘rtol’ is met, ‘fixed’ otherwise.

  • max_samples – Hard limit on the number of Hutchinson samples.

  • rtol – Relative tolerance for the Hutchinson trace estimate.

  • block_size – Number of new samples per iteration in the ‘variable’ method.

  • lanczos_degree – Maximum Krylov dimension (k) per probe vector.

  • lanczos_rtol – Relative tolerance for dynamic Lanczos truncation. If None, uses fixed ‘lanczos_degree’ steps.

  • parallel – If True, evaluates probe vectors in parallel.

  • n_jobs – Number of CPU cores to use if parallel=True.

Returns:

The estimated log-determinant ln(|N|).

Return type:

float

get_normal_equations_rhs(data: Vector) Vector[source]

Computes the exact right-hand side vector (v) of the normal equations N * w = v for a given observed data vector, automatically accounting for non-zero prior and noise expectations.

property joint_prior_measure: GaussianMeasure

The joint prior distribution of both the model and the data.

kalman_operator(solver: LinearSolver, /, *, preconditioner: LinearOperator | None = None) LinearOperator[source]

Constructs the Kalman gain operator K.

The Kalman gain maps data residuals to model space updates.

For ‘data_space’: K = Q A* (A Q A* + R)^-1 For ‘model_space’: K = (Q^-1 + A* R^-1 A)^-1 A* R^-1

Parameters:
  • solver – The LinearSolver used to invert the normal operator.

  • preconditioner – Optional preconditioner for iterative solvers.

Returns:

A LinearOperator representing the Kalman gain.

log_evidence(data: Vector, solver: LinearSolver, /, *, preconditioner: LinearOperator | None = None, size_estimate: int = 10, method: Literal['variable', 'fixed'] = 'variable', max_samples: int | None = None, rtol: float = 0.01, block_size: int = 5, lanczos_degree: int = 40, lanczos_rtol: float | None = 0.001, parallel: bool = False, n_jobs: int = -1) float[source]

Computes the approximate log marginal likelihood (evidence) of the data, ln(p(d)).

The log-evidence is a critical metric for Bayesian model selection, allowing users to quantitatively compare different prior assumptions or forward models. It evaluates the likelihood of the observed data marginalized over all possible model states.

The computation evaluates the Gaussian marginal density equation:

ln p(d) = -0.5 * [ ln|N_d| + <v, N_d^-1 v> + M * ln(2*pi) ]

Parameters:
  • data – The observed data vector ‘d’.

  • solver – The LinearSolver used to invert the normal operator for the Mahalanobis misfit term.

  • preconditioner – Optional preconditioner for the iterative solver.

  • size_estimate – Initial number of SLQ Hutchinson samples.

  • method – ‘variable’ to dynamically bound SLQ error, ‘fixed’ otherwise.

  • max_samples – Hard limit on SLQ Hutchinson samples.

  • rtol – Relative tolerance for the SLQ trace estimate.

  • block_size – Number of new SLQ samples per iteration.

  • lanczos_degree – Maximum Krylov dimension for SLQ.

  • lanczos_rtol – Relative tolerance for dynamic Lanczos truncation.

  • parallel – If True, evaluates SLQ probe vectors in parallel.

  • n_jobs – Number of CPU cores to use if parallel=True.

Returns:

The estimated log-evidence ln(p(d)).

Return type:

float

Raises:

ValueError – If the ‘model_space’ formalism is used but no data error measure has been set on the forward problem.

low_rank_surrogate(*, forward_rank: int | None = None, prior_rank: int | None = None, data_error_rank: int | None = None, forward_kwargs: dict | None = None, prior_kwargs: dict | None = None, data_error_kwargs: dict | None = None) LinearBayesianInversion[source]

Constructs a surrogate Bayesian inversion problem by replacing the exact physics and statistical measures with their low-rank approximations.

This method generates computationally cheap surrogate models to be used in constructing preconditioners for massive, ill-conditioned inverse problems (e.g., using spectral or banded methods). The low-rank approximations are computed using randomized SVD and eigendecomposition algorithms.

Parameters:
  • forward_rank – Target rank for the randomized SVD of the forward operator.

  • prior_rank – Target rank for the randomized eigendecomposition of the prior.

  • data_error_rank – Target rank for the randomized eigendecomposition of the noise.

  • forward_kwargs – Additional kwargs passed directly to LinearOperator.random_svd.

  • prior_kwargs – Additional kwargs passed directly to GaussianMeasure.low_rank_approximation.

  • data_error_kwargs – Additional kwargs passed directly to GaussianMeasure.low_rank_approximation.

Returns:

A LinearBayesianInversion representing the low-rank surrogate problem.

mahalanobis_evidence_term(data: Vector, solver: LinearSolver, /, *, preconditioner: LinearOperator | None = None) float[source]

Computes the data-dependent Mahalanobis term of the log-evidence.

This value represents the optimal, penalty-balanced data misfit. It is equivalent to the unnormalized log-posterior evaluated at the posterior expectation.

Mathematically, for a shifted data residual vector v = d - A(mu_u) - mu_e, the term evaluates the quadratic form:

Misfit = <v, (A Q A* + R)^-1 v>

In the ‘model_space’ formalism, this is computed far more efficiently using the Woodbury matrix identity to bypass the massive data-space inversion:

Misfit = <v, R^-1 v> - <A* R^-1 v, (Q^-1 + A* R^-1 A)^-1 A* R^-1 v>

Parameters:
  • data – The observed data vector ‘d’.

  • solver – The LinearSolver used to invert the normal operator.

  • preconditioner – An optional preconditioner to accelerate iterative solvers.

Returns:

The scalar Mahalanobis distance.

Return type:

float

Raises:

ValueError – If the forward problem lacks a defined data error measure.

model_posterior_measure(data: Vector, solver: LinearSolver, /, *, preconditioner: LinearOperator | None = None) GaussianMeasure[source]

Computes and returns the posterior Gaussian measure p(u|d).

This method applies the Kalman update equations to find the posterior expectation and covariance. If both the prior and data error measures have sampling enabled, it automatically constructs a randomize-then-optimize exact sampling function for the posterior.

Parameters:
  • data – The observed data vector.

  • solver – A linear solver for inverting the normal operator.

  • preconditioner – An optional preconditioner for iterative solvers.

Returns:

A GaussianMeasure representing the posterior distribution.

property model_prior_measure: GaussianMeasure

The prior Gaussian measure on the model space.

property normal_operator: LinearOperator

Constructs the Bayesian Normal operator for the chosen formalism.

For ‘data_space’: Returns N = A Q A* + R For ‘model_space’: Returns N = Q^-1 + A* R^-1 A

Returns:

A LinearOperator representing the normal equations matrix.

normal_residual_callback(data: Vector, /, *, message: str = 'CG Iteration: {iter} | Normal Residual: {res:.3e}', print_progress: bool = True) ResidualTrackingCallback[source]

Generates a ResidualTrackingCallback pre-configured to track the convergence of the Bayesian normal equations for the given data vector.

Parameters:
  • data – The observed data vector.

  • message – The formatting string for printing progress.

  • print_progress – If True, prints the message to stdout at each iteration.

Returns:

A configured ResidualTrackingCallback ready to be passed to a solver.

parameterized_inversion(parameterization: LinearOperator, /, *, parameter_prior: GaussianMeasure | None = None, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) LinearBayesianInversion[source]

Constructs a parameterized surrogate of the Bayesian inversion.

If the target formalism resolves to ‘model_space’ (which is typical for parameterized inversions), the parameter prior’s covariance matrix will be automatically densified to explicitly compute the required precision (inverse covariance) operator.

Parameters:
  • parameterization – A LinearOperator mapping from the parameter space to the full model space.

  • parameter_prior – An optional prior measure on the parameter space. If not provided, the original model prior is pulled back to the parameter space automatically.

  • dense – If True, computes and stores operators as dense matrices.

  • parallel – If True, computes the dense matrices in parallel.

  • n_jobs – Number of CPU cores to use. -1 means all available.

  • formalism – An optional override for the formalism of the new inversion. If None, inherits the formalism of the parent inversion.

Returns:

A new LinearBayesianInversion instance operating on the parameter space.

posterior_expectation_operator(solver: LinearSolver, /, *, preconditioner: LinearOperator | None = None) LinearOperator | AffineOperator[source]

Constructs the operator mapping observed data to the posterior expectation.

The mapping evaluates F(d) = mu_u + K(d - A(mu_u) - mu_e).

If the prior and data error measures both have a zero expectation, this mapping is purely linear and returns the Kalman gain operator directly. Otherwise, it regroups the terms into an AffineOperator: F(d) = K(d) + (mu_u - K(A(mu_u) + mu_e)).

Parameters:
  • solver – The LinearSolver used to invert the normal operator.

  • preconditioner – Optional preconditioner for iterative solvers.

Returns:

A LinearOperator (if expectations are zero) or an AffineOperator.

sparse_localized_preconditioner(interacting_blocks: list[list[int]], rank: int = 10, parallel: bool = False, n_jobs: int = -1) LinearOperator[source]

Builds a sparse preconditioner specifically for the data-space Bayesian normal equations using randomized Nystrom approximations on localized, potentially overlapping sub-blocks.

Parameters:
  • interacting_blocks – A list of lists, where each sub-list contains the indices of data points that strongly couple to each other.

  • rank – The rank of the randomized Nystrom approximation to use per block.

  • parallel – If True, computes the sub-block approximations in parallel.

  • n_jobs – Number of CPU cores to use if parallel=True (-1 uses all cores).

Returns:

A LinearOperator representing the inverse of the sparse approximation.

Raises:

ValueError – If the inversion was initialized with formalism=’model_space’, as this preconditioner is mathematically invalid for that normal operator.

surrogate_inversion(*, alternate_forward_operator: LinearOperator | None = None, alternate_prior_measure: GaussianMeasure | None = None, alternate_data_error_measure: GaussianMeasure | None = None) LinearBayesianInversion[source]

Constructs a surrogate Bayesian inversion problem using simplified physics, priors, or data errors.

This is primarily used to construct robust, computationally cheap surrogate models to use as preconditioners for the full, complex inverse problem.

Parameters:
  • alternate_forward_operator – An optional simplified forward operator.

  • alternate_prior_measure – An optional simplified prior measure.

  • alternate_data_error_measure – An optional simplified data error measure.

Returns:

A new LinearBayesianInversion instance representing the surrogate problem. The surrogate inherits the formalism of the parent problem.

Raises:

ValueError – If the alternative operators/measures exist in incompatible domains/codomains.

surrogate_normal_preconditioner(solver: LinearSolver, /, *, alternate_forward_operator: LinearOperator | None = None, alternate_prior_measure: GaussianMeasure | None = None, alternate_data_error_measure: GaussianMeasure | None = None) LinearOperator[source]

Builds a preconditioner by exactly inverting the normal operator of a simplified surrogate inverse problem.

Parameters:
  • solver – The LinearSolver to use to exactly invert the surrogate normal operator.

  • alternate_forward_operator – An optional simplified forward operator.

  • alternate_prior_measure – An optional simplified prior measure.

  • alternate_data_error_measure – An optional simplified data error measure.

Returns:

A LinearOperator representing the inverse of the surrogate normal equations.

surrogate_woodbury_data_preconditioner(solver: LinearSolver, /, *, alternate_forward_operator: LinearOperator | None = None, alternate_prior_measure: GaussianMeasure | None = None, alternate_data_error_measure: GaussianMeasure | None = None, prior_solver: LinearSolver | None = None, noise_solver: LinearSolver | None = None) LinearOperator[source]

Builds a data-space preconditioner by applying the Woodbury matrix identity to a simplified surrogate inverse problem.

This method chains the construction of the surrogate model with the extraction of the Woodbury inverse in one step.

Note

Ensure that any alternate measures provided have well-defined inverse covariances, or use .with_regularized_inverse() on them before passing them to this method.

Parameters:
  • solver – The LinearSolver used to invert the inner Woodbury operator.

  • alternate_forward_operator – An optional simplified forward operator.

  • alternate_prior_measure – An optional simplified prior measure.

  • alternate_data_error_measure – An optional simplified data error measure.

  • prior_solver – Optional solver for the prior covariance.

  • noise_solver – Optional solver for the noise covariance.

Returns:

A LinearOperator representing the Woodbury-approximated inverse.

surrogate_woodbury_model_preconditioner(solver: LinearSolver, /, *, alternate_forward_operator: LinearOperator | None = None, alternate_prior_measure: GaussianMeasure | None = None, alternate_data_error_measure: GaussianMeasure | None = None) LinearOperator[source]

Builds a model-space preconditioner by applying the Woodbury matrix identity to a simplified surrogate inverse problem.

Parameters:
  • solver – The LinearSolver used to invert the inner Woodbury operator.

  • alternate_forward_operator – An optional simplified forward operator.

  • alternate_prior_measure – An optional simplified prior measure.

  • alternate_data_error_measure – An optional simplified data error measure.

Returns:

A LinearOperator representing the Woodbury-approximated inverse.

with_formalism(formalism: Literal['model_space', 'data_space']) LinearBayesianInversion[source]

Returns a new instance of the Bayesian inversion using the specified formalism.

Parameters:

formalism – The algebraic space in which the normal equations should be assembled and solved. Must be ‘model_space’ or ‘data_space’.

Returns:

A new LinearBayesianInversion instance sharing the exact same forward problem and prior measure, but configured to use the specified formalism.

woodbury_data_preconditioner(solver: LinearSolver, /, *, prior_solver: LinearSolver | None = None, noise_solver: LinearSolver | None = None) LinearOperator[source]

Constructs a data-space preconditioner using the Woodbury matrix identity.

Data Space Normal Operator: N_d = A Q A* + R Woodbury Identity: N_d^-1 = R^-1 - R^-1 A (Q^-1 + A* R^-1 A)^-1 A* R^-1

Note

This method assumes the prior (Q) and noise (R) measures either already have well-defined inverse covariances, or are well-conditioned enough to be inverted by the provided solvers. If your prior is an unbounded operator on a function space, use Q.with_regularized_inverse() before passing it to the inversion.

Parameters:
  • solver – The LinearSolver used to invert the inner Woodbury operator (N_m).

  • prior_solver – An optional solver used to explicitly invert the prior covariance (Q) if its inverse is not already set. Defaults to solver if not provided.

  • noise_solver – An optional solver used to explicitly invert the data error covariance (R) if its inverse is not already set. Defaults to solver if not provided.

Returns:

A LinearOperator representing the Woodbury-approximated inverse.

woodbury_model_preconditioner(solver: LinearSolver, /) LinearOperator[source]

Constructs a model-space preconditioner using the Woodbury matrix identity.

Model Space Normal Operator: N_m = Q^-1 + A* R^-1 A Woodbury Identity: N_m^-1 = Q - Q A* (R + A Q A*)^-1 A Q

Note

Unlike the data-space Woodbury identity, this formulation does not require evaluating the explicit inverses of the prior (Q) or noise (R) covariances, making it highly robust for unbounded or complex measures.

Parameters:

solver – The LinearSolver used to invert the inner Woodbury operator (N_d).

Returns:

A LinearOperator representing the Woodbury-approximated inverse.

pygeoinf.linear_forms module

Provides the LinearForm class for representing linear functionals.

A linear form is a linear mapping from a vector in a Hilbert space to a scalar. This class provides a concrete, component-based representation for elements of the dual space of a HilbertSpace. It inherits from NonLinearForm, specializing it for the linear case.

class pygeoinf.linear_forms.LinearForm(domain: HilbertSpace, /, *, components: np.ndarray | None = None, mapping: Callable[[Vector], float] | None = None, parallel: bool = False, n_jobs: int = -1)[source]

Bases: NonLinearForm

Represents a linear form as an efficient, component-based functional.

A LinearForm is an element of a dual HilbertSpace and is defined by its action on vectors from its domain. Internally, this action is represented by a component vector. This class provides optimized arithmetic operations and correctly defines the gradient (a constant vector) and the Hessian (the zero operator) for any linear functional.

property as_linear_operator: LinearOperator

Represents the linear form as a LinearOperator.

The resulting operator maps from the form’s original domain to a 1-dimensional EuclideanSpace, where the single component of the output is the scalar result of the form’s action.

property components: ndarray

The component vector of the form.

copy() LinearForm[source]

Creates a deep copy of the linear form.

property domain: HilbertSpace

The Hilbert space on which the form is defined.

static from_linear_operator(operator: LinearOperator) LinearForm[source]

Creates a LinearForm from an operator that maps to a 1D Euclidean space.

pygeoinf.linear_operators module

Provides classes for linear operators between Hilbert spaces.

This module is the primary tool for defining and manipulating linear mappings between HilbertSpace objects. It provides a powerful LinearOperator class that supports a rich algebra and includes numerous factory methods for convenient construction from matrices, forms, or tensor products.

Key Classes

  • LinearOperator: The main workhorse for linear algebra. It represents a linear map L(x) = Ax and provides rich functionality, including composition (@), adjoints (.adjoint), duals (.dual), and matrix representations (.matrix).

  • DiagonalLinearOperator: A specialized, efficient implementation for linear operators that are diagonal in their component representation, notable for supporting functional calculus (e.g., .inverse, .sqrt).

class pygeoinf.linear_operators.DenseMatrixLinearOperator(domain: HilbertSpace, codomain: HilbertSpace, matrix: np.ndarray, /, *, galerkin=False)[source]

Bases: MatrixLinearOperator

A specialisation of the MatrixLinearOperator class to instances where the matrix representation is always provided as a numpy array.

This is a class provides some additional methods for component-wise access.

static from_linear_operator(operator: LinearOperator, /, *, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) DenseMatrixLinearOperator[source]

Converts a LinearOperator into a DenseMatrixLinearOperator by forming its dense matrix representation.

Parameters:
  • operator – The operator to be converted.

  • galerkin – If True, the Galerkin representation is used. Default is False.

  • parallel – If True, dense matrix calculation is done in parallel. Default is False.

  • n_jobs – Number of jobs used for parallel calculations. Default is False.

class pygeoinf.linear_operators.DiagonalSparseMatrixLinearOperator(domain: HilbertSpace, codomain: HilbertSpace, diagonals: Tuple[np.ndarray, List[int]], /, *, galerkin: bool = False)[source]

Bases: SparseMatrixLinearOperator

A highly specialized operator for matrices defined purely by a set of non-zero diagonals.

This class internally stores the operator using a scipy.sparse.dia_array for maximum efficiency in storage and matrix-vector products. It provides extremely fast methods for extracting diagonals, as this is its native storage format.

A key feature of this class is its support for functional calculus. It dynamically proxies element-wise mathematical functions (e.g., .sqrt(), .log(), abs(), **) to the underlying sparse array. For reasons of mathematical correctness, these operations are restricted to operators that are strictly diagonal (i.e., have only a non-zero main diagonal) and will raise a NotImplementedError otherwise.

Aggregation methods that do not return a new operator (e.g., .sum()) are not restricted and can be used on any multi-diagonal operator.

Class Methods

from_diagonal_values:

Constructs a strictly diagonal operator from a 1D array of values.

from_operator:

Creates a diagonal approximation of another LinearOperator.

Properties

offsets:

The array of stored diagonal offsets.

is_strictly_diagonal:

True if the operator only has a non-zero main diagonal.

inverse:

The inverse of a strictly diagonal operator.

sqrt:

The square root of a strictly diagonal operator.

apply_function(func: str | Callable[[ndarray], ndarray]) DiagonalSparseMatrixLinearOperator[source]

Applies a function to the diagonal values (eigenvalues) of the operator.

This supports functional calculus for strictly diagonal operators. For maximum performance, pass a NumPy ufunc (e.g., np.sqrt, np.exp).

Parameters:

func – A callable function, or the string name of a SciPy sparse method.

extract_diagonals(offsets: List[int], /, *, galerkin: bool = True, parallel: bool = False, n_jobs: int = -1) Tuple[ndarray, List[int]][source]

Overrides the base method for extreme efficiency.

This operation is nearly free, as it involves selecting the requested diagonals from the data already stored in the native format.

classmethod from_diagonal_values(domain: HilbertSpace, codomain: HilbertSpace, diagonal_values: np.ndarray, /, *, galerkin: bool = False) DiagonalSparseMatrixLinearOperator[source]

Constructs a purely diagonal operator from a 1D array of values.

This provides a convenient way to create an operator with non-zero entries only on its main diagonal (offset k=0).

Parameters:
  • domain – The domain of the operator.

  • codomain – The codomain of the operator. Must have the same dimension.

  • diagonal_values – A 1D NumPy array of the values for the main diagonal.

  • galerkin – If True, the operator is in Galerkin form.

Returns:

A new DiagonalSparseMatrixLinearOperator.

classmethod from_operator(operator: LinearOperator, offsets: List[int], /, *, galerkin: bool = True) DiagonalSparseMatrixLinearOperator[source]

Creates a diagonal approximation of another LinearOperator.

This factory method works by calling the source operator’s .extract_diagonals() method and using the result to construct a new, highly efficient DiagonalSparseMatrixLinearOperator.

Parameters:
  • operator – The source operator to approximate.

  • offsets – The list of diagonal offsets to extract and keep.

  • galerkin – Specifies which matrix representation to use.

Returns:

A new DiagonalSparseMatrixLinearOperator.

property inverse: DiagonalSparseMatrixLinearOperator

The inverse of the operator, computed via functional calculus. Requires the operator to be strictly diagonal with no zero entries.

property is_strictly_diagonal: bool

True if the operator only has a non-zero main diagonal (offset=0).

property offsets: ndarray

Returns the array of stored diagonal offsets.

property sqrt: DiagonalSparseMatrixLinearOperator

The square root of the operator, computed via functional calculus. Requires the operator to be strictly diagonal with non-negative entries.

class pygeoinf.linear_operators.LinearOperator(domain: HilbertSpace, codomain: HilbertSpace, mapping: Callable[[Any], Any], /, *, dual_mapping: Callable[[Any], Any] | None = None, adjoint_mapping: Callable[[Any], Any] | None = None, dual_base: LinearOperator | None = None, adjoint_base: LinearOperator | None = None)[source]

Bases: NonLinearOperator, LinearOperatorAxiomChecks

A linear operator between two Hilbert spaces.

This class represents a linear map L(x) = Ax and provides rich functionality for linear algebraic operations. It specializes NonLinearOperator, with the derivative mapping taking the required form (i.e., the derivative is just the operator itself).

Key features include operator algebra (@, +, *), automatic derivation of adjoint (.adjoint) and dual (.dual) operators, and multiple matrix representations (.matrix()) for use with numerical solvers.

property adjoint: LinearOperator

The adjoint of the operator.

property dual: LinearOperator

The dual of the operator.

extract_diagonal(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) ndarray[source]

Computes the main diagonal of the operator’s matrix representation.

This method is highly parallelizable and memory-efficient, as it avoids forming the full dense matrix.

Parameters:
  • galerkin – If True, computes the diagonal of the Galerkin matrix.

  • parallel – If True, computes the entries in parallel.

  • n_jobs – Number of parallel jobs to use.

Returns:

A NumPy array containing the diagonal entries.

extract_diagonals(offsets: List[int], /, *, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) Tuple[ndarray, List[int]][source]

Computes specified diagonals of the operator’s matrix representation.

This is a memory-efficient and parallelizable method that computes the matrix one column at a time.

Parameters:
  • offsets – A list of diagonal offsets to extract (e.g., [0] for the main diagonal, [-1, 0, 1] for a tridiagonal matrix).

  • galerkin – If True, computes the diagonals of the Galerkin matrix.

  • parallel – If True, computes columns in parallel.

  • n_jobs – Number of parallel jobs to use.

Returns:

  • A NumPy array where each row is a diagonal.

  • The list of offsets.

This format is compatible with scipy.sparse.spdiags.

Return type:

A tuple containing

static from_formal_adjoint(domain: HilbertSpace, codomain: HilbertSpace, operator: LinearOperator) LinearOperator[source]

Constructs an operator on weighted spaces from one on the underlying spaces.

This is a key method for working with MassWeightedHilbertSpace. It takes an operator A that is defined on the simple, unweighted underlying spaces and “lifts” it to be a proper operator on the mass-weighted spaces. It correctly defines the new operator’s adjoint with respect to the weighted inner products.

This method automatically handles cases where the domain and/or codomain are a HilbertSpaceDirectSum, recursively building the necessary block-structured mass operators.

Parameters:
  • domain – The (potentially) mass-weighted domain of the new operator.

  • codomain – The (potentially) mass-weighted codomain of the new operator.

  • operator – The original operator defined on the underlying, unweighted spaces.

Returns:

A new LinearOperator that acts between the mass-weighted spaces.

static from_formally_self_adjoint(domain: HilbertSpace, operator: LinearOperator) LinearOperator[source]

Constructs a self-adjoint operator on a weighted space.

This method takes an operator that is formally self-adjoint on an underlying (unweighted) space and promotes it to a truly self-adjoint operator on the MassWeightedHilbertSpace. It automatically handles HilbertSpaceDirectSum domains.

Parameters:
  • domain (HilbertSpace) – The domain of the operator, which can be a MassWeightedHilbertSpace or a HilbertSpaceDirectSum.

  • operator (LinearOperator) – The operator to be converted.

static from_linear_form(form: LinearForm) LinearOperator[source]

Creates a rank-1 LinearOperator from a single LinearForm.

The resulting operator maps from the form’s domain to a 1-dimensional Euclidean space.

The forward mapping evaluates the form: A(x) = [form(x)]. The dual mapping scales the form: A’(y’) = y’_0 * form. The adjoint mapping is handled automatically by the base class.

Parameters:

form – A LinearForm representing a continuous linear functional.

Returns:

A LinearOperator mapping from ‘form.domain’ to EuclideanSpace(1).

static from_linear_forms(forms: List[LinearForm]) LinearOperator[source]

Creates an operator from a list of linear forms.

The resulting operator maps from the forms’ domain to an N-dimensional Euclidean space, where N is the number of forms.

static from_matrix(domain: HilbertSpace, codomain: HilbertSpace, matrix: np.ndarray | sp.sparray | ScipyLinOp, /, *, galerkin: bool = False) MatrixLinearOperator[source]

Creates the most appropriate LinearOperator from a matrix representation.

This factory method acts as a dispatcher, inspecting the type of the input matrix and returning the most specialized and optimized operator subclass (e.g., Dense, Sparse, or DiagonalSparse). It also handles matrix-free scipy.sparse.linalg.LinearOperator objects.

Parameters:
  • domain – The operator’s domain space.

  • codomain – The operator’s codomain space.

  • matrix – The matrix representation (NumPy ndarray, SciPy sparray, or SciPy LinearOperator).

  • galerkin – If True, the matrix is interpreted in Galerkin form.

Returns:

An instance of the most appropriate MatrixLinearOperator subclass.

static from_tensor_product(domain: HilbertSpace, codomain: HilbertSpace, vector_pairs: List[Tuple[Any, Any]], /, *, weights: List[float] | None = None) LinearOperator[source]

Creates an operator from a weighted sum of tensor products.

The operator represents A(x) = sum_i( w_i * <x, v_i> * u_i ), where vector_pairs are (u_i, v_i).

static from_vector(domain: HilbertSpace, vector: Vector) LinearOperator[source]

Creates a rank-1 LinearOperator from a single domain vector.

The resulting operator maps from the given domain to a 1-dimensional Euclidean space.

The forward mapping evaluates the inner product: A(x) = [<vector, x>]. The adjoint mapping scales the vector: A*(y) = y[0] * vector.

Parameters:
  • domain – The Hilbert space the vector belongs to.

  • vector – A single vector in the domain space.

Returns:

A LinearOperator mapping from ‘domain’ to EuclideanSpace(1).

static from_vectors(domain: HilbertSpace, vectors: List[Vector]) LinearOperator[source]

Creates a LinearOperator from a list of domain vectors.

The resulting operator maps from the given domain to a Euclidean space of dimension n (where n is the number of vectors).

The forward mapping is given by A(x)_i = <vectors[i], x>. The adjoint mapping is given by A*(y) = sum(y_i * vectors[i]).

Parameters:
  • domain – The Hilbert space the vectors belong to.

  • vectors – A list of vectors in the domain space.

Returns:

A LinearOperator mapping from ‘domain’ to EuclideanSpace(len(vectors)).

Raises:

ValueError – If the list of vectors is empty.

property linear: bool

True, as this is a LinearOperator.

matrix(*, dense: bool = False, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) LinearOperator | ndarray[source]

Returns a matrix representation of the operator.

This provides a concrete matrix that represents the operator’s action on the underlying component vectors.

Parameters:
  • dense – If True, returns a dense numpy.ndarray. If False (default), returns a memory-efficient, matrix-free scipy.sparse.linalg.LinearOperator.

  • galerkin – If True, the returned matrix is the Galerkin representation, whose rmatvec corresponds to the adjoint operator. If False (default), the rmatvec corresponds to the dual operator. The Galerkin form is essential for algorithms that rely on symmetry/self-adjointness.

  • parallel – If True and dense=True, computes the matrix columns in parallel.

  • n_jobs – Number of parallel jobs to use. -1 uses all available cores.

Returns:

The matrix representation, either dense or matrix-free.

static self_adjoint(domain: HilbertSpace, mapping: Callable[[Any], Any]) LinearOperator[source]

Creates a self-adjoint operator.

static self_adjoint_from_matrix(domain: HilbertSpace, matrix: np.ndarray | sp.sparray | ScipyLinOp) MatrixLinearOperator[source]

Creates the most appropriate self-adjoint LinearOperator from a matrix.

This factory acts as a dispatcher, returning the most specialized subclass for the given matrix type (e.g., Dense, Sparse).

It ALWAYS assumes the provided matrix is the Galerkin representation of the operator. The user is responsible for ensuring the input matrix is symmetric (or self-adjoint for ScipyLinOp).

Parameters:
  • domain – The operator’s domain and codomain space.

  • matrix – The symmetric matrix representation.

Returns:

An instance of the most appropriate MatrixLinearOperator subclass.

static self_adjoint_from_tensor_product(domain: HilbertSpace, vectors: List[Any], /, *, weights: List[float] | None = None) LinearOperator[source]

Creates a self-adjoint operator from a tensor product sum.

static self_dual(domain: HilbertSpace, mapping: Callable[[Any], Any]) LinearOperator[source]

Creates a self-dual operator.

with_dense_matrix(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) DenseMatrixLinearOperator[source]

Returns a new operator equivalent to the existing one, but with its matrix representation computed and stored internally in dense form.

Parameters:
  • galerkin – If True, the Galerkin representation is used. Default is False.

  • parallel – If True, computes the dense matrix in parallel.

  • n_jobs – Number of CPU cores to use. -1 means all available.

Returns:

A DenseMatrixLinearOperator instance.

class pygeoinf.linear_operators.MatrixLinearOperator(domain: HilbertSpace, codomain: HilbertSpace, matrix: np.ndarray | ScipyLinOp, /, *, galerkin=False)[source]

Bases: LinearOperator

A sub-class of LinearOperator for which the operator’s action is defined internally through its matrix representation.

This matrix can be either a dense numpy matrix or a scipy LinearOperator.

extract_diagonal(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) ndarray[source]

Overload for efficiency.

extract_diagonals(offsets: List[int], /, *, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) Tuple[ndarray, List[int]][source]

Overrides the base method for efficiency by extracting diagonals directly from the stored dense matrix when possible.

property is_dense: bool

Returns True if the matrix representation is stored internally in dense form.

property is_galerkin: bool

Returns True if the matrix representation is stored in Galerkin form.

class pygeoinf.linear_operators.SparseMatrixLinearOperator(domain: HilbertSpace, codomain: HilbertSpace, matrix: sp.sparray, /, *, galerkin: bool = False)[source]

Bases: MatrixLinearOperator

A specialization for operators represented by a modern SciPy sparse array.

This class requires a scipy.sparse.sparray object (e.g., csr_array) and provides optimized methods that delegate to efficient SciPy routines.

Upon initialization, the internal array is converted to the CSR (Compressed Sparse Row) format to ensure consistently fast matrix-vector products and row-slicing operations.

extract_diagonal(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) ndarray[source]

Overrides the base method to efficiently extract the main diagonal.

extract_diagonals(offsets: List[int], /, *, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) Tuple[ndarray, List[int]][source]

Overrides the base method for efficiency by extracting diagonals directly from the stored sparse array.

property sparse_array: sparray

Provides public read-only access to the underlying SciPy sparse array.

pygeoinf.linear_optimisation module

Provides deterministic, optimization-based methods for solving linear inverse problems.

This module implements classical inversion techniques that seek a single “best-fit” model by minimizing a specific cost functional. It leverages the abstract operator algebra of the library, allowing inversions to be rigorously formulated in Hilbert spaces and seamlessly applied to discrete representations.

A core feature of this module is its dual algebraic formalism, allowing users to optimize computational efficiency based on the problem geometry: - Model Space Formulation: Assembles and solves the standard normal equations

(size N x N, where N is the model dimension). Best suited for overdetermined problems.

  • Data Space Formulation: Assembles and solves the dual formulation (size M x M, where M is the data dimension) using the representer method. Highly efficient for underdetermined problems where data measurements are sparse compared to the model.

Key Classes

  • LinearLeastSquaresInversion: Solves the inverse problem by minimizing a Tikhonov-regularized least-squares functional.

  • ConstrainedLinearLeastSquaresInversion: Solves the regularized least-squares problem strictly within an affine subspace (e.g., enforcing exact boundary conditions or mean property values).

  • LinearMinimumNormInversion: Finds the model with the smallest norm that fits the data to a statistically acceptable degree using the discrepancy principle. Provides exact analytical Fréchet derivatives of the discrepancy search.

  • ConstrainedLinearMinimumNormInversion: Applies the discrepancy principle subject to an exact affine subspace constraint, resolving the non-linear mapping between constraint values and the resulting model.

class pygeoinf.linear_optimisation.ConstrainedLinearLeastSquaresInversion(forward_problem: LinearForwardProblem, constraint: AffineSubspace, /, *, formalism: Literal['model_space', 'data_space'] = 'data_space')[source]

Bases: LinearInversion

Solves a linear inverse problem subject to an affine subspace constraint.

This method finds the model u that minimizes the Tikhonov-regularized least-squares functional while strictly confining the solution to an affine subspace (e.g., enforcing a specific average property value or boundary condition).

Supports both ‘model_space’ and ‘data_space’ formalisms for the underlying unconstrained inversion.

data_reduced_inversion(reduction_operator: LinearOperator, /, *, reduced_data_error_measure: GaussianMeasure | None = None, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) ConstrainedLinearLeastSquaresInversion[source]

Constructs a surrogate of the constrained linear inversion using a reduced data space.

least_squares_operator(damping: float, solver: LinearSolver, /, *, preconditioner: LinearOperator | LinearSolver | None = None) AffineOperator[source]

Returns an operator that maps data to the constrained least-squares solution.

normal_residual_callback(damping: float, data: Vector, /, *, message: str = 'Iteration: {iter} | Normal Residual: {res:.3e}', print_progress: bool = True)[source]

Generates a ResidualTrackingCallback for the reduced, unconstrained normal equations.

parameterized_inversion(parameterization: LinearOperator, /, *, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) ConstrainedLinearLeastSquaresInversion[source]

Constructs a parameterized surrogate of the constrained least-squares inversion.

Parameters:
  • parameterization – A LinearOperator mapping from the parameter space to the full model space.

  • dense – If True, computes and stores operators as dense matrices.

  • parallel – If True, computes the dense matrices in parallel.

  • n_jobs – Number of CPU cores to use. -1 means all available.

  • formalism – An optional override for the formalism of the new inversion. If None, inherits the formalism of the parent inversion.

Returns:

A new ConstrainedLinearLeastSquaresInversion instance operating on the parameter space.

with_formalism(formalism: Literal['model_space', 'data_space']) ConstrainedLinearLeastSquaresInversion[source]

Returns a new instance of the constrained inversion using the specified formalism.

Parameters:

formalism – The algebraic space in which the normal equations should be assembled and solved. Must be ‘model_space’ or ‘data_space’.

Returns:

A new ConstrainedLinearLeastSquaresInversion instance with the updated formalism.

class pygeoinf.linear_optimisation.ConstrainedLinearMinimumNormInversion(forward_problem: LinearForwardProblem, constraint: AffineSubspace, /, *, formalism: Literal['model_space', 'data_space'] = 'data_space')[source]

Bases: LinearInversion

Finds the minimum-norm solution subject to an affine subspace constraint.

This class solves the regularized inverse problem using the discrepancy principle while strictly confining the solution to an affine subspace.

constraint_value_mapping(data: Vector, solver: LinearSolver, /, *, preconditioner: LinearOperator | LinearSolver | None = None, significance_level: float = 0.95, minimum_damping: float = 0.0, maxiter: int = 100, rtol: float = 1e-06, atol: float = 0.0) NonLinearOperator[source]

Returns an operator mapping a constraint value ‘w’ to the corresponding constrained minimum norm solution ‘u’ for a strictly fixed dataset. The operator has its derivative set.

data_reduced_inversion(reduction_operator: LinearOperator, /, *, reduced_data_error_measure: GaussianMeasure | None = None, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) ConstrainedLinearMinimumNormInversion[source]

Constructs a surrogate of the constrained linear inversion using a reduced data space.

minimum_norm_operator(solver: LinearSolver, /, *, preconditioner: LinearOperator | LinearSolver | None = None, significance_level: float = 0.95, minimum_damping: float = 0.0, maxiter: int = 100, rtol: float = 1e-06, atol: float = 0.0) NonLinearOperator[source]

Returns an operator that maps data to the constrained minimum-norm solution. The operator has its derivative set.

parameterized_inversion(parameterization: LinearOperator, /, *, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) ConstrainedLinearMinimumNormInversion[source]

Constructs a parameterized surrogate of the constrained minimum norm inversion.

Parameters:
  • parameterization – A LinearOperator mapping from the parameter space to the full model space.

  • dense – If True, computes and stores operators as dense matrices.

  • parallel – If True, computes the dense matrices in parallel.

  • n_jobs – Number of CPU cores to use. -1 means all available.

  • formalism – An optional override for the formalism of the new inversion. If None, inherits the formalism of the parent inversion.

Returns:

A new ConstrainedLinearMinimumNormInversion instance operating on the parameter space.

with_formalism(formalism: Literal['model_space', 'data_space']) ConstrainedLinearMinimumNormInversion[source]

Returns a new instance of the constrained inversion using the specified formalism.

Parameters:

formalism – The algebraic space in which the normal equations should be assembled and solved. Must be ‘model_space’ or ‘data_space’.

Returns:

A new ConstrainedLinearMinimumNormInversion instance with the updated formalism.

class pygeoinf.linear_optimisation.LinearLeastSquaresInversion(forward_problem: LinearForwardProblem, /, *, formalism: Literal['model_space', 'data_space'] = 'data_space')[source]

Bases: LinearInversion

Solves a linear inverse problem using Tikhonov-regularized least-squares.

This method finds the model u that minimizes the cost functional:

J(u) = ||A(u) - d||^2_R + damping * ||u||^2

where A is the forward operator, d is the observed data, R is the data covariance (if a data error measure is set), and damping is the Tikhonov regularization parameter.

This class supports two formalisms for constructing the linear system: 1. ‘model_space’: Solves the standard normal equations of size (N x N),

where N is the model dimension. Best for overdetermined problems.

  1. ‘data_space’: Solves the dual formulation of size (M x M), where M is the data dimension. Best for highly underdetermined problems.

data_reduced_inversion(reduction_operator: LinearOperator, /, *, reduced_data_error_measure: GaussianMeasure | None = None, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) LinearLeastSquaresInversion[source]

Constructs a surrogate of the least-squares inversion using a reduced data space.

least_squares_operator(damping: float, solver: LinearSolver, /, *, preconditioner: LinearOperator | LinearSolver | None = None) LinearOperator | AffineOperator[source]

Constructs the full operator that maps observed data directly to the least-squares model solution.

This method solves the internal normal equations and applies the necessary algebraic transformations (and affine shifts) to recover the model parameters from the data, seamlessly handling whichever formalism was selected during initialization.

Parameters:
  • damping – The Tikhonov regularization parameter.

  • solver – The LinearSolver instance used to invert the normal operator.

  • preconditioner – An optional LinearOperator, or a LinearSolver factory, used to precondition the normal equations. Only utilized if the provided solver is an IterativeLinearSolver.

Returns:

A LinearOperator (or AffineOperator, if a non-zero data expectation exists) that maps a vector from the data space to the optimal vector in the model space.

Raises:

TypeError – If the provided preconditioner is neither a LinearOperator nor a LinearSolver.

normal_operator(damping: float) LinearOperator[source]

Constructs the regularized normal operator for the chosen formalism.

For ‘model_space’, this returns: A* R^{-1} A + damping * I For ‘data_space’, this returns: A A* + damping * R

Parameters:

damping – The non-negative Tikhonov regularization parameter.

Returns:

A LinearOperator representing the left-hand side of the normal equations.

Raises:

ValueError – If the damping parameter is negative.

normal_residual_callback(damping: float, data: Vector, /, *, message: str = 'Iteration: {iter} | Normal Residual: {res:.3e}', print_progress: bool = True)[source]

Generates a ResidualTrackingCallback pre-configured to track the convergence of the least-squares normal equations for the given data vector.

normal_rhs(data: Vector) Vector[source]

Computes the right-hand side vector for the normal equations.

Prior to construction, the data is shifted by the expected value of the data error measure (i.e., v - z_bar), if applicable.

For ‘model_space’, this returns: A* R^{-1} (v - z_bar) For ‘data_space’, this returns: (v - z_bar)

Parameters:

data – The observed data vector in the data space.

Returns:

The right-hand side Vector for the chosen linear system.

surrogate_inversion(*, alternate_forward_operator: LinearOperator | None = None, alternate_data_error_measure: GaussianMeasure | None = None) LinearLeastSquaresInversion[source]

Constructs a surrogate least-squares inversion problem using simplified physics or data errors.

surrogate_woodbury_data_preconditioner(damping: float, solver: LinearSolver, /, *, alternate_forward_operator: LinearOperator | None = None, alternate_data_error_measure: GaussianMeasure | None = None, noise_solver: LinearSolver | None = None) LinearOperator[source]

Builds a data-space preconditioner by applying the Woodbury matrix identity to a simplified surrogate inverse problem.

Note

Ensure that any alternate measures provided have well-defined inverse covariances, or use .with_regularized_inverse() on them before passing them to this method.

Parameters:
  • damping – The Tikhonov regularization parameter.

  • solver – The LinearSolver used to invert the inner Woodbury operator.

  • alternate_forward_operator – An optional simplified forward operator.

  • alternate_data_error_measure – An optional simplified data error measure.

  • noise_solver – Optional solver for the noise covariance.

Returns:

A LinearOperator representing the Woodbury-approximated inverse.

surrogate_woodbury_model_preconditioner(damping: float, solver: LinearSolver, /, *, alternate_forward_operator: LinearOperator | None = None, alternate_data_error_measure: GaussianMeasure | None = None) LinearOperator[source]

Builds a model-space preconditioner by applying the Woodbury matrix identity to a simplified surrogate inverse problem.

with_formalism(formalism: Literal['model_space', 'data_space']) LinearLeastSquaresInversion[source]

Returns a new instance of the inversion using the specified formalism.

Parameters:

formalism – The algebraic space in which the normal equations should be assembled and solved. Must be ‘model_space’ or ‘data_space’.

Returns:

A new LinearLeastSquaresInversion instance with the updated formalism.

woodbury_data_preconditioner(damping: float, solver: LinearSolver, /, *, noise_solver: LinearSolver | None = None) LinearOperator[source]

Constructs a data-space preconditioner using the Woodbury matrix identity.

Data Space Normal Operator: N_d = A A* + damping * R Woodbury Identity: N_d^-1 = (1/damping) * [R^-1 - R^-1 A (A* R^-1 A + damping * I)^-1 A* R^-1]

Note

This method assumes the noise measure (R) either already has a well-defined inverse covariance, or is well-conditioned enough to be inverted by the provided solver. If it is an unbounded operator on a function space, use R.with_regularized_inverse() before passing it to the inversion.

Parameters:
  • damping – The Tikhonov regularization parameter.

  • solver – The LinearSolver used to invert the inner Woodbury operator (N_m).

  • noise_solver – An optional solver used to explicitly invert the data error covariance (R) if its inverse is not already set. Defaults to solver if not provided.

Returns:

A LinearOperator representing the Woodbury-approximated inverse.

woodbury_model_preconditioner(damping: float, solver: LinearSolver, /) LinearOperator[source]

Constructs a model-space preconditioner using the Woodbury matrix identity.

Model Space Normal Operator: N_m = A* R^-1 A + damping * I Woodbury Identity: N_m^-1 = (1/damping) * [I - A* (damping * R + A A*)^-1 A]

Note

This formulation does not require evaluating the explicit inverse of the noise covariance (R).

Parameters:
  • damping – The Tikhonov regularization parameter.

  • solver – The LinearSolver used to invert the inner Woodbury operator (N_d).

Returns:

A LinearOperator representing the Woodbury-approximated inverse.

class pygeoinf.linear_optimisation.LinearMinimumNormInversion(forward_problem: LinearForwardProblem, /, *, formalism: Literal['model_space', 'data_space'] = 'data_space')[source]

Bases: LinearInversion

Finds a regularized solution using the discrepancy principle.

This method finds the model u with the smallest norm that fits the data to a statistically acceptable degree (determined by a target chi-squared value and significance level).

This class supports two formalisms for constructing the linear systems: 1. ‘model_space’: Solves the normal equations of size (N x N). 2. ‘data_space’: Solves the dual formulation of size (M x M).

minimum_norm_operator(solver: LinearSolver, /, *, preconditioner: LinearOperator | LinearSolver | None = None, significance_level: float = 0.95, minimum_damping: float = 0.0, maxiter: int = 100, rtol: float = 1e-06, atol: float = 0.0) NonLinearOperator | LinearOperator[source]

Maps data to the minimum-norm solution matching target chi-squared.

The returned NonLinearOperator includes the exact analytical Fréchet derivative of the discrepancy search, complete with its adjoint mapping.

with_formalism(formalism: Literal['model_space', 'data_space']) LinearMinimumNormInversion[source]

Returns a new instance of the inversion using the specified formalism.

Parameters:

formalism – The algebraic space in which the normal equations should be assembled and solved. Must be ‘model_space’ or ‘data_space’.

Returns:

A new LinearMinimumNormInversion instance with the updated formalism.

pygeoinf.linear_solvers module

Provides a collection of solvers for linear systems of equations.

This module offers a unified interface for solving linear systems A(x) = y, where A is a LinearOperator. It includes both direct methods based on matrix factorization and iterative, matrix-free methods suitable for large-scale problems.

The solvers are implemented as callable classes. An instance of a solver can be called with an operator to produce a new operator representing its inverse.

Key Classes

  • LUSolver, CholeskySolver: Direct solvers based on matrix factorization.

  • ScipyIterativeSolver: A general wrapper for SciPy’s iterative algorithms (CG, GMRES, etc.) that operate on matrix representations.

  • CGSolver: A pure, matrix-free implementation of the Conjugate Gradient algorithm that operates directly on abstract Hilbert space vectors.

pygeoinf.linear_solvers.BICGMatrixSolver(galerkin: bool = False, **kwargs) ScipyIterativeSolver[source]
pygeoinf.linear_solvers.BICGStabMatrixSolver(galerkin: bool = False, **kwargs) ScipyIterativeSolver[source]
class pygeoinf.linear_solvers.BICGStabSolver(*, preconditioning_method: LinearSolver = None, rtol: float = 1e-05, atol: float = 1e-08, maxiter: int | None = None)[source]

Bases: IterativeLinearSolver

A matrix-free implementation of the BiCGStab algorithm.

Suitable for non-symmetric linear systems Ax = y. It operates directly on Hilbert space vectors using native inner products and arithmetic.

solve_linear_system(operator: LinearOperator, preconditioner: LinearOperator | None, y: Vector, x0: Vector | None) Vector[source]

Solves the linear system Ax = y for x.

Parameters:
  • operator (LinearOperator) – The operator A of the linear system.

  • preconditioner (LinearOperator, optional) – The preconditioner.

  • y (Vector) – The right-hand side vector.

  • x0 (Vector, optional) – The initial guess for the solution.

Returns:

The solution vector x.

Return type:

Vector

pygeoinf.linear_solvers.CGMatrixSolver(galerkin: bool = False, **kwargs) ScipyIterativeSolver[source]
class pygeoinf.linear_solvers.CGSolver(*, preconditioning_method: LinearSolver = None, rtol: float = 1e-05, atol: float = 0.0, maxiter: int | None = None, callback: Callable[[Vector], None] | None = None)[source]

Bases: IterativeLinearSolver

A matrix-free implementation of the Conjugate Gradient (CG) algorithm.

This solver operates directly on Hilbert space vectors and operator actions without explicitly forming a matrix. It is suitable for self-adjoint, positive-definite operators on a general Hilbert space.

solve_linear_system(operator: LinearOperator, preconditioner: LinearOperator | None, y: Vector, x0: Vector | None) Vector[source]

Solves the linear system Ax = y for x.

Parameters:
  • operator (LinearOperator) – The operator A of the linear system.

  • preconditioner (LinearOperator, optional) – The preconditioner.

  • y (Vector) – The right-hand side vector.

  • x0 (Vector, optional) – The initial guess for the solution.

Returns:

The solution vector x.

Return type:

Vector

class pygeoinf.linear_solvers.CholeskySolver(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1)[source]

Bases: DirectLinearSolver

A direct linear solver based on Cholesky decomposition.

class pygeoinf.linear_solvers.DirectLinearSolver(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1)[source]

Bases: LinearSolver

An abstract base class for direct linear solvers that rely on matrix factorization.

class pygeoinf.linear_solvers.EigenSolver(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1, rtol: float = 1e-12)[source]

Bases: DirectLinearSolver

A direct linear solver based on the eigendecomposition of a symmetric operator.

class pygeoinf.linear_solvers.FCGSolver(*, rtol: float = 1e-05, atol: float = 1e-08, maxiter: int | None = None, preconditioning_method: LinearSolver | None = None)[source]

Bases: IterativeLinearSolver

Flexible Conjugate Gradient (FCG) solver.

FCG is designed to handle variable preconditioning, such as using an inner iterative solver to approximate the action of M^-1.

solve_linear_system(operator: LinearOperator, preconditioner: LinearOperator | None, y: Vector, x0: Vector | None) Vector[source]

Solves the linear system Ax = y for x.

Parameters:
  • operator (LinearOperator) – The operator A of the linear system.

  • preconditioner (LinearOperator, optional) – The preconditioner.

  • y (Vector) – The right-hand side vector.

  • x0 (Vector, optional) – The initial guess for the solution.

Returns:

The solution vector x.

Return type:

Vector

pygeoinf.linear_solvers.GMRESMatrixSolver(galerkin: bool = False, **kwargs) ScipyIterativeSolver[source]
class pygeoinf.linear_solvers.IterativeLinearSolver(*, preconditioning_method: LinearSolver = None)[source]

Bases: LinearSolver

An abstract base class for iterative linear solvers.

property iterations: int

Returns the number of iterations within the last solve. The value is zero if the solver has yet to be called.

solve_adjoint_linear_system(operator: LinearOperator, adjoint_preconditioner: LinearOperator | None, x: Vector, y0: Vector | None) Vector[source]

Solves the adjoint linear system A*y = x for y.

abstract solve_linear_system(operator: LinearOperator, preconditioner: LinearOperator | None, y: Vector, x0: Vector | None) Vector[source]

Solves the linear system Ax = y for x.

Parameters:
  • operator (LinearOperator) – The operator A of the linear system.

  • preconditioner (LinearOperator, optional) – The preconditioner.

  • y (Vector) – The right-hand side vector.

  • x0 (Vector, optional) – The initial guess for the solution.

Returns:

The solution vector x.

Return type:

Vector

class pygeoinf.linear_solvers.LSQRSolver(*, rtol: float = 1e-05, atol: float = 1e-08, maxiter: int | None = None)[source]

Bases: IterativeLinearSolver

A matrix-free implementation of the LSQR algorithm with damping support.

This solver is designed to solve the problem: minimize ||Ax - y||_2^2 + damping^2 * ||x||_2^2.

solve_linear_system(operator: LinearOperator, preconditioner: LinearOperator | None, y: Vector, x0: Vector | None, damping: float = 0.0) Vector[source]

Solves the linear system Ax = y for x.

Parameters:
  • operator (LinearOperator) – The operator A of the linear system.

  • preconditioner (LinearOperator, optional) – The preconditioner.

  • y (Vector) – The right-hand side vector.

  • x0 (Vector, optional) – The initial guess for the solution.

Returns:

The solution vector x.

Return type:

Vector

class pygeoinf.linear_solvers.LUSolver(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1)[source]

Bases: DirectLinearSolver

A direct linear solver based on the LU decomposition of an operator’s dense matrix representation.

class pygeoinf.linear_solvers.LinearSolver[source]

Bases: ABC

An abstract base class for linear solvers.

class pygeoinf.linear_solvers.MinResSolver(*, preconditioning_method: LinearSolver = None, rtol: float = 1e-05, atol: float = 1e-08, maxiter: int | None = None)[source]

Bases: IterativeLinearSolver

A matrix-free implementation of the MINRES algorithm.

Suitable for symmetric, possibly indefinite or singular linear systems. It minimizes the norm of the residual ||r|| in each step using the Hilbert space’s native inner product.

solve_linear_system(operator: LinearOperator, preconditioner: LinearOperator | None, y: Vector, x0: Vector | None) Vector[source]

Solves the linear system Ax = y for x.

Parameters:
  • operator (LinearOperator) – The operator A of the linear system.

  • preconditioner (LinearOperator, optional) – The preconditioner.

  • y (Vector) – The right-hand side vector.

  • x0 (Vector, optional) – The initial guess for the solution.

Returns:

The solution vector x.

Return type:

Vector

class pygeoinf.linear_solvers.ProgressCallback(message: str = 'Iteration: ')[source]

Bases: object

A simple callback that prints the solver’s iteration count.

finalize() None[source]

Called at the end of a solve to clean up the console output.

reset() None[source]

Resets the state for a new solve.

class pygeoinf.linear_solvers.ResidualTrackingCallback(operator: LinearOperator, y: Vector, print_progress: bool = True, message: str = 'Iteration: {iter} | Residual: {res:.3e}')[source]

Bases: ProgressCallback

A callback that computes and tracks the exact residual norm ||y - A(x)||.

Warning: This evaluates the forward operator once per iteration. For very large problems, this may introduce computational overhead.

class pygeoinf.linear_solvers.ScipyIterativeSolver(method: str, /, *, preconditioning_method: LinearSolver = None, galerkin: bool = False, **kwargs)[source]

Bases: IterativeLinearSolver

A general iterative solver that wraps SciPy’s iterative algorithms.

This class provides a unified interface to SciPy’s sparse iterative solvers like cg, gmres, bicgstab, etc. The specific algorithm is chosen during instantiation, and keyword arguments are passed directly to the chosen SciPy function.

solve_linear_system(operator: LinearOperator, preconditioner: LinearOperator | None, y: Vector, x0: Vector | None) Vector[source]

Solves the linear system Ax = y for x.

Parameters:
  • operator (LinearOperator) – The operator A of the linear system.

  • preconditioner (LinearOperator, optional) – The preconditioner.

  • y (Vector) – The right-hand side vector.

  • x0 (Vector, optional) – The initial guess for the solution.

Returns:

The solution vector x.

Return type:

Vector

class pygeoinf.linear_solvers.SolutionTrackingCallback(domain: HilbertSpace, message: str = 'Iteration: ', print_progress: bool = True)[source]

Bases: ProgressCallback

A callback that tracks the solution vector at each iteration.

Useful for visualizing the convergence path of the solver or calculating diagnostics post-hoc without slowing down the inversion.

pygeoinf.low_rank module

Implements randomized matrix-free algorithms for operators on Hilbert spaces.

This module provides fully abstract, matrix-free implementations of randomized factorizations (SVD, Cholesky, Eigendecomposition). These algorithms operate entirely via operator composition, adjoint mappings, and the intrinsic geometry of the underlying HilbertSpace objects.

If a GaussianMeasure is provided, the algorithms draw structured samples to respect mass matrices and continuous function space geometries. If no measure is provided, they fall back to highly optimized, component-based matrix-free algorithms.

class pygeoinf.low_rank.LowRankCholesky(l_op: LinearOperator)[source]

Bases: LinearOperator

A LinearOperator representing the Cholesky-like factorization: A ≈ L @ L*.

This class provides a memory-efficient low-rank Cholesky decomposition of a positive semi-definite operator, highly useful for drawing samples from Gaussian measures.

classmethod from_randomized(operator: LinearOperator, size_estimate: int, *, measure: GaussianMeasure | None = None, galerkin: bool = True, method: str = 'variable', max_rank: int | None = None, power: int = 2, rtol: float = 0.0001, block_size: int = 10, parallel: bool = False, n_jobs: int = -1) LowRankCholesky[source]

Computes a robust approximate Cholesky factorization via randomized range finding.

Attempts a direct dense Cholesky factorization on the projected core matrix. If it fails (due to numerical precision issues), it safely falls back to an Eigendecomposition-based square root.

Parameters:
  • operator (LinearOperator) – Positive semi-definite operator to factorize.

  • size_estimate (int) – Target rank or initial block size.

  • measure (GaussianMeasure, optional) – Prior measure for drawing test vectors.

  • galerkin (bool) – Default True. Computes Galerkin representation on fallback.

  • method (str) – {‘variable’, ‘fixed’}.

  • max_rank (int, optional) – Upper limit on rank for ‘variable’ method.

  • power (int) – Number of power iterations.

  • rtol (float) – Relative tolerance for ‘variable’ method.

  • block_size (int) – Samples per iteration.

  • parallel (bool) – Parallelize the sampling/multiplication.

  • n_jobs (int) – CPU cores to utilize.

Returns:

An instantiated operator containing the L factor.

Return type:

LowRankCholesky

Raises:

ValueError – If the operator is not an automorphism.

property l_factor: LinearOperator

The Cholesky factor (L).

Type:

LinearOperator

property rank: int

The rank of the approximation.

Type:

int

class pygeoinf.low_rank.LowRankEig(u_op: LinearOperator, d_op: DiagonalSparseMatrixLinearOperator)[source]

Bases: LinearOperator

A LinearOperator representing the eigendecomposition: A ≈ U @ D @ U*.

This class encapsulates the components of an Eigendecomposition for a self-adjoint operator, allowing it to act as a LinearOperator while exposing the eigenvectors and eigenvalues.

apply_function(func: Callable[[ndarray], ndarray], *, regularization: float = 0.0) LowRankEig[source]

Applies a function to the spectrum of the operator. Returns a new LowRankEig representing f(A).

property d_factor: DiagonalSparseMatrixLinearOperator

The diagonal matrix of eigenvalues (D).

Type:

DiagonalSparseMatrixLinearOperator

property eigenvalues: ndarray

A 1D array of the computed eigenvalues.

Type:

np.ndarray

classmethod from_randomized(operator: LinearOperator, size_estimate: int, *, measure: GaussianMeasure | None = None, galerkin: bool = True, method: str = 'variable', max_rank: int | None = None, power: int = 2, rtol: float = 0.0001, block_size: int = 10, parallel: bool = False, n_jobs: int = -1) LowRankEig[source]

Computes the Eigendecomposition using a unified randomized range finder.

Parameters:
  • operator (LinearOperator) – The self-adjoint operator to approximate.

  • size_estimate (int) – Target rank or initial block size.

  • measure (GaussianMeasure, optional) – Prior measure for drawing test vectors.

  • galerkin (bool) – Default True for Eig. Computes Galerkin representation on fallback.

  • method (str) – {‘variable’, ‘fixed’}.

  • max_rank (int, optional) – Upper limit on rank for ‘variable’ method.

  • power (int) – Number of power iterations.

  • rtol (float) – Relative tolerance for ‘variable’ method.

  • block_size (int) – Samples per iteration.

  • parallel (bool) – Parallelize the sampling/multiplication.

  • n_jobs (int) – CPU cores to utilize.

Returns:

An instantiated operator containing the U and D factors.

Return type:

LowRankEig

Raises:

ValueError – If the operator is not an automorphism (domain != codomain).

property rank: int

The rank of the approximation.

Type:

int

property u_factor: LinearOperator

The eigenvectors (U).

Type:

LinearOperator

class pygeoinf.low_rank.LowRankSVD(u_op: LinearOperator, sigma_op: DiagonalSparseMatrixLinearOperator, v_op: LinearOperator)[source]

Bases: LinearOperator

A LinearOperator representing the low-rank SVD: A ≈ U @ Sigma @ V*.

This class encapsulates the components of a Singular Value Decomposition, allowing it to be used directly as a LinearOperator while providing access to the individual low-rank factors.

classmethod from_randomized(operator: LinearOperator, size_estimate: int, *, measure: GaussianMeasure | None = None, galerkin: bool = False, method: str = 'variable', max_rank: int | None = None, power: int = 2, rtol: float = 0.0001, block_size: int = 10, parallel: bool = False, n_jobs: int = -1) LowRankSVD[source]

Computes the SVD using a unified randomized range finder.

Parameters:
  • operator (LinearOperator) – The operator to approximate.

  • size_estimate (int) – For ‘fixed’ method, the exact target rank. For ‘variable’ method, this is the initial rank to sample.

  • measure (GaussianMeasure, optional) – A prior measure used to draw test vectors. If provided, respects the domain’s geometry. If None, falls back to a component-based SciPy LinearOperator representation.

  • galerkin (bool) – If True, computes the Galerkin representation when falling back to components.

  • method (str) – {‘variable’, ‘fixed’}. The rank-determination algorithm to use.

  • max_rank (int, optional) – Hard limit on the rank for the ‘variable’ method.

  • power (int) – Number of power iterations to improve accuracy.

  • rtol (float) – Relative tolerance for the ‘variable’ method.

  • block_size (int) – Number of new vectors to sample per iteration in ‘variable’ method.

  • parallel (bool) – Whether to parallelize the matrix/operator evaluations.

  • n_jobs (int) – Number of cores to use if parallel=True (-1 for all).

Returns:

An instantiated operator containing the U, Sigma, and V factors.

Return type:

LowRankSVD

property rank: int

The rank of the approximation.

Type:

int

property sigma_factor: DiagonalSparseMatrixLinearOperator

The diagonal matrix of singular values (Sigma).

Type:

DiagonalSparseMatrixLinearOperator

property singular_values: ndarray

A 1D array of the computed singular values.

Type:

np.ndarray

property u_factor: LinearOperator

The left singular vectors (U).

Type:

LinearOperator

property v_factor: LinearOperator

The right singular vectors (V).

Type:

LinearOperator

pygeoinf.low_rank.deflated_diagonal(operator: LinearOperator, rank: int, size_estimate: int, /, *, method: str = 'variable', use_rademacher: bool = True, max_samples: int | None = None, rtol: float = 0.01, block_size: int = 10, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) ndarray[source]

Estimates the diagonal of a square operator’s component matrix using SVD deflation.

This combines a deterministic low-rank diagonal approximation (via SVD) with a stochastic estimate of the residual diagonal (via Hutchinson’s).

Parameters:
  • operator – The square LinearOperator to analyze.

  • rank – The rank of the deterministic SVD deflation.

  • size_estimate – Initial number of samples for the stochastic residual.

  • method – ‘variable’ or ‘fixed’ for the stochastic residual phase.

  • use_rademacher – If True, uses [-1, 1] Rademacher noise for Hutchinson’s.

  • max_samples – Hard limit on residual samples.

  • rtol – Relative tolerance for the stochastic residual phase.

  • block_size – Samples added per iteration in the stochastic phase.

  • galerkin – If True, computes the diagonal of the Galerkin matrix.

  • parallel – Whether to compute operations in parallel.

  • n_jobs – Number of CPU cores to utilize.

Returns:

A 1D array representing the diagonal of the operator.

Return type:

np.ndarray

pygeoinf.low_rank.random_diagonal(matrix: ndarray | LinearOperator, size_estimate: int, /, *, method: str = 'variable', use_rademacher: bool = False, max_samples: int | None = None, rtol: float = 0.01, block_size: int = 10, parallel: bool = False, n_jobs: int = -1) ndarray[source]

Computes an approximate diagonal of a square matrix using Hutchinson’s method.

This algorithm uses a progressive, iterative approach to estimate the diagonal. It starts with an initial number of samples and adds new blocks of random vectors until the estimate of the diagonal converges to a specified tolerance.

Note: This is a specialized, component-based implementation relying on element-wise array multiplication.

Parameters:
  • matrix – The (n, n) matrix or LinearOperator to analyze.

  • size_estimate – For ‘fixed’ method, the exact target rank. For ‘variable’ method, this is the initial rank to sample.

  • method ({'variable', 'fixed'}) – The algorithm to use. - ‘variable’: Progressively samples to meet tolerance rtol. - ‘fixed’: Returns an estimate based on exactly size_estimate samples.

  • use_rademacher – If true, draw components from [-1,1]. Default method draws normally distributed components.

  • max_samples – For ‘variable’ method, a hard limit on the number of samples. Ignored if method=’fixed’. Defaults to dimension of matrix.

  • rtol – Relative tolerance for the ‘variable’ method.

  • block_size – Number of new vectors to sample per iteration in ‘variable’ method.

  • parallel – Whether to use parallel matrix multiplication.

  • n_jobs – Number of jobs for parallelism.

Returns:

A 1D numpy array of size n containing the approximate diagonal.

Return type:

np.ndarray

pygeoinf.low_rank.random_range(operator: LinearOperator, size_estimate: int, /, *, measure: GaussianMeasure | None = None, galerkin: bool = False, method: str = 'variable', max_rank: int | None = None, power: int = 2, rtol: float = 0.0001, block_size: int = 10, parallel: bool = False, n_jobs: int = -1) LinearOperator[source]

Unified random range finder acting as an architectural bridge.

If a GaussianMeasure is provided, it draws abstract structured samples to respect Hilbert space geometries and mass matrices. If no measure is provided, it routes to high-performance component-based representations via SciPy `LinearOperator`s.

Parameters:
  • operator (LinearOperator) – The linear operator whose range is to be approximated.

  • size_estimate (int) – Target rank (‘fixed’) or initial sample size (‘variable’).

  • measure (GaussianMeasure, optional) – Measure to draw test samples from.

  • galerkin (bool) – If True, uses the Galerkin representation for the component fallback.

  • method (str) – {‘variable’, ‘fixed’}. Algorithm choice.

  • max_rank (int, optional) – Hard limit on rank for variable sampling.

  • power (int) – Number of power iterations to enhance singular value decay.

  • rtol (float) – Relative tolerance for convergence checking.

  • block_size (int) – Size of new sample batches.

  • parallel (bool) – Parallelize computations where applicable.

  • n_jobs (int) – CPU cores to use.

Returns:

The isometry Q mapping from Euclidean(k) into the codomain.

Return type:

LinearOperator

pygeoinf.low_rank.white_noise_measure(domain: HilbertSpace) GaussianMeasure[source]

Creates a formal N(0, I) “white noise” measure on the given domain.

WARNING: Mathematically, the identity operator is not trace-class in infinite dimensions, meaning this does not define a valid Radon measure on the Hilbert space. It is a cylinder measure.

In this library, it is used strictly as a numerical tool to generate isotropic test vectors for randomized algorithms, ensuring that the sampling perfectly respects the domain’s inner product (e.g., mass matrices) without biasing the range approximation.

pygeoinf.nonlinear_forms module

Provides the NonLinearForm base class to represent non-linear functionals.

A non-linear form, or functional, is a mapping from a vector in a Hilbert space to a scalar. This class provides a foundational structure for these functionals, equipping them with algebraic operations and an interface for derivatives like gradients and Hessians.

For non-smooth convex functions, the class also supports subgradients, which generalize gradients to non-differentiable points.

class pygeoinf.nonlinear_forms.NonLinearForm(domain: HilbertSpace, mapping: Callable[[Vector], float], /, *, gradient: Callable[[Vector], Vector] | None = None, subgradient: Callable[[Vector], Vector] | None = None, hessian: Callable[[Vector], LinearOperator] | None = None)[source]

Bases: object

Represents a general non-linear functional that maps vectors to scalars.

This class serves as the foundation for all forms. It defines the basic callable interface form(x) and overloads arithmetic operators (+, -, *) to create new forms. It also provides an optional framework for specifying a form’s gradient, Hessian, and subgradient.

For smooth functions, use gradient and hessian. For non-smooth convex functions, use subgradient.

derivative(x: Vector) LinearForm[source]

Computes the derivative of the form at a given point.

Parameters:

x – The vector at which to evaluate the derivative.

Returns:

The derivative of the form as a LinearForm.

Raises:

NotImplementedError – If a gradient function was not provided during initialization.

property domain: HilbertSpace

The Hilbert space on which the form is defined.

gradient(x: Any) Vector[source]

Computes the gradient of the form at a given point.

Parameters:

x – The vector at which to evaluate the gradient.

Returns:

The gradient of the form as a vector in the domain space.

Raises:

NotImplementedError – If a gradient function was not provided during initialization.

property has_gradient: bool

True if the form has a gradient.

property has_hessian: bool

True if the form has a Hessian.

property has_subgradient: bool

True if the form has a subgradient.

hessian(x: Any) LinearOperator[source]

Computes the Hessian of the form at a given point.

Parameters:

x – The vector at which to evaluate the Hessian.

Returns:

The Hessian of the form as a LinearOperator mapping the domain to itself.

Raises:

NotImplementedError – If a Hessian function was not provided during initialization.

subgradient(x: Any) Vector[source]

Computes a subgradient of the form at a given point.

For convex functions, a subgradient g ∈ ∂f(x) satisfies:

f(y) ≥ f(x) + ⟨g, y - x⟩ for all y

At points where the function is differentiable, the subgradient equals the gradient. At non-smooth points, there may be multiple subgradients; this method returns one of them.

Parameters:

x – The vector at which to evaluate the subgradient.

Returns:

A subgradient of the form as a vector in the domain space.

Raises:

NotImplementedError – If a subgradient function was not provided during initialization.

pygeoinf.nonlinear_operators module

Provides the NonLinearOperator base class for mappings between Hilbert spaces.

A non-linear operator is a general mapping F(x) from a vector x in a domain Hilbert space to a vector y in a codomain Hilbert space. This class provides a foundational structure for these mappings, equipping them with algebraic operations and an interface for the Frécher derivative.

class pygeoinf.nonlinear_operators.NonLinearOperator(domain: HilbertSpace, codomain: HilbertSpace, mapping: Callable[[Vector], Any], /, *, derivative: Callable[[Vector], LinearOperator] = None)[source]

Bases: NonLinearOperatorAxiomChecks

Represents a general non-linear operator that maps vectors to vectors.

This class provides a functional representation for an operator F(x), and includes an interface for its Fréchet derivative, F’(x), which is the linear operator that best approximates F at a given point x. It serves as the base class for the more specialized LinearOperator.

property codomain: HilbertSpace

The codomain of the operator.

derivative(x: Vector) LinearOperator[source]

Computes the Fréchet derivative of the operator at a given point.

The Fréchet derivative is the linear operator that best approximates the non-linear operator in the neighborhood of the point x.

Parameters:

x – The point at which to compute the derivative.

Returns:

The derivative as a LinearOperator.

Raises:

NotImplementedError – If a derivative function was not provided.

property domain: HilbertSpace

The domain of the operator.

property has_derivative: bool

Returns true if the operators derivative is implemented.

property is_automorphism: bool

True if the operator maps a space into itself.

property is_square: bool

True if the operator’s domain and codomain have the same dimension.

pygeoinf.nonlinear_optimisation module

Module for solution of non-linear inverse and inference problems based on optimisation methods.

class pygeoinf.nonlinear_optimisation.ScipyUnconstrainedOptimiser(method: str, /, **kwargs: Any)[source]

Bases: object

A wrapper for scipy.optimize.minimize that adapts a NonLinearForm.

Note on derivative-free methods: Internal testing has shown that the ‘Nelder-Mead’ solver can be unreliable for some problems, failing to converge to the correct minimum while still reporting success. The ‘Powell’ method appears to be more robust. Users should exercise caution and verify results when using derivative-free methods.

minimize(form: NonLinearForm, x0: Vector) Vector[source]

Finds the minimum of a NonLinearForm starting from an initial guess.

Parameters:
  • form (NonLinearForm) – The non-linear functional to minimize.

  • x0 (Vector) – The initial guess in the Hilbert space.

Returns:

The vector that minimizes the form.

Return type:

Vector

Wrapper for the scipy line_search method for application to a non-linear form.

Parameters:
  • form (NonLinearForm) – The non-linear functional to minimize.

  • xk (Vector) – The current point.

  • pk (Vector) – The search direction.

  • gfk (Vector, optional) – The gradient at x=xk. If not provided will be recalculated.

  • old_fval (float, optional) – The function value at x=xk. If not provided will be recalculated.

  • old_old_fval (float, optional) – The valur at the point proceeding x=xk.

  • c1 (float, optional) – Parameter for Armijo condition rule.

  • c2 (float, optional) – Parameter for curvature condition rule.

  • amax (float, optional) – Maximum step size.

  • extra_condition (callable, optional) – A callable of the form extra_condition(alpha, x, f, g) returning a boolean. Arguments are the proposed step alpha and the corresponding x, f and g values. The line search accepts the value of alpha only if this callable returns True. If the callable returns False for the step length, the algorithm will continue with new iterates. The callable is only called for iterates satisfying the strong Wolfe conditions.

  • maxiter (int, optional) – Maximum number of iterations to perform.

Returns:

Alpha for which x_new = x0 + alpha * pk, or None if the

line search algorithm did not converge.

fc (int): Number of function evaluations made. gc (int): Numner of gradient evaluations mades. new_fval (float | None): New function value f(x_new)=f(x0+alpha*pk), or

None if the line search algorithm did not converge.

old_fval (float): Old function value f(x0). new_slope (float | None): The local slope along the search direction at

the new value <myfprime(x_new), pk>, or None if the line search algorithm did not converge.

Return type:

alpha (float | None)

Raises:

ValueError – If the non-linear form does not have a gradient set.

pygeoinf.parallel module

A collection of helper functions for parallel computation using Joblib.

These functions are designed to be top-level to ensure they can be “pickled” (serialized) and sent to worker processes by libraries like multiprocessing or its wrapper, Joblib.

pygeoinf.parallel.parallel_compute_dense_matrix_from_scipy_op(scipy_op: ScipyLinOp, n_jobs: int = -1) np.ndarray[source]

Computes the dense matrix representation of a scipy.LinearOperator in parallel.

It builds the matrix column by column by applying the operator to each basis vector.

Parameters:
  • scipy_op – The SciPy LinearOperator wrapper for the matrix action.

  • n_jobs – The number of CPU cores to use. -1 means all available.

Returns:

The dense matrix as a NumPy array.

pygeoinf.parallel.parallel_mat_mat(A: MatrixLike, B: np.ndarray, n_jobs: int = -1) np.ndarray[source]

Computes the matrix product A @ B in parallel by applying A to each column of B.

This is particularly useful when A is a LinearOperator whose action is computationally expensive.

Parameters:
  • A – The matrix or LinearOperator to apply.

  • B – The matrix whose columns will be operated on.

  • n_jobs – The number of CPU cores to use. -1 means all available.

Returns:

The result of the matrix product A @ B as a dense NumPy array.

pygeoinf.plot module

Plotting module for pygeoinf measures and distributions.

class pygeoinf.plot.SubspaceSlicePlotter(subset: Subset, on_subspace: AffineSubspace, grid_size: int = 200, rtol: float = 1e-06, alpha: float = 0.5, bar_pixel_height: int = 6)[source]

Bases: object

Plotter for visualizing subsets sliced along 1D, 2D, or 3D affine subspaces.

Fully implemented for 1D, 2D, and 3D subspaces via three rendering paths:

  • PolyhedralSet → exact affine slice via scipy.spatial.HalfspaceIntersection + convex hull; payload is vertex array (n_vertices, n_dims).

  • Ball / Ellipsoid → exact quadratic slice via Cholesky-factored pullback metric; no grid sampling is performed:

    • 1D slice: payload is np.array([lo, hi]) — the two interval endpoints.

    • 2D slice: payload is boundary points, shape (N, 2) (N 360).

    • 3D slice: payload is surface points, shape (N_pts, 3).

    An empty or degenerate slice raises ValueError explicitly.

  • All other sets → raster oracle sampling on a grid_size^n grid; payload is boolean membership mask. For 3D, the mask is rendered as filled voxels using Matplotlib’s mpl_toolkits.mplot3d backend (Axes3D.voxels()).

Architecture:

  • Common methods (parse_bounds, embed_point, sample_membership) work for 1D, 2D, and 3D.

  • Dimension-specific _render_*() methods handle visualization.

embed_point(params: float | Tuple[float, ...] | List[float]) object[source]

Map parameter(s) to ambient point using tangent basis.

Universal formula (works for any dimension): x = translation + sum(params[i] * tangent_basis[i])

Parameters:
  • params

  • 1D (-) – Single float

  • 2D (-) – 2-tuple (u, v)

  • 3D (-) – 3-tuple (u, v, w)

Returns:

Ambient point as Vector

parse_bounds(bounds: tuple | List | None) tuple[source]

Parse and validate bounds for current dimension.

Flexible input format handling: - None: Use default [-1, 1] per dimension - 1D: (u_min, u_max) - 2D: (u_min, u_max, v_min, v_max) OR ((u_min, u_max), (v_min, v_max)) - 3D: (u_min, u_max, v_min, v_max, w_min, w_max) OR

((u_min, u_max), (v_min, v_max), (w_min, w_max))

Parameters:

bounds – User-provided bounds or None

Returns:

  • 1D: (u_min, u_max)

  • 2D: (u_min, u_max, v_min, v_max)

  • 3D: (u_min, u_max, v_min, v_max, w_min, w_max)

Return type:

Normalized tuple

Raises:

ValueError – If bounds format doesn’t match dimension

plot(bounds: tuple | List | None = None, cmap: str = 'Blues', color: str = 'steelblue', show_plot: bool = True, ax: Axes | None = None, backend: str = 'auto') tuple[source]

Main plotting method. Orchestrates bounds parsing, grid generation, membership sampling, and dimension-specific rendering.

Parameters:
  • bounds – Plot bounds (format depends on dimension)

  • cmap – Colormap for 2D/3D (ignored for 1D)

  • color – Color for 1D (ignored for 2D/3D)

  • show_plot – Whether to display the plot

  • ax – Optional existing Matplotlib Axes (must be None when backend='plotly').

  • backend – Rendering backend — "auto" (default), "matplotlib", or "plotly". "auto" selects Plotly for 3D when it is installed and falls back to Matplotlib otherwise. 1D/2D always use Matplotlib regardless of the backend value.

Returns:

(fig, ax, payload) tuple.

When the Matplotlib backend is used fig is a matplotlib.figure.Figure and ax is a Matplotlib Axes. When the Plotly backend is used fig is a plotly.graph_objects.Figure and ax is None.

payload semantics depend on the rendering path:

  • PolyhedralSet (exact affine path): vertex array in parameter coords, shape (n_vertices, n_dims).

  • Ball / Ellipsoid (exact quadratic path): interval endpoints [lo, hi] for 1D; boundary points (N, 2) for 2D; surface points (N_pts, 3) for 3D. Empty slices raise ValueError.

  • All other sets (sampled path): boolean membership mask, shape (grid_size,) in 1D, (grid_size, grid_size) in 2D, or (grid_size, grid_size, grid_size) in 3D.

Raises:

ValueError – If ax is not None when backend='plotly'.

sample_membership(param_grid: ndarray | Tuple[ndarray, ...]) ndarray[source]

Evaluate subset membership on parameter grid.

For each grid point, converts parameter coordinates to ambient space via embed_point(), then tests membership using subset.is_element().

Parameters:

param_grid – Parameter grid from _generate_param_grid()

Returns:

  • 1D: shape (grid_size,)

  • 2D: shape (grid_size, grid_size)

  • 3D: shape (grid_size, grid_size, grid_size)

Return type:

Boolean mask array

pygeoinf.plot.plot_1d_distributions(posterior_measures: GaussianMeasure | Any | List[GaussianMeasure | Any], /, *, prior_measures: GaussianMeasure | Any | List[GaussianMeasure | Any] | None = None, true_value: float | None = None, show_true_value_in_legend: bool = False, ax: Axes | None = None, xlabel: str = 'Property Value', title: str = 'Prior and Posterior Probability Distributions', prior_labels: str | List[str] | None = None, posterior_labels: str | List[str] | None = None, width_scaling: float = 6.0, legend_position: tuple = (0.95, 0.95), fill_density: bool = False, **kwargs) Axes | Tuple[Axes, Axes][source]

Plot 1D probability distributions for prior and posterior measures using dual y-axes.

Parameters:
  • posterior_measures – Single measure or list of measures for posterior distributions

  • prior_measures – Single measure or list of measures for prior distributions (optional)

  • true_value – True value to mark with a vertical line (optional)

  • ax – An existing Matplotlib Axes object. If None, plots to the current active axes.

  • xlabel – Label for x-axis

  • title – Title for the plot

  • prior_labels – Manual labels for prior distributions (optional)

  • posterior_labels – Manual labels for posterior distributions (optional)

  • width_scaling – Width scaling factor in standard deviations (default: 6.0)

  • legend_position – Position of legend as (x, y) tuple (default: (0.95, 0.95))

  • fill_density – Whether to fill the area under the PDF curves (default: False)

  • **kwargs – Additional kwargs (e.g., figsize) safely ignored or forwarded.

Returns:

ax1 (if no priors) or (ax1, ax2) if dual axes are used.

pygeoinf.plot.plot_corner_distributions(posterior_measure: GaussianMeasure, /, *, prior_measure: GaussianMeasure | None = None, true_values: List[float] | ndarray | None = None, show_true_value_in_legend: bool = False, labels: List[str] | None = None, title: str = 'Joint Posterior Distribution', figsize: tuple | None = None, colormap: str = 'Blues', contour_color: str = 'darkblue', parallel: bool = False, n_jobs: int = -1, width_scaling: float = 3.75, legend_position: tuple = (0.9, 0.95), fill_density: bool = False, num_sigmas: int = 3) ndarray[source]

Create a professional corner plot for multi-dimensional posterior distributions.

Parameters:
  • posterior_measure – Multi-dimensional posterior measure (pygeoinf GaussianMeasure)

  • prior_measure – Optional prior measure to plot secondary axes showing prior standard deviations.

  • true_values – True values for each dimension (optional)

  • labels – Labels for each dimension (optional)

  • title – Title for the plot

  • figsize – Figure size tuple (if None, calculated based on dimensions)

  • colormap – Colormap for 2D plots (used when fill_density=True)

  • contour_color – Uniform color for the 2D contour lines (used when fill_density=False)

  • parallel – Compute dense covariance matrix in parallel, default False.

  • n_jobs – Number of cores to use in parallel calculations, default -1.

  • width_scaling – Width scaling factor in standard deviations for default boundaries (default: 3.75)

  • legend_position – Position of legend as (x, y) tuple (default: (0.9, 0.95))

  • fill_density – Whether to fill the 2D contour background with color. False is recommended for sparse truth values.

  • num_sigmas – Minimum number of standard deviation contours to draw (dynamically scales up to enclose true values).

Returns:

An N x N NumPy array of Matplotlib Axes objects.

Return type:

axes

pygeoinf.plot.plot_slice(subset: Subset, on_subspace: AffineSubspace, bounds=None, grid_size: int = 200, rtol: float = 1e-06, alpha: float = 0.5, cmap: str = 'Blues', color: str = 'steelblue', show_plot: bool = True, ax=None, backend: str = 'auto') Tuple[Any, Axes | None, ndarray][source]

Convenience wrapper: slice a subset along a 1D, 2D, or 3D affine subspace and plot.

Thin wrapper over SubspaceSlicePlotter. See that class for full documentation on the bounds format and return-value semantics.

Parameters:
  • subset – The Subset to visualize (domain must be EuclideanSpace).

  • on_subspace – A 1D, 2D, or 3D AffineSubspace to slice along.

  • bounds – Plot bounds — passed directly to SubspaceSlicePlotter.plot().

  • grid_size – Samples per axis (passed to SubspaceSlicePlotter).

  • rtol – Oracle tolerance (passed to SubspaceSlicePlotter).

  • alpha – Fill transparency (passed to SubspaceSlicePlotter).

  • cmap – Colormap for 2D/3D plots.

  • color – Color string for 1D plots.

  • show_plot – Whether to call plt.show().

  • ax – Optional existing Axes (or Axes3D) to draw into.

  • backend – Rendering backend — "auto" (default), "matplotlib", or "plotly". "auto" prefers Plotly for 3D when it is installed and warns then falls back to Matplotlib otherwise; 1D/2D always use Matplotlib.

Returns:

(fig, ax, payload) — identical to SubspaceSlicePlotter.plot().

payload semantics depend on set type and dimension:

  • Sampled path (non-PolyhedralSet): boolean membership mask.

    • 1D: shape (grid_size,)

    • 2D: shape (grid_size, grid_size)

    • 3D: shape (grid_size, grid_size, grid_size)mask[i, j, k] is True when the point at local parameter coordinates (u[i], v[j], w[k]) lies inside the subset.

  • Exact path (PolyhedralSet): vertex array in parameter coordinates.

    • 1D: np.array([u_lo, u_hi]) — interval endpoints

    • 2D: shape (n_vertices, 2) — polygon vertices

    • 3D: shape (n_vertices, 3) — polytope vertices

For 3D subspaces using backend='matplotlib' (or backend='auto' when Plotly is not installed), fig is a matplotlib.figure.Figure and ax is an Axes3D instance. For 3D subspaces using backend='plotly' (or backend='auto' when Plotly is installed), fig is a plotly.graph_objects.Figure and ax is None.

Raises:
  • TypeError – If subset.domain is not an EuclideanSpace.

  • ValueError – If bounds format is incompatible with the subspace dimension, or if grid_size, rtol, or alpha are out of range.

pygeoinf.preconditioners module

class pygeoinf.preconditioners.BandedPreconditioningMethod(bandwidth: int, /, *, incomplete: bool = False, drop_tol: float = 0.0001, fill_factor: float = 10.0, parallel: bool = False, n_jobs: int = -1)[source]

Bases: LinearSolver

A LinearSolver wrapper that generates a symmetrically banded sparse preconditioner.

Extracts a symmetric band of diagonals from the operator’s Galerkin matrix representation, constructs a sparse matrix, and uses a sparse direct solver (exact or incomplete LU) to invert it.

class pygeoinf.preconditioners.ColumnThresholdedPreconditioningMethod(threshold: float, /, *, max_nnz: int | None = None, galerkin: bool = True, incomplete: bool = False, drop_tol: float = 0.0001, fill_factor: float = 10.0, parallel: bool = False, n_jobs: int = -1)[source]

Bases: LinearSolver

A LinearSolver wrapper that generates a sparse preconditioner by evaluating the operator column-by-column, dropping elements that are small relative to the diagonal element, and optionally capping the maximum number of retained non-zeros per column.

class pygeoinf.preconditioners.ExactBlockPreconditioningMethod(blocks: list[list[int]], /, *, galerkin: bool = True, incomplete: bool = False, drop_tol: float = 0.0001, fill_factor: float = 10.0, parallel: bool = False, n_jobs: int = -1)[source]

Bases: LinearSolver

A LinearSolver wrapper that generates a sparse block preconditioner using exact matrix-vector evaluations.

Explicitly probes the operator with basis vectors but only retains the entries specified by the interaction blocks. Factorizes the resulting sparse matrix using exact or incomplete LU.

class pygeoinf.preconditioners.IdentityPreconditioningMethod[source]

Bases: LinearSolver

A trivial preconditioning method that returns the Identity operator.

This acts as a “no-op” placeholder in the preconditioning framework, useful for benchmarking or default configurations.

class pygeoinf.preconditioners.IterativePreconditioningMethod(inner_solver: IterativeLinearSolver, max_inner_iter: int = 5, rtol: float = 0.1)[source]

Bases: LinearSolver

Wraps an iterative solver to act as a preconditioner.

This is best used with FCGSolver to handle the potential variability of the inner iterations.

class pygeoinf.preconditioners.JacobiPreconditioningMethod(num_samples: int | None = 20, method: str = 'variable', rtol: float = 0.01, block_size: int = 10, parallel: bool = False, n_jobs: int = -1)[source]

Bases: LinearSolver

A LinearSolver wrapper that generates a Jacobi preconditioner.

class pygeoinf.preconditioners.SpectralPreconditioningMethod(*, damping: float | None = None, rank: int = 20, method: str = 'variable', max_rank: int | None = None, power: int = 2, rtol: float = 0.0001, block_size: int = 10, parallel: bool = False, n_jobs: int = -1)[source]

Bases: LinearSolver

A LinearSolver wrapper that generates a spectral (low-rank) preconditioner.

This preconditioner uses a randomized eigendecomposition to invert the dominant modes of the operator. The unresolved tail is regularized using a damping parameter.

pygeoinf.quadratic_form_quantile module

Weighted chi-square distribution: CDF and quantile.

This module implements numerical methods for the distribution of

Q = sum_j w_j Z_j^2, Z_j ~ iid N(0, 1), w_j > 0,

which arises when calibrating Gaussian credible sets in function spaces. With weights $w_j = lambda_j$ (eigenvalues of the covariance) the distribution governs ambient norm balls; with $w_j = lambda_j^{1-theta}$ it governs weakened-covariance ellipsoids.

The mathematical background is in docs/agent-docs/theory/function-space-hardening.md, section 4.

Public functions

weighted_chi2_cdf : evaluate $P(Q le t)$. weighted_chi2_quantile : invert the CDF for given probability $p$.

Both accept a method argument selecting between

  • "auto" (default): Automatically selects between "saddlepoint" and "imhof" based on spectrum isotropy (effective degrees of freedom $nu_{text{eff}} = (sum w)^2 / sum w^2$) and the caller’s requested relative tolerance tol. Saddlepoint is used when its expected error is well below tol; Imhof is used otherwise.

  • "imhof": Imhof’s numerical inversion of the characteristic function. Exact to quadrature tolerance.

  • "ws": Welch–Satterthwaite moment-match to a scaled $chi^2_nu$. Closed form; exact when all weights are equal; degrades with anisotropy.

  • "saddlepoint": Lugannani–Rice saddlepoint approximation. Excellent in deep tails.

  • "mc": Monte Carlo empirical quantile / CDF. Unbiased reference.

The tol parameter (default 1e-2) sets the desired relative accuracy of the result and is used only by "auto" mode to pick a method. When an explicit method is specified, tol is ignored.

pygeoinf.quadratic_form_quantile.weighted_chi2_cdf(weights: ndarray, t: float | ndarray, *, method: str = 'auto', tol: float = 0.01, rtol: float = 1e-08, n_samples: int = 100000, rng: Generator | None = None) float | ndarray[source]

Evaluate $P(Q le t)$ for $Q = sum_j w_j Z_j^2$.

Parameters:
  • weights – Non-negative weights $w_j$. Length-zero or all-zero arrays are treated as the degenerate point mass at 0.

  • t – Threshold(s); scalar or array. Returns matching shape.

  • method – One of {“auto”, “imhof”, “ws”, “saddlepoint”, “mc”}. "auto" selects saddlepoint or Imhof based on spectrum isotropy and tol.

  • tol – Desired relative accuracy of the result. Used only when method="auto" to decide whether saddlepoint is accurate enough or Imhof is required. Ignored for explicit methods.

  • rtol – Relative quadrature tolerance for Imhof’s integral.

  • n_samples – Sample count for Monte Carlo. Ignored by other methods.

  • rng – Optional NumPy generator for Monte Carlo. Defaults to np.random.default_rng().

Returns:

$P(Q le t)$ as a float (if t is scalar) or numpy array.

Raises:

ValueError – For unknown method, negative weights, or NaN inputs.

pygeoinf.quadratic_form_quantile.weighted_chi2_quantile(weights: ndarray, probability: float, *, method: str = 'auto', tol: float = 0.01, rtol: float = 1e-06, n_samples: int = 100000, rng: Generator | None = None) float[source]

Return $r$ such that $P(sum_j w_j Z_j^2 le r) = $ probability.

Parameters:
  • weights – Non-negative weights $w_j$.

  • probability – Target probability, strictly between 0 and 1.

  • method – One of {“auto”, “imhof”, “ws”, “saddlepoint”, “mc”}. "auto" (default) selects between saddlepoint and Imhof based on spectrum isotropy and tol: saddlepoint is used when its expected error (empirically $approx 0.1/nu_{text{eff}}$ with $nu_{text{eff}} = (sum w)^2/sum w^2$) is comfortably below tol; Imhof is used otherwise.

  • tol – Desired relative accuracy of the result. Used only when method="auto"; ignored for explicit method choices.

  • rtol – Relative tolerance for CDF root-finding (Imhof, saddlepoint).

  • n_samples – Sample count for Monte Carlo.

  • rng – Optional NumPy generator for Monte Carlo.

Returns:

The quantile $r$.

Raises:

ValueError – For invalid probability, unknown method, or negative weights.

pygeoinf.spectral_operator module

Spectral functional calculus for self-adjoint operators.

Given an approximate eigendecomposition $A approx U Lambda U^*$ of a self-adjoint operator on a Hilbert space, this module builds a matrix-free LinearOperator representing $f(A) approx U f(Lambda) U^*$ for any real-valued function $f$ on the spectrum. The intended use case is covariance-derived ellipsoid metrics in pygeoinf.gaussian_measure, specifically the family of fractional powers $C^theta$ for $theta in mathbb{R}$.

The construction mirrors pygeoinf.low_rank.LowRankEig but lifts an arbitrary function $f$ rather than a fixed identity on the eigenvalues. For the standard square-root, inverse, and inverse-square-root operators this is more flexible than the diagonal functional calculus on DiagonalSparseMatrixLinearOperator, which only handles the natural coefficient basis.

Mathematical background: see docs/agent-docs/theory/function-space-hardening.md section 3 and the sequel.

class pygeoinf.spectral_operator.SpectralFractionalOperator(u_op: LinearOperator, eigenvalues: ndarray | ArrayLike, func: Callable[[ndarray], ndarray])[source]

Bases: LinearOperator

Matrix-free $f(A)$ from an eigendecomposition $A = U Lambda U^*$.

The operator acts as

$$

f(A), v = U,mathrm{diag}(f(lambda_1), ldots, f(lambda_k)), U^* v.

$$

When $U$ is an isometry onto $mathrm{ran}(A)$ this is the standard functional calculus restricted to that range; outside the range the operator returns zero, consistent with $f(0) cdot 0 = 0$ for typical fractional powers.

Parameters:
  • u_op – A LinearOperator mapping the coefficient space ($mathbb{R}^k$ for some rank $k$) to the ambient Hilbert space $H$.

  • eigenvalues – A 1-D array of $k$ eigenvalues.

  • func – A vectorised function that maps the eigenvalue array to the transformed-eigenvalue array. For fractional powers, func = lambda x: x ** theta.

Raises:

ValueError – if eigenvalues length does not match u_op.domain.dim.

coefficients(v) ndarray[source]

Return $U^* v$ as a NumPy array.

property diagonal_operator: DiagonalSparseMatrixLinearOperator

The diagonal operator $mathrm{diag}(f(lambda_j))$ on $mathbb{R}^k$.

classmethod from_callable(u_op: LinearOperator, eigenvalues: ndarray, func: Callable[[ndarray], ndarray]) SpectralFractionalOperator[source]

Build $f(A)$ from explicit factors and a callable.

This is the lowest-level constructor; the typical user wants from_low_rank_eig().

classmethod from_low_rank_eig(eig: LowRankEig, power: float, *, regularization: float = 0.0) SpectralFractionalOperator[source]

Build $A^{text{power}}$ from a LowRankEig decomposition.

For power < 0, the eigenvalues must be strictly positive — in practice $A$ is the covariance and the trailing eigenvalues from a randomised decomposition can be numerically zero or tiny. The regularization argument adds a constant offset to the eigenvalues before applying the power, to avoid blow-up.

Parameters:
  • eig – A LowRankEig instance, typically obtained via LowRankEig.from_randomized(covariance, ...).

  • power – The real exponent.

  • regularization – Constant added to every eigenvalue before the power; ignored if power >= 0.

Returns:

A SpectralFractionalOperator acting as $A^{text{power}}$ on the range of eig.u_factor.

quadratic_form_squared(v) float[source]

Return $langle v, f(A), vrangle_H$ via the spectral expansion.

Equals $sum_j f(lambda_j), c_j^2$ where $c = U^* v$ is the coefficient vector. Uses the underlying u_op.adjoint to compute coefficients, avoiding the extra round-trip through u_op.

property rank: int

Number of eigenvalue/eigenvector pairs retained.

property raw_eigenvalues: ndarray

The input eigenvalues $lambda_j$ (before $f$).

property transformed_eigenvalues: ndarray

The output eigenvalues $f(lambda_j)$.

property u_factor: LinearOperator

Eigenvectors $U$ as a LinearOperator $mathbb{R}^k to H$.

pygeoinf.spectral_operator.fractional_operators_from_eig(eig: LowRankEig, theta: float, *, regularization: float | None = None) tuple[SpectralFractionalOperator, SpectralFractionalOperator, SpectralFractionalOperator][source]

Convenience triple for the weakened-ellipsoid construction.

Returns (A, A_inv, A_inv_sqrt) corresponding to $C^{-theta}$, $C^{theta}$, $C^{theta/2}$ respectively. These are exactly the operator/inverse/inverse-sqrt arguments that pygeoinf.subsets.Ellipsoid needs to support its quadratic form and support-function evaluations.

Parameters:
  • eig – Eigendecomposition of the covariance $C$.

  • theta – Fractional power; expected in $(0, 1)$.

  • regularization – Constant offset added to eigenvalues before applying negative powers. Defaults to a relative floor of 1e-12 times the largest eigenvalue.

pygeoinf.subsets module

Defines classes for representing subsets of a Hilbert space.

This module provides a hierarchy of classes for sets, ranging from abstract definitions to concrete geometric shapes. It supports Constructive Solid Geometry (CSG) operations, with specialized handling for convex intersections via functional combination.

Hierarchy: - Subset (Abstract Base)

  • EmptySet / UniversalSet

  • LevelSet (f(x) = c)
    • EllipsoidSurface -> Sphere

  • SublevelSet (f(x) <= c)
    • ConvexSubset -> Ellipsoid -> Ball

    • ConvexIntersection (Max-Functional Combination)

  • Intersection (Generic)

  • Union (Generic)

  • Complement (S^c)

class pygeoinf.subsets.Ball(domain: HilbertSpace, center: Vector, radius: float, open_set: bool = True)[source]

Bases: Ellipsoid

Represents a ball in a Hilbert space: B = {x | ||x - c||^2 <= r^2}. This is an Ellipsoid where A is the Identity operator.

property boundary: Subset

Returns the Sphere bounding this Ball.

directional_bound(direction: Vector) tuple['Vector', float][source]

Returns extreme point in direction of ‘direction’.

For a ball B(c, r) and direction q:

x_max = c + r * (q / ||q||) h(q) = ⟨q, c⟩ + r||q||

Parameters:

direction – The direction vector q.

Returns:

(x_max, h(q))

Return type:

tuple[Vector, float]

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if x lies within the ball. Optimized to use geometric distance ||x-c|| directly.

property support_function: SupportFunction

Returns the support function object for this ball.

Always available for valid Ball instances.

class pygeoinf.subsets.Complement(subset: Subset)[source]

Bases: Subset

Represents the complement of a set: S^c = {x | x ∉ S}.

property boundary: Subset

Returns the boundary of the complement.

Ideally, ∂(S^c) = ∂S.

property complement: Subset

Returns the complement of the complement, which is the original set. (S^c)^c = S.

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if x is NOT in the underlying subset.

class pygeoinf.subsets.ConvexIntersection(subsets: Iterable[ConvexSubset])[source]

Bases: ConvexSubset

Represents the intersection of multiple convex sets as a single convex set.

This class combines the defining functionals of its components into a single max-functional: F(x) = max_i (f_i(x) - c_i). The intersection is then defined as {x | F(x) <= 0}.

This allows the intersection to be treated as a standard ConvexSubset for optimization algorithms, providing gradients and Hessians of the active constraint, while preserving access to the individual constraints.

directional_bound(direction: Vector) tuple['Vector', float][source]

Directional bound for an intersection requires solving a constrained maximization problem.

In general, picking the component set with the smallest support value is NOT correct, because the corresponding support point need not satisfy the other constraints.

Use:
  • support_upper_bound(direction) for a safe upper bound

  • feasible_lower_bound(direction) for a safe (feasible) lower bound

  • or supply/implement an optimizer to solve the true problem.

feasible_lower_bound(direction: Vector, /, *, rtol: float = 1e-06) tuple['Vector' | None, float][source]

Feasible lower bound on the true intersection support.

Strategy:

Try candidate maximizers from each component set (their directional_bound). Keep the best candidate that is feasible for ALL constraints (i.e., belongs to the intersection).

This is always safe (never overestimates), but may be loose.

Returns:

  • x_best is a feasible candidate point (or None if none found)

  • value_best = ⟨direction, x_best⟩, or -∞ if none found

Return type:

(x_best, value_best)

property subsets: List[ConvexSubset]

Direct access to the individual convex constraints.

property support_function: 'SupportFunction' | None

Support function of an intersection is not generally available.

In general,

σ_{∩_i C_i}(q) != min_i σ_{C_i}(q).

The pointwise minimum of support functions is typically NOT sublinear, hence not a valid support function of any closed convex set.

Returns:

by default. Use support_upper_bound(direction) for a safe upper bound, or provide an optimizer to compute the true support.

Return type:

None

support_upper_bound(direction: Vector) float[source]

Safe upper bound for the true intersection support.

Because ∩_i C_i ⊆ C_i for each i, we always have

σ_{∩_i C_i}(q) ≤ min_i σ_{C_i}(q).

Returns:

min_i σ_{C_i}(direction)

Return type:

float

Raises:

ValueError – if any component subset lacks a support function.

class pygeoinf.subsets.ConvexSubset(form: NonLinearForm, level: float, open_set: bool = False, support_fn: 'SupportFunction' | None = None)[source]

Bases: SublevelSet

Represents a closed convex set via dual representations.

A closed convex set can be equivalently defined by: 1. A sublevel set: S = {x | f(x) <= c} where f is convex 2. Its support function: h(q) = sup{⟨q, x⟩ : x ∈ S}

This class supports both representations. The support function is abstract and must be implemented by all concrete subclasses (Ball, Ellipsoid, etc.).

check(n_samples: int = 10, /, *, rtol: float = 1e-05, atol: float = 1e-08) None[source]

Performs a randomized check of the convexity inequality: f(tx + (1-t)y) <= t*f(x) + (1-t)*f(y)

Parameters:
  • n_samples – Number of random pairs to test.

  • rtol – Relative tolerance.

  • atol – Absolute tolerance.

Raises:

AssertionError – If the function is found to be non-convex.

closure() ConvexSubset[source]

Returns the closure of this convex set.

For a convex set S: - If S is already closed ({f <= c}), returns self (no copy needed). - If S is open ({f < c}), returns a new closed version.

The closure is the smallest closed set containing S. Note: The returned object uses the same functional and level as self, with open_set flag set to False.

Returns:

A ConvexSubset instance (subclass) representing cl(S).

Return type:

ConvexSubset

abstract directional_bound(direction: Vector) tuple['Vector', float][source]

Returns extreme point and support value in a given direction.

For a convex set S and direction q, computes:

x_max = argmax{⟨q, x⟩ : x ∈ S} h(q) = sup{⟨q, x⟩ : x ∈ S}

This method must be implemented by all concrete ConvexSubset subclasses.

Parameters:

direction – A vector q specifying the direction.

Returns:

(x_max, h(q)) where x_max achieves the supremum

and h(q) is the support function value.

Return type:

tuple[Vector, float]

Raises:

NotImplementedError – If not implemented for this set type.

property is_closed: bool

Returns True if the set is closed (defined by <=), False if open (<).

property support_fn: 'SupportFunction' | None

Access the stored support function object, if any.

abstract property support_function: 'SupportFunction' | None

Returns the SupportFunction instance for this set, or None if unavailable.

This property should not raise errors if the support function cannot be created (e.g., missing inverse operators for ellipsoids). The SupportFunction itself is responsible for raising errors when required inputs are missing during evaluation.

class pygeoinf.subsets.Ellipsoid(domain: HilbertSpace, center: Vector, radius: float, operator: LinearOperator, open_set: bool = False, *, inverse_operator: LinearOperator | None = None, inverse_sqrt_operator: LinearOperator | None = None)[source]

Bases: ConvexSubset, _EllipsoidalGeometry

Represents a solid ellipsoid: E = {x | <A(x-c), x-c> <= r^2}.

property boundary: Subset

Returns the boundary EllipsoidSurface.

directional_bound(direction: Vector) tuple['Vector', float][source]

Returns extreme point in given direction.

For an ellipsoid E(c, r, A) and direction q:

x_max = c + r * (A^{-1} q) / ||A^{-1/2} q|| h(q) = support_function(q)

Parameters:

direction – The direction vector q.

Returns:

(x_max, h(q))

Return type:

tuple[Vector, float]

property normalized: NormalisedEllipsoid

Returns a normalized version of this ellipsoid with radius 1. The operator is scaled by 1/r^2 to represent the same set.

property support_function: SupportFunction

Returns the support function object for this ellipsoid.

This property does not require inverse operators at construction time. The returned SupportFunction will raise errors if missing operators are required for evaluation.

class pygeoinf.subsets.EllipsoidSurface(domain: HilbertSpace, center: Vector, radius: float, operator: LinearOperator)[source]

Bases: LevelSet, _EllipsoidalGeometry

Represents the surface of an ellipsoid: S = {x | <A(x-c), x-c> = r^2}.

property boundary: Subset

Returns EmptySet (manifold without boundary).

property normalized: EllipsoidSurface

Returns a normalized version of this surface with radius 1.

class pygeoinf.subsets.EmptySet(domain: HilbertSpace | None = None)[source]

Bases: Subset

Represents the empty set (∅).

property boundary: Subset

The boundary of an empty set is the empty set itself.

property complement: Subset

The complement of the empty set is the whole space (Universal Set).

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns False for any vector.

property is_empty: bool

Returns True, as this is the empty set.

class pygeoinf.subsets.HalfSpace(domain: HilbertSpace, normal_vector: Vector, offset: float, inequality_type: str = '<=')[source]

Bases: Subset

Represents a half-space in a Hilbert space: H_+ = {x | ⟨a, x⟩ ≤ b}.

A half-space is an unbounded convex set defined by a linear inequality. It is the epigraph or hypograph of a linear functional, depending on the orientation of the normal vector.

property boundary: Subset

Returns the boundary of the half-space.

The boundary of {x | ⟨a, x⟩ ≤ b} is the hyperplane {x | ⟨a, x⟩ = b}.

Returns:

The boundary hyperplane.

Return type:

HyperPlane

distance_to(x: Vector) float[source]

Computes the perpendicular distance from x to the half-space boundary.

For a point inside the half-space, returns the distance to the boundary plane ⟨a, x⟩ = b. For points outside, distance is negative (by convention).

Distance = (⟨a, x⟩ - b) / ||a|| (unsigned distance to boundary plane)

Parameters:

x – A vector from the domain.

Returns:

The signed distance to the boundary plane:
  • Positive if x is on the “outside” (violating constraint)

  • Negative if x is inside the half-space

  • Zero if x is on the boundary

Return type:

float

property inequality_type: str

‘<=’ or ‘>=’.

Type:

The inequality type

is_bounded() bool[source]

Returns False, as half-spaces are unbounded convex sets.

A half-space extends to infinity in at least one direction.

Returns:

Always False for half-spaces.

Return type:

bool

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if x lies within the half-space.

For ‘<=’ type: checks if ⟨a, x⟩ ≤ b + rtol * max(1, |b|). For ‘>=’ type: checks if ⟨a, x⟩ ≥ b - rtol * max(1, |b|).

Parameters:
  • x – A vector from the domain.

  • rtol – Relative tolerance for the inequality check.

Returns:

True if x satisfies the half-space inequality.

Return type:

bool

property is_empty: bool

Returns False. A half-space is never empty.

Mathematically, any half-space {x | ⟨a, x⟩ ≤ b} or {x | ⟨a, x⟩ ≥ b} is non-empty in a Hilbert space.

Returns:

Always False.

Return type:

bool

property normal_norm: float

The Euclidean norm of the normal vector.

property normal_vector: Vector

The normal vector defining the half-space.

property offset: float

The offset scalar b in the definition ⟨a, x⟩ ≤ b or ⟨a, x⟩ ≥ b.

project(x: Vector) Vector[source]

Projects a point onto the half-space boundary.

This is the orthogonal projection onto the boundary hyperplane {z | ⟨a, z⟩ = b}, which is independent of inequality type.

Projection: x_proj = x - ((⟨a, x⟩ - b) / ||a||²) * a

Parameters:

x – A vector from the domain.

Returns:

The orthogonal projection of x onto the boundary plane.

Return type:

Vector

property support_function: HalfSpaceSupportFunction

Returns the support function for this half-space.

Note

Half-spaces are UNBOUNDED, so their support functions are infinite in SOME directions (depends on query q and inequality type). The support function may raise ValueError when unbounded. This property is provided for completeness and theoretical consistency with other convex sets.

Half-spaces are more useful as constraints in optimization (via their indicator functions) rather than through support functions.

Returns:

Support function (may raise

ValueError when called with unbounded directions).

Return type:

HalfSpaceSupportFunction

class pygeoinf.subsets.HyperPlane(domain: HilbertSpace, normal_vector: Vector, offset: float)[source]

Bases: Subset

Represents a hyperplane in a Hilbert space: H = {x | ⟨a, x⟩ = b}.

A hyperplane is a flat affine subspace of codimension 1, defined by a normal vector ‘a’ and an offset scalar ‘b’.

property boundary: Subset

Returns the boundary of the hyperplane (itself). A hyperplane is ‘thin’ so its boundary is itself.

dimension() int[source]

Returns the geometric dimension of the hyperplane: dim(domain) - 1.

A hyperplane has codimension 1.

Returns:

The dimension of this hyperplane subspace.

Return type:

int

distance_to(x: Vector) float[source]

Computes the perpendicular distance from x to the hyperplane.

Distance = |⟨a, x⟩ - b| / ||a||

Parameters:

x – A vector from the domain.

Returns:

The perpendicular distance from x to this hyperplane.

Return type:

float

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if x lies on the hyperplane.

Checks if ⟨a, x⟩ ≈ b within relative tolerance.

Parameters:
  • x – A vector from the domain.

  • rtol – Relative tolerance for the equality check.

Returns:

True if |⟨a, x⟩ - b| ≤ rtol * max(1, |b|).

Return type:

bool

property normal_norm: float

The Euclidean norm of the normal vector.

property normal_vector: Vector

The normal vector defining the hyperplane.

property offset: float

The offset scalar b in the definition ⟨a, x⟩ = b.

project(x: Vector) Vector[source]

Projects a point onto the hyperplane.

The projection is: x_proj = x - ((⟨a, x⟩ - b) / ||a||²) * a

Parameters:

x – A vector from the domain.

Returns:

The orthogonal projection of x onto this hyperplane.

Return type:

Vector

class pygeoinf.subsets.Intersection(subsets: Iterable[Subset])[source]

Bases: Subset

Represents the generic intersection of multiple subsets: S = S_1 ∩ S_2 …

Used when the subsets cannot be mathematically combined into a single functional (e.g., non-convex sets).

property boundary: Subset

Returns the boundary of the intersection.

The general topological boundary is complex: ∂(A ∩ B) ⊆ (∂A ∩ B) ∪ (A ∩ ∂B). Currently raises NotImplementedError.

property complement: Subset

Returns the complement of the intersection.

Applies De Morgan’s Law: (A ∩ B)^c = A^c ∪ B^c. Returns a Union of the complements.

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if x is in ALL component subsets.

property subsets: List[Subset]

Direct access to the component sets.

class pygeoinf.subsets.LevelSet(form: NonLinearForm, level: float)[source]

Bases: Subset

Represents a level set of a functional: S = {x | f(x) = c}.

property boundary: Subset

Returns the boundary of the level set. Assuming regularity, a level set is a closed manifold without boundary.

property form: NonLinearForm

The defining functional f(x).

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if f(x) is approximately equal to c. Tolerance is scaled by max(1.0, |c|).

property level: float

The scalar value c.

class pygeoinf.subsets.NormalisedEllipsoid(domain: HilbertSpace, center: Vector, operator: LinearOperator, open_set: bool = False)[source]

Bases: Ellipsoid

Represents a normalised ellipsoid with radius 1: E = {x | <A(x-c), x-c> <= 1}.

class pygeoinf.subsets.PolyhedralSet(domain: HilbertSpace, half_spaces: list['HalfSpace'])[source]

Bases: Subset

Represents a polyhedral set as the intersection of half-spaces.

P = {x | ⟨a_i, x⟩ ≤ b_i for all i} ∩ {x | ⟨a_j, x⟩ ≥ b_j for all j}

A polyhedral set is a closed, bounded or unbounded convex set defined as the intersection of finitely many half-spaces.

property boundary: Subset

Returns the boundary of the polyhedral set.

The boundary consists of faces where one or more constraints are active (⟨a_i, x⟩ = b_i). Computing the complete boundary is complex in general Hilbert spaces, so this raises NotImplementedError.

Raises:

NotImplementedError – General polyhedral boundary computation is not yet implemented for arbitrary Hilbert spaces.

property half_spaces: list[HalfSpace]

Returns the list of half-spaces defining this polyhedral set.

is_bounded() bool[source]

Check if the polyhedral set is bounded.

A polyhedral set is bounded iff it’s a polytope (all variables have finite bounds). This check is non-trivial in general Hilbert spaces and is not yet implemented.

Returns:

Raises NotImplementedError.

Return type:

bool

Raises:

NotImplementedError – Boundedness detection for general polyhedral sets is not yet implemented.

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Check if x belongs to the polyhedral set.

x ∈ P iff x satisfies all half-space constraints.

Parameters:
  • x – A vector from the domain.

  • rtol – Relative tolerance for constraint checks.

Returns:

True if x satisfies all half-space constraints.

Return type:

bool

property is_empty: bool

Check if the polyhedral set is empty.

A polyhedral set is empty iff the intersection of half-spaces is empty. This is a feasibility problem and requires LP techniques to determine precisely.

Returns:

Raises NotImplementedError.

Return type:

bool

Raises:

NotImplementedError – Emptiness checking for general polyhedral sets requires LP feasibility testing, not yet implemented.

property support_function: SupportFunction | None

Returns the support function of the polyhedral set.

For a polyhedral set P = ∩_i H_i (intersection of half-spaces), the support function is:

σ_P(q) = inf_{i} σ_{H_i}(q)

However, evaluating the infimum of support functions is complex and may require iterative methods (e.g., linear programming).

Returns:

Support function for general polyhedral sets is not yet

implemented. Consider using the half-space constraints directly in optimization or implementing LP-based evaluation.

Return type:

None

Note

For specific cases (e.g., bounded polytopes or simplices), specialized implementations are available in other modules.

class pygeoinf.subsets.Sphere(domain: HilbertSpace, center: Vector, radius: float)[source]

Bases: EllipsoidSurface

Represents a sphere in a Hilbert space: S = {x | ||x - c||^2 = r^2}. This is an EllipsoidSurface where A is the Identity operator.

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if ||x - c|| is approximately equal to r.

class pygeoinf.subsets.SublevelSet(form: NonLinearForm, level: float, open_set: bool = False)[source]

Bases: Subset

Represents a sublevel set defined by a functional: S = {x | f(x) <= c}.

This class serves as a base for sets defined by inequalities. Unlike ConvexSubset, it does not assume the defining functional is convex.

property boundary: Subset

Returns the boundary of the sublevel set. The boundary is typically the LevelSet {x | f(x) = c}.

property form: NonLinearForm

The defining functional f(x).

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if f(x) <= c (or < c). Tolerance is scaled by max(1.0, |c|).

property is_open: bool

True if the set is defined by strict inequality.

property level: float

The scalar upper bound c.

class pygeoinf.subsets.Subset(domain: HilbertSpace | None = None)[source]

Bases: ABC

Abstract base class for a subset of a HilbertSpace.

This class defines the minimal interface required for a mathematical set: knowing which space it lives in, determining if a vector belongs to it, accessing its boundary, and performing logical set operations.

abstract property boundary: Subset

Returns the boundary of the subset.

Returns:

A new Subset instance representing ∂S.

Return type:

Subset

property complement: Subset

S^c = {x | x not in S}.

Returns:

A generic Complement wrapper around this set.

Return type:

Complement

Type:

Returns the complement of this set

property domain: HilbertSpace

The underlying Hilbert space.

Returns:

The HilbertSpace instance associated with this subset.

Raises:

ValueError – If the domain was not set during initialization.

intersect(other: Subset) Subset[source]

Returns the intersection of this set and another: S ∩ O.

If both sets are instances of ConvexSubset, this returns a ConvexIntersection, which combines their functionals into a single convex constraint F(x) = max(f1(x), f2(x)).

Parameters:

other – Another Subset instance.

Returns:

A new set representing elements present in both sets.

Return type:

Subset

abstract is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if the vector x lies within the subset.

Parameters:
  • x – A vector from the domain.

  • rtol – Relative tolerance for floating-point comparisons (e.g., checking equality f(x) = c or inequality f(x) <= c).

Returns:

True if x ∈ S, False otherwise.

Return type:

bool

property is_empty: bool

Returns True if the set is known to be empty.

Returns:

True if the set contains no elements, False otherwise. Note that returning False does not guarantee the set is non-empty, only that it is not trivially known to be empty.

Return type:

bool

plot(on_subspace=None, *, bounds=None, grid_size: int = 200, rtol: float = 1e-06, alpha: float = 0.5, cmap: str = 'Blues', color: str = 'steelblue', show_plot: bool = True, ax=None, backend: str = 'auto')[source]

Visualize this subset along a 1D, 2D, or 3D affine subspace.

Delegates to pygeoinf.plot.plot_slice. For EuclideanSpace domains of dimension 1 or 2, on_subspace may be omitted and a canonical full-space AffineSubspace will be constructed automatically. For all other domains, on_subspace is required.

Parameters:
  • on_subspace – The AffineSubspace to slice along. Required unless the domain is a 1D or 2D EuclideanSpace.

  • bounds – Plot bounds passed to plot_slice.

  • grid_size – Samples per axis (passed to plot_slice).

  • rtol – Oracle tolerance (passed to plot_slice).

  • alpha – Fill transparency in [0, 1] (passed to plot_slice).

  • cmap – Colormap for 2D plots (passed to plot_slice).

  • color – Color string for 1D plots (passed to plot_slice).

  • show_plot – Whether to call plt.show() (passed to plot_slice).

  • ax – Optional existing Axes to draw into (passed to plot_slice).

  • backend – Rendering backend — "auto" (default), "matplotlib", or "plotly" (passed to plot_slice).

Returns:

(fig, ax, payload) — identical to plot_slice.

Raises:
  • ValueError – If on_subspace is None and the domain is not a 1D or 2D EuclideanSpace.

  • TypeError – If the domain is not an EuclideanSpace.

union(other: Subset) Union[source]

Returns the union of this set and another: S ∪ O.

Parameters:

other – Another Subset instance.

Returns:

A new set representing elements present in either set.

Return type:

Union

class pygeoinf.subsets.Union(subsets: Iterable[Subset])[source]

Bases: Subset

Represents the union of multiple subsets: S = S_1 ∪ S_2 …

property boundary: Subset

Returns the boundary of the union. Currently raises NotImplementedError.

property complement: Subset

Returns the complement of the union.

Applies De Morgan’s Law: (A ∪ B)^c = A^c ∩ B^c. Returns an Intersection of the complements.

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if x is in ANY of the component subsets.

property subsets: List[Subset]

Direct access to the component sets.

class pygeoinf.subsets.UniversalSet(domain: HilbertSpace | None = None)[source]

Bases: Subset

Represents the entire domain (Ω).

property boundary: Subset

The boundary of the entire topological space is empty.

property complement: Subset

The complement of the universe is the empty set.

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True for any vector in the domain.

pygeoinf.subspaces module

Defines classes for representing affine and linear subspaces, including hyperplanes and half-spaces.

The primary abstraction is the AffineSubspace, which represents a subset of a Hilbert space defined by a translation and a closed linear tangent space. This module integrates with the subset module, allowing subspaces to be treated as standard geometric sets.

class pygeoinf.subspaces.AffineSubspace(projector: OrthogonalProjector, translation: Vector | None = None, constraint_operator: LinearOperator | None = None, constraint_value: Vector | None = None, solver: LinearSolver | None = None, preconditioner: LinearOperator | None = None)[source]

Bases: Subset

Represents an affine subspace A = x0 + V.

This class serves two primary roles: 1. A geometric subset that can project points and check membership. 2. A constraint definition for Bayesian inversion (conditioning a Gaussian

measure on the subspace).

property boundary: Subset

Returns the boundary of the affine subspace.

Geometrically, an affine subspace (like a line or plane) is a closed manifold without a boundary. Returns EmptySet.

condition_gaussian_measure(prior: GaussianMeasure, geometric: bool = False) GaussianMeasure[source]

Conditions a Gaussian measure on this subspace.

Parameters:
  • prior – The prior Gaussian measure.

  • geometric – If True, performs a geometric projection of the measure (equivalent to conditioning on “measurement = truth” with infinite precision, effectively squashing the distribution onto the subspace). If False (default), performs standard Bayesian conditioning using the constraint equation B(u) = w.

Returns:

The posterior (conditioned) GaussianMeasure.

Raises:

ValueError – If geometric=False and the subspace was constructed without a solver capable of handling the constraint operator.

property constraint_operator: LinearOperator

Returns the operator B defining the subspace as {u | B(u)=w}.

If no explicit operator was provided (geometric construction), this falls back to the complement projector (I - P).

property constraint_value: Vector

Returns the value w defining the subspace as {u | B(u)=w}.

If no explicit operator was provided, this falls back to (I - P)x0.

classmethod from_complement_basis(domain: HilbertSpace, basis_vectors: List[Vector], translation: Vector | None = None, orthonormalize: bool = True) AffineSubspace[source]

Constructs a subspace defined by orthogonality to a set of complement basis vectors.

The subspace is defined as {u | <u - x0, v_i> = 0} for all v_i in basis. This provides an explicit constraint operator B where B(u)_i = <u, v_i>.

Parameters:
  • domain – The Hilbert space.

  • basis_vectors – Basis vectors for the orthogonal complement.

  • translation – A point x0 in the subspace.

  • orthonormalize – If True, orthonormalizes the complement basis.

classmethod from_hyperplanes(hyperplanes: List[Subset], solver: LinearSolver | None = None, preconditioner: LinearOperator | None = None) AffineSubspace[source]

Constructs an affine subspace as the intersection of hyperplanes.

Each hyperplane is defined as {x | ⟨a_i, x⟩ = b_i}. The intersection of m hyperplanes defines an affine subspace of codimension m (assuming the normal vectors are linearly independent).

Parameters:
  • hyperplanes – A list of HyperPlane objects from subsets module. All hyperplanes must have the same domain.

  • solver – Solver used to invert the Gram matrix (B B*) during construction. Defaults to CholeskySolver.

  • preconditioner – Optional preconditioner for iterative solvers.

Returns:

The affine subspace defined by the intersection.

Return type:

AffineSubspace

Raises:
  • ValueError – If hyperplanes list is empty or domains don’t match.

  • ImportError – If hyperplanes don’t have the required attributes.

classmethod from_linear_equation(operator: LinearOperator, value: Vector, solver: LinearSolver | None = None, preconditioner: LinearOperator | None = None) AffineSubspace[source]

Constructs a subspace defined by the linear equation B(u) = w.

Parameters:
  • operator – The linear operator B.

  • value – The RHS vector w.

  • solver – Solver used to invert the Gram matrix (B B*) during construction and later conditioning. Defaults to CholeskySolver.

  • preconditioner – Optional preconditioner for iterative solvers.

classmethod from_tangent_basis(domain: HilbertSpace, basis_vectors: List[Vector], translation: Vector | None = None, orthonormalize: bool = True, solver: LinearSolver | None = None, preconditioner: LinearOperator | None = None) AffineSubspace[source]

Constructs an affine subspace from a translation and a basis for the tangent space.

This method defines the subspace geometrically. The constraint is implicit: (I - P)u = (I - P)x0.

Parameters:
  • domain – The Hilbert space.

  • basis_vectors – Basis vectors for the tangent space V.

  • translation – A point x0 in the subspace.

  • orthonormalize – If True, orthonormalizes the basis.

  • solver – A linear solver capable of handling the singular operator (I-P). Required if you intend to use this subspace for Bayesian conditioning.

  • preconditioner – Optional preconditioner for the solver.

get_tangent_basis() List[Vector][source]

Returns an orthonormal basis for the tangent space of this affine subspace.

Extracts orthonormal basis vectors that span the tangent space $V$ by applying the projector $P$ to each standard basis vector $e_i$, then performing a tolerant Gram-Schmidt orthogonalisation to discard linearly dependent projections.

Returns:

Orthonormal basis vectors for the tangent space.

Return type:

List[Vector]

property has_explicit_equation: bool

True if defined by B(u)=w, False if defined only by geometry.

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if the vector x lies within the subspace.

Checks if the projection residual ||x - P_A(x)|| is small relative to the norm of x (or 1.0).

Parameters:
  • x – The vector to check.

  • rtol – Relative tolerance for the residual check.

property preconditioner: LinearOperator | None

Returns the preconditioner associated with the solver, if any.

project(x: Vector) Vector[source]

Orthogonally projects a vector x onto the affine subspace.

Formula: P_A(x) = P(x - x0) + x0

property projection_operator: AffineOperator

Returns the affine orthogonal projection operator onto the subspace. P_A(x) = P(x) + (I - P)x_0

property projector: OrthogonalProjector

Returns the orthogonal projector P onto the tangent space.

property pseudo_inverse: LinearOperator

Returns the right pseudo-inverse operator B^dagger = B* (B B*)^{-1}.

property solver: LinearSolver | None

Returns the linear solver associated with this subspace.

property tangent_space: LinearSubspace

Returns the LinearSubspace V parallel to this affine subspace.

to_hyperplanes() List[Subset][source]

Decomposes this affine subspace into a minimal set of hyperplanes.

Returns a list of HyperPlane objects whose intersection equals this affine subspace. The number of hyperplanes equals the codimension of the subspace (i.e., the rank of the constraint operator).

Returns:

The minimal set of hyperplanes defining this subspace.

Return type:

List[HyperPlane]

Raises:
  • ValueError – If the subspace does not have an explicit constraint operator.

  • ImportError – If HyperPlane class is not available.

property translation: Vector

Returns the translation vector x0.

with_constraint_value(new_value: Vector) AffineSubspace[source]

Returns a new AffineSubspace parallel to this one, shifted to satisfy the new explicit constraint equation B(u) = new_value.

with_translation(new_translation: Vector) AffineSubspace[source]

Returns a new AffineSubspace parallel to this one, shifted to pass through the new translation vector.

class pygeoinf.subspaces.LinearSubspace(projector: OrthogonalProjector)[source]

Bases: AffineSubspace

Represents a linear subspace (an affine subspace passing through the origin).

property complement: LinearSubspace

Returns the orthogonal complement of this subspace as a new LinearSubspace.

classmethod from_basis(domain: HilbertSpace, basis_vectors: List[Vector], orthonormalize: bool = True, solver: LinearSolver | None = None, preconditioner: LinearOperator | None = None) LinearSubspace[source]

Constructs a linear subspace from a set of basis vectors.

Parameters:
  • domain – The Hilbert space.

  • basis_vectors – List of vectors spanning the subspace.

  • orthonormalize – Whether to orthonormalize the basis.

  • solver – Optional solver for implicit constraints (see AffineSubspace.from_tangent_basis).

  • preconditioner – Optional preconditioner.

classmethod from_complement_basis(domain: HilbertSpace, basis_vectors: List[Vector], orthonormalize: bool = True) LinearSubspace[source]

Constructs a linear subspace defined by orthogonality to a complement basis. S = {u | <u, v_i> = 0}.

Parameters:
  • domain – The Hilbert space.

  • basis_vectors – Basis vectors for the complement.

  • orthonormalize – Whether to orthonormalize the complement basis.

classmethod from_kernel(operator: LinearOperator, solver: LinearSolver | None = None, preconditioner: LinearOperator | None = None) LinearSubspace[source]

Constructs the subspace corresponding to the kernel (null space) of an operator. K = {u | A(u) = 0}.

Parameters:
  • operator – The operator A.

  • solver – Solver used for the Gram matrix (A A*).

  • preconditioner – Optional preconditioner.

class pygeoinf.subspaces.OrthogonalProjector(domain: HilbertSpace, mapping: Callable[[Any], Any], complement_projector: LinearOperator | None = None)[source]

Bases: LinearOperator

Internal engine for subspace projections. Represents an orthogonal projection operator P = P* = P^2.

property complement: LinearOperator

Returns the projector onto the orthogonal complement (I - P).

If a complement projector was not provided at initialization, one is constructed automatically as the difference between the identity and self.

classmethod from_basis(domain: HilbertSpace, basis_vectors: List[Vector], orthonormalize: bool = True) OrthogonalProjector[source]

Constructs a projector P onto the span of the provided basis vectors.

Parameters:
  • domain – The Hilbert space.

  • basis_vectors – A list of vectors spanning the subspace.

  • orthonormalize – If True, performs Gram-Schmidt orthonormalization on the basis vectors before constructing the projector. If False, assumes the basis is already orthonormal.

Returns:

An OrthogonalProjector instance.

pygeoinf.utils module

pygeoinf.utils.configure_threading(n_threads: int = 1)[source]

Sets the maximum number of threads used by underlying linear algebra backends (MKL, OpenBLAS, etc.).

Parameters:

n_threads – The number of threads to allow. Set to 1 for serial execution (safe for multiprocessing). Set to -1 or None to use all available cores.

Module contents

Unified imports for the package.

class pygeoinf.AffineOperator(linear_part: LinearOperator, translation: Any)[source]

Bases: AffineOperatorAxiomChecks, NonLinearOperator

Represents an affine transformation between two Hilbert spaces.

An affine operator is a mapping F(x) = A(x) + b, where ‘A’ is a bounded linear operator and ‘b’ is a fixed translation vector in the codomain.

property linear_part: LinearOperator

The underlying linear mapping ‘A’.

property translation_part: Any

The translation vector ‘b’.

class pygeoinf.AffineSubspace(projector: OrthogonalProjector, translation: Vector | None = None, constraint_operator: LinearOperator | None = None, constraint_value: Vector | None = None, solver: LinearSolver | None = None, preconditioner: LinearOperator | None = None)[source]

Bases: Subset

Represents an affine subspace A = x0 + V.

This class serves two primary roles: 1. A geometric subset that can project points and check membership. 2. A constraint definition for Bayesian inversion (conditioning a Gaussian

measure on the subspace).

property boundary: Subset

Returns the boundary of the affine subspace.

Geometrically, an affine subspace (like a line or plane) is a closed manifold without a boundary. Returns EmptySet.

condition_gaussian_measure(prior: GaussianMeasure, geometric: bool = False) GaussianMeasure[source]

Conditions a Gaussian measure on this subspace.

Parameters:
  • prior – The prior Gaussian measure.

  • geometric – If True, performs a geometric projection of the measure (equivalent to conditioning on “measurement = truth” with infinite precision, effectively squashing the distribution onto the subspace). If False (default), performs standard Bayesian conditioning using the constraint equation B(u) = w.

Returns:

The posterior (conditioned) GaussianMeasure.

Raises:

ValueError – If geometric=False and the subspace was constructed without a solver capable of handling the constraint operator.

property constraint_operator: LinearOperator

Returns the operator B defining the subspace as {u | B(u)=w}.

If no explicit operator was provided (geometric construction), this falls back to the complement projector (I - P).

property constraint_value: Vector

Returns the value w defining the subspace as {u | B(u)=w}.

If no explicit operator was provided, this falls back to (I - P)x0.

classmethod from_complement_basis(domain: HilbertSpace, basis_vectors: List[Vector], translation: Vector | None = None, orthonormalize: bool = True) AffineSubspace[source]

Constructs a subspace defined by orthogonality to a set of complement basis vectors.

The subspace is defined as {u | <u - x0, v_i> = 0} for all v_i in basis. This provides an explicit constraint operator B where B(u)_i = <u, v_i>.

Parameters:
  • domain – The Hilbert space.

  • basis_vectors – Basis vectors for the orthogonal complement.

  • translation – A point x0 in the subspace.

  • orthonormalize – If True, orthonormalizes the complement basis.

classmethod from_hyperplanes(hyperplanes: List[Subset], solver: LinearSolver | None = None, preconditioner: LinearOperator | None = None) AffineSubspace[source]

Constructs an affine subspace as the intersection of hyperplanes.

Each hyperplane is defined as {x | ⟨a_i, x⟩ = b_i}. The intersection of m hyperplanes defines an affine subspace of codimension m (assuming the normal vectors are linearly independent).

Parameters:
  • hyperplanes – A list of HyperPlane objects from subsets module. All hyperplanes must have the same domain.

  • solver – Solver used to invert the Gram matrix (B B*) during construction. Defaults to CholeskySolver.

  • preconditioner – Optional preconditioner for iterative solvers.

Returns:

The affine subspace defined by the intersection.

Return type:

AffineSubspace

Raises:
  • ValueError – If hyperplanes list is empty or domains don’t match.

  • ImportError – If hyperplanes don’t have the required attributes.

classmethod from_linear_equation(operator: LinearOperator, value: Vector, solver: LinearSolver | None = None, preconditioner: LinearOperator | None = None) AffineSubspace[source]

Constructs a subspace defined by the linear equation B(u) = w.

Parameters:
  • operator – The linear operator B.

  • value – The RHS vector w.

  • solver – Solver used to invert the Gram matrix (B B*) during construction and later conditioning. Defaults to CholeskySolver.

  • preconditioner – Optional preconditioner for iterative solvers.

classmethod from_tangent_basis(domain: HilbertSpace, basis_vectors: List[Vector], translation: Vector | None = None, orthonormalize: bool = True, solver: LinearSolver | None = None, preconditioner: LinearOperator | None = None) AffineSubspace[source]

Constructs an affine subspace from a translation and a basis for the tangent space.

This method defines the subspace geometrically. The constraint is implicit: (I - P)u = (I - P)x0.

Parameters:
  • domain – The Hilbert space.

  • basis_vectors – Basis vectors for the tangent space V.

  • translation – A point x0 in the subspace.

  • orthonormalize – If True, orthonormalizes the basis.

  • solver – A linear solver capable of handling the singular operator (I-P). Required if you intend to use this subspace for Bayesian conditioning.

  • preconditioner – Optional preconditioner for the solver.

get_tangent_basis() List[Vector][source]

Returns an orthonormal basis for the tangent space of this affine subspace.

Extracts orthonormal basis vectors that span the tangent space $V$ by applying the projector $P$ to each standard basis vector $e_i$, then performing a tolerant Gram-Schmidt orthogonalisation to discard linearly dependent projections.

Returns:

Orthonormal basis vectors for the tangent space.

Return type:

List[Vector]

property has_explicit_equation: bool

True if defined by B(u)=w, False if defined only by geometry.

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if the vector x lies within the subspace.

Checks if the projection residual ||x - P_A(x)|| is small relative to the norm of x (or 1.0).

Parameters:
  • x – The vector to check.

  • rtol – Relative tolerance for the residual check.

property preconditioner: LinearOperator | None

Returns the preconditioner associated with the solver, if any.

project(x: Vector) Vector[source]

Orthogonally projects a vector x onto the affine subspace.

Formula: P_A(x) = P(x - x0) + x0

property projection_operator: AffineOperator

Returns the affine orthogonal projection operator onto the subspace. P_A(x) = P(x) + (I - P)x_0

property projector: OrthogonalProjector

Returns the orthogonal projector P onto the tangent space.

property pseudo_inverse: LinearOperator

Returns the right pseudo-inverse operator B^dagger = B* (B B*)^{-1}.

property solver: LinearSolver | None

Returns the linear solver associated with this subspace.

property tangent_space: LinearSubspace

Returns the LinearSubspace V parallel to this affine subspace.

to_hyperplanes() List[Subset][source]

Decomposes this affine subspace into a minimal set of hyperplanes.

Returns a list of HyperPlane objects whose intersection equals this affine subspace. The number of hyperplanes equals the codimension of the subspace (i.e., the rank of the constraint operator).

Returns:

The minimal set of hyperplanes defining this subspace.

Return type:

List[HyperPlane]

Raises:
  • ValueError – If the subspace does not have an explicit constraint operator.

  • ImportError – If HyperPlane class is not available.

property translation: Vector

Returns the translation vector x0.

with_constraint_value(new_value: Vector) AffineSubspace[source]

Returns a new AffineSubspace parallel to this one, shifted to satisfy the new explicit constraint equation B(u) = new_value.

with_translation(new_translation: Vector) AffineSubspace[source]

Returns a new AffineSubspace parallel to this one, shifted to pass through the new translation vector.

pygeoinf.BICGMatrixSolver(galerkin: bool = False, **kwargs) ScipyIterativeSolver[source]
pygeoinf.BICGStabMatrixSolver(galerkin: bool = False, **kwargs) ScipyIterativeSolver[source]
class pygeoinf.BICGStabSolver(*, preconditioning_method: LinearSolver = None, rtol: float = 1e-05, atol: float = 1e-08, maxiter: int | None = None)[source]

Bases: IterativeLinearSolver

A matrix-free implementation of the BiCGStab algorithm.

Suitable for non-symmetric linear systems Ax = y. It operates directly on Hilbert space vectors using native inner products and arithmetic.

solve_linear_system(operator: LinearOperator, preconditioner: LinearOperator | None, y: Vector, x0: Vector | None) Vector[source]

Solves the linear system Ax = y for x.

Parameters:
  • operator (LinearOperator) – The operator A of the linear system.

  • preconditioner (LinearOperator, optional) – The preconditioner.

  • y (Vector) – The right-hand side vector.

  • x0 (Vector, optional) – The initial guess for the solution.

Returns:

The solution vector x.

Return type:

Vector

class pygeoinf.Ball(domain: HilbertSpace, center: Vector, radius: float, open_set: bool = True)[source]

Bases: Ellipsoid

Represents a ball in a Hilbert space: B = {x | ||x - c||^2 <= r^2}. This is an Ellipsoid where A is the Identity operator.

property boundary: Subset

Returns the Sphere bounding this Ball.

directional_bound(direction: Vector) tuple['Vector', float][source]

Returns extreme point in direction of ‘direction’.

For a ball B(c, r) and direction q:

x_max = c + r * (q / ||q||) h(q) = ⟨q, c⟩ + r||q||

Parameters:

direction – The direction vector q.

Returns:

(x_max, h(q))

Return type:

tuple[Vector, float]

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if x lies within the ball. Optimized to use geometric distance ||x-c|| directly.

property support_function: SupportFunction

Returns the support function object for this ball.

Always available for valid Ball instances.

class pygeoinf.BallSupportFunction(primal_domain: HilbertSpace, center: Vector, radius: float)[source]

Bases: SupportFunction

Support function of a closed ball B(c, r) = {x : ||x - c|| ≤ r}:

h(q) = ⟨q, c⟩ + r ||q||

support_point(q: Vector) 'Vector' | None[source]

Return x* = c + r * (q / ||q||) achieving the supremum.

value_and_support_point(q: Vector) tuple[float, Vector | None][source]

Return (h(q), x*(q)) computing $|q|$ once.

Both $h(q) = langle q, c rangle + r|q|$ and $x^*(q) = c + r , q / |q|$ depend on $|q|$, so computing it once halves the number of norm evaluations compared to the default two-call fallback.

Parameters:

q – A vector in the primal domain $H$.

Returns:

(value, point) where value $= h(q)$ and point $= x^*(q)$, or the center $c$ when $q approx 0$.

class pygeoinf.BandedPreconditioningMethod(bandwidth: int, /, *, incomplete: bool = False, drop_tol: float = 0.0001, fill_factor: float = 10.0, parallel: bool = False, n_jobs: int = -1)[source]

Bases: LinearSolver

A LinearSolver wrapper that generates a symmetrically banded sparse preconditioner.

Extracts a symmetric band of diagonals from the operator’s Galerkin matrix representation, constructs a sparse matrix, and uses a sparse direct solver (exact or incomplete LU) to invert it.

class pygeoinf.BlockDiagonalLinearOperator(operators: List[LinearOperator])[source]

Bases: LinearOperator, BlockStructure

A block operator where all off-diagonal blocks are zero operators.

block(i: int, j: int) LinearOperator[source]

Returns the operator in the (i, j)-th sub-block.

If i equals j, this is one of the diagonal operators. Otherwise, it is a zero operator.

class pygeoinf.BlockLinearOperator(blocks: List[List[LinearOperator]])[source]

Bases: LinearOperator, BlockStructure

A linear operator between direct sum spaces, defined by a matrix of sub-operators.

This operator acts like a matrix where each entry is itself a LinearOperator. It maps a list of input vectors [x_1, x_2, …] to a list of output vectors [y_1, y_2, …]. The constructor checks for dimensional consistency between the blocks.

block(i: int, j: int) LinearOperator[source]

Returns the operator in the (i, j)-th sub-block.

class pygeoinf.BlockStructure(row_dim: int, col_dim: int)[source]

Bases: ABC

An abstract base class for operators with a block structure.

abstract block(i: int, j: int) LinearOperator[source]

Returns the operator in the (i, j)-th sub-block.

property col_dim: int

Returns the number of columns in the block structure.

property row_dim: int

Returns the number of rows in the block structure.

pygeoinf.CGMatrixSolver(galerkin: bool = False, **kwargs) ScipyIterativeSolver[source]
class pygeoinf.CGSolver(*, preconditioning_method: LinearSolver = None, rtol: float = 1e-05, atol: float = 0.0, maxiter: int | None = None, callback: Callable[[Vector], None] | None = None)[source]

Bases: IterativeLinearSolver

A matrix-free implementation of the Conjugate Gradient (CG) algorithm.

This solver operates directly on Hilbert space vectors and operator actions without explicitly forming a matrix. It is suitable for self-adjoint, positive-definite operators on a general Hilbert space.

solve_linear_system(operator: LinearOperator, preconditioner: LinearOperator | None, y: Vector, x0: Vector | None) Vector[source]

Solves the linear system Ax = y for x.

Parameters:
  • operator (LinearOperator) – The operator A of the linear system.

  • preconditioner (LinearOperator, optional) – The preconditioner.

  • y (Vector) – The right-hand side vector.

  • x0 (Vector, optional) – The initial guess for the solution.

Returns:

The solution vector x.

Return type:

Vector

class pygeoinf.CallableSupportFunction(primal_domain: HilbertSpace, fn: Callable[[Vector], float], support_point_fn: Callable[[Vector], Vector] | None = None)[source]

Bases: SupportFunction

Support function defined by a user-provided callable.

Wraps an arbitrary callable $q mapsto h(q)$ as a SupportFunction. Optionally accepts a second callable $q mapsto x^*(q)$ that returns the support point (subgradient of $h$ at $q$).

Parameters:
  • primal_domain – The Hilbert space $H$ on which $h$ is defined.

  • fn – A callable fn(q) -> float computing the support value $h(q)$.

  • support_point_fn – An optional callable support_point_fn(q) -> Vector returning $x^*(q) in argmax_{x in C} langle q, x rangle$. When provided, support_point() delegates to it and subgradient() returns the result. When None, support_point() returns None and subgradient() raises NotImplementedError.

Example:

fn = lambda q: float(np.linalg.norm(q))          # L2-ball support
sp = lambda q: q / np.linalg.norm(q)             # support point
h = CallableSupportFunction(space, fn, support_point_fn=sp)
support_point(q: Vector) Vector | None[source]

Return $x^*(q)$ via the user-supplied callback, or None.

class pygeoinf.CholeskySolver(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1)[source]

Bases: DirectLinearSolver

A direct linear solver based on Cholesky decomposition.

class pygeoinf.ColumnLinearOperator(operators: List[LinearOperator])[source]

Bases: LinearOperator, BlockStructure

An operator that maps from a single space to a direct sum space.

It can be visualized as a column vector of operators, [A_1, A_2, …]^T. It takes a single input vector x and produces a list of output vectors [A_1(x), A_2(x), …]. This is often used to represent a joint forward operator in an inverse problem.

block(i: int, j: int) LinearOperator[source]

Returns the operator in the (i, 0)-th sub-block.

class pygeoinf.ColumnThresholdedPreconditioningMethod(threshold: float, /, *, max_nnz: int | None = None, galerkin: bool = True, incomplete: bool = False, drop_tol: float = 0.0001, fill_factor: float = 10.0, parallel: bool = False, n_jobs: int = -1)[source]

Bases: LinearSolver

A LinearSolver wrapper that generates a sparse preconditioner by evaluating the operator column-by-column, dropping elements that are small relative to the diagonal element, and optionally capping the maximum number of retained non-zeros per column.

class pygeoinf.Complement(subset: Subset)[source]

Bases: Subset

Represents the complement of a set: S^c = {x | x ∉ S}.

property boundary: Subset

Returns the boundary of the complement.

Ideally, ∂(S^c) = ∂S.

property complement: Subset

Returns the complement of the complement, which is the original set. (S^c)^c = S.

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if x is NOT in the underlying subset.

class pygeoinf.ConstrainedLinearLeastSquaresInversion(forward_problem: LinearForwardProblem, constraint: AffineSubspace, /, *, formalism: Literal['model_space', 'data_space'] = 'data_space')[source]

Bases: LinearInversion

Solves a linear inverse problem subject to an affine subspace constraint.

This method finds the model u that minimizes the Tikhonov-regularized least-squares functional while strictly confining the solution to an affine subspace (e.g., enforcing a specific average property value or boundary condition).

Supports both ‘model_space’ and ‘data_space’ formalisms for the underlying unconstrained inversion.

data_reduced_inversion(reduction_operator: LinearOperator, /, *, reduced_data_error_measure: GaussianMeasure | None = None, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) ConstrainedLinearLeastSquaresInversion[source]

Constructs a surrogate of the constrained linear inversion using a reduced data space.

least_squares_operator(damping: float, solver: LinearSolver, /, *, preconditioner: LinearOperator | LinearSolver | None = None) AffineOperator[source]

Returns an operator that maps data to the constrained least-squares solution.

normal_residual_callback(damping: float, data: Vector, /, *, message: str = 'Iteration: {iter} | Normal Residual: {res:.3e}', print_progress: bool = True)[source]

Generates a ResidualTrackingCallback for the reduced, unconstrained normal equations.

parameterized_inversion(parameterization: LinearOperator, /, *, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) ConstrainedLinearLeastSquaresInversion[source]

Constructs a parameterized surrogate of the constrained least-squares inversion.

Parameters:
  • parameterization – A LinearOperator mapping from the parameter space to the full model space.

  • dense – If True, computes and stores operators as dense matrices.

  • parallel – If True, computes the dense matrices in parallel.

  • n_jobs – Number of CPU cores to use. -1 means all available.

  • formalism – An optional override for the formalism of the new inversion. If None, inherits the formalism of the parent inversion.

Returns:

A new ConstrainedLinearLeastSquaresInversion instance operating on the parameter space.

with_formalism(formalism: Literal['model_space', 'data_space']) ConstrainedLinearLeastSquaresInversion[source]

Returns a new instance of the constrained inversion using the specified formalism.

Parameters:

formalism – The algebraic space in which the normal equations should be assembled and solved. Must be ‘model_space’ or ‘data_space’.

Returns:

A new ConstrainedLinearLeastSquaresInversion instance with the updated formalism.

class pygeoinf.ConstrainedLinearMinimumNormInversion(forward_problem: LinearForwardProblem, constraint: AffineSubspace, /, *, formalism: Literal['model_space', 'data_space'] = 'data_space')[source]

Bases: LinearInversion

Finds the minimum-norm solution subject to an affine subspace constraint.

This class solves the regularized inverse problem using the discrepancy principle while strictly confining the solution to an affine subspace.

constraint_value_mapping(data: Vector, solver: LinearSolver, /, *, preconditioner: LinearOperator | LinearSolver | None = None, significance_level: float = 0.95, minimum_damping: float = 0.0, maxiter: int = 100, rtol: float = 1e-06, atol: float = 0.0) NonLinearOperator[source]

Returns an operator mapping a constraint value ‘w’ to the corresponding constrained minimum norm solution ‘u’ for a strictly fixed dataset. The operator has its derivative set.

data_reduced_inversion(reduction_operator: LinearOperator, /, *, reduced_data_error_measure: GaussianMeasure | None = None, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) ConstrainedLinearMinimumNormInversion[source]

Constructs a surrogate of the constrained linear inversion using a reduced data space.

minimum_norm_operator(solver: LinearSolver, /, *, preconditioner: LinearOperator | LinearSolver | None = None, significance_level: float = 0.95, minimum_damping: float = 0.0, maxiter: int = 100, rtol: float = 1e-06, atol: float = 0.0) NonLinearOperator[source]

Returns an operator that maps data to the constrained minimum-norm solution. The operator has its derivative set.

parameterized_inversion(parameterization: LinearOperator, /, *, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) ConstrainedLinearMinimumNormInversion[source]

Constructs a parameterized surrogate of the constrained minimum norm inversion.

Parameters:
  • parameterization – A LinearOperator mapping from the parameter space to the full model space.

  • dense – If True, computes and stores operators as dense matrices.

  • parallel – If True, computes the dense matrices in parallel.

  • n_jobs – Number of CPU cores to use. -1 means all available.

  • formalism – An optional override for the formalism of the new inversion. If None, inherits the formalism of the parent inversion.

Returns:

A new ConstrainedLinearMinimumNormInversion instance operating on the parameter space.

with_formalism(formalism: Literal['model_space', 'data_space']) ConstrainedLinearMinimumNormInversion[source]

Returns a new instance of the constrained inversion using the specified formalism.

Parameters:

formalism – The algebraic space in which the normal equations should be assembled and solved. Must be ‘model_space’ or ‘data_space’.

Returns:

A new ConstrainedLinearMinimumNormInversion instance with the updated formalism.

class pygeoinf.ConvexSubset(form: NonLinearForm, level: float, open_set: bool = False, support_fn: 'SupportFunction' | None = None)[source]

Bases: SublevelSet

Represents a closed convex set via dual representations.

A closed convex set can be equivalently defined by: 1. A sublevel set: S = {x | f(x) <= c} where f is convex 2. Its support function: h(q) = sup{⟨q, x⟩ : x ∈ S}

This class supports both representations. The support function is abstract and must be implemented by all concrete subclasses (Ball, Ellipsoid, etc.).

check(n_samples: int = 10, /, *, rtol: float = 1e-05, atol: float = 1e-08) None[source]

Performs a randomized check of the convexity inequality: f(tx + (1-t)y) <= t*f(x) + (1-t)*f(y)

Parameters:
  • n_samples – Number of random pairs to test.

  • rtol – Relative tolerance.

  • atol – Absolute tolerance.

Raises:

AssertionError – If the function is found to be non-convex.

closure() ConvexSubset[source]

Returns the closure of this convex set.

For a convex set S: - If S is already closed ({f <= c}), returns self (no copy needed). - If S is open ({f < c}), returns a new closed version.

The closure is the smallest closed set containing S. Note: The returned object uses the same functional and level as self, with open_set flag set to False.

Returns:

A ConvexSubset instance (subclass) representing cl(S).

Return type:

ConvexSubset

abstract directional_bound(direction: Vector) tuple['Vector', float][source]

Returns extreme point and support value in a given direction.

For a convex set S and direction q, computes:

x_max = argmax{⟨q, x⟩ : x ∈ S} h(q) = sup{⟨q, x⟩ : x ∈ S}

This method must be implemented by all concrete ConvexSubset subclasses.

Parameters:

direction – A vector q specifying the direction.

Returns:

(x_max, h(q)) where x_max achieves the supremum

and h(q) is the support function value.

Return type:

tuple[Vector, float]

Raises:

NotImplementedError – If not implemented for this set type.

property is_closed: bool

Returns True if the set is closed (defined by <=), False if open (<).

property support_fn: 'SupportFunction' | None

Access the stored support function object, if any.

abstract property support_function: 'SupportFunction' | None

Returns the SupportFunction instance for this set, or None if unavailable.

This property should not raise errors if the support function cannot be created (e.g., missing inverse operators for ellipsoids). The SupportFunction itself is responsible for raising errors when required inputs are missing during evaluation.

class pygeoinf.DenseMatrixLinearOperator(domain: HilbertSpace, codomain: HilbertSpace, matrix: np.ndarray, /, *, galerkin=False)[source]

Bases: MatrixLinearOperator

A specialisation of the MatrixLinearOperator class to instances where the matrix representation is always provided as a numpy array.

This is a class provides some additional methods for component-wise access.

static from_linear_operator(operator: LinearOperator, /, *, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) DenseMatrixLinearOperator[source]

Converts a LinearOperator into a DenseMatrixLinearOperator by forming its dense matrix representation.

Parameters:
  • operator – The operator to be converted.

  • galerkin – If True, the Galerkin representation is used. Default is False.

  • parallel – If True, dense matrix calculation is done in parallel. Default is False.

  • n_jobs – Number of jobs used for parallel calculations. Default is False.

class pygeoinf.DiagonalSparseMatrixLinearOperator(domain: HilbertSpace, codomain: HilbertSpace, diagonals: Tuple[np.ndarray, List[int]], /, *, galerkin: bool = False)[source]

Bases: SparseMatrixLinearOperator

A highly specialized operator for matrices defined purely by a set of non-zero diagonals.

This class internally stores the operator using a scipy.sparse.dia_array for maximum efficiency in storage and matrix-vector products. It provides extremely fast methods for extracting diagonals, as this is its native storage format.

A key feature of this class is its support for functional calculus. It dynamically proxies element-wise mathematical functions (e.g., .sqrt(), .log(), abs(), **) to the underlying sparse array. For reasons of mathematical correctness, these operations are restricted to operators that are strictly diagonal (i.e., have only a non-zero main diagonal) and will raise a NotImplementedError otherwise.

Aggregation methods that do not return a new operator (e.g., .sum()) are not restricted and can be used on any multi-diagonal operator.

Class Methods

from_diagonal_values:

Constructs a strictly diagonal operator from a 1D array of values.

from_operator:

Creates a diagonal approximation of another LinearOperator.

Properties

offsets:

The array of stored diagonal offsets.

is_strictly_diagonal:

True if the operator only has a non-zero main diagonal.

inverse:

The inverse of a strictly diagonal operator.

sqrt:

The square root of a strictly diagonal operator.

apply_function(func: str | Callable[[ndarray], ndarray]) DiagonalSparseMatrixLinearOperator[source]

Applies a function to the diagonal values (eigenvalues) of the operator.

This supports functional calculus for strictly diagonal operators. For maximum performance, pass a NumPy ufunc (e.g., np.sqrt, np.exp).

Parameters:

func – A callable function, or the string name of a SciPy sparse method.

extract_diagonals(offsets: List[int], /, *, galerkin: bool = True, parallel: bool = False, n_jobs: int = -1) Tuple[ndarray, List[int]][source]

Overrides the base method for extreme efficiency.

This operation is nearly free, as it involves selecting the requested diagonals from the data already stored in the native format.

classmethod from_diagonal_values(domain: HilbertSpace, codomain: HilbertSpace, diagonal_values: np.ndarray, /, *, galerkin: bool = False) DiagonalSparseMatrixLinearOperator[source]

Constructs a purely diagonal operator from a 1D array of values.

This provides a convenient way to create an operator with non-zero entries only on its main diagonal (offset k=0).

Parameters:
  • domain – The domain of the operator.

  • codomain – The codomain of the operator. Must have the same dimension.

  • diagonal_values – A 1D NumPy array of the values for the main diagonal.

  • galerkin – If True, the operator is in Galerkin form.

Returns:

A new DiagonalSparseMatrixLinearOperator.

classmethod from_operator(operator: LinearOperator, offsets: List[int], /, *, galerkin: bool = True) DiagonalSparseMatrixLinearOperator[source]

Creates a diagonal approximation of another LinearOperator.

This factory method works by calling the source operator’s .extract_diagonals() method and using the result to construct a new, highly efficient DiagonalSparseMatrixLinearOperator.

Parameters:
  • operator – The source operator to approximate.

  • offsets – The list of diagonal offsets to extract and keep.

  • galerkin – Specifies which matrix representation to use.

Returns:

A new DiagonalSparseMatrixLinearOperator.

property inverse: DiagonalSparseMatrixLinearOperator

The inverse of the operator, computed via functional calculus. Requires the operator to be strictly diagonal with no zero entries.

property is_strictly_diagonal: bool

True if the operator only has a non-zero main diagonal (offset=0).

property offsets: ndarray

Returns the array of stored diagonal offsets.

property sqrt: DiagonalSparseMatrixLinearOperator

The square root of the operator, computed via functional calculus. Requires the operator to be strictly diagonal with non-negative entries.

class pygeoinf.DirectLinearSolver(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1)[source]

Bases: LinearSolver

An abstract base class for direct linear solvers that rely on matrix factorization.

class pygeoinf.DualHilbertSpace(space: HilbertSpace)[source]

Bases: HilbertSpace

A wrapper class representing the dual of a HilbertSpace.

An element of a dual space is a continuous linear functional, represented in this library by the LinearForm class. This wrapper provides a full HilbertSpace interface for these LinearForm objects, allowing them to be treated as vectors in their own right.

property dim: int

The dimension of the dual space.

property dual: HilbertSpace

The dual of the dual space, which is the original primal space.

final duality_product(xp: LinearForm, x: Vector) float[source]

Computes the duality product <x, xp>.

In this context, x is from the primal space and xp is the dual vector (a LinearForm). This is unconventional but maintains the method signature; it evaluates x(xp).

from_components(c: np.ndarray) LinearForm[source]

Creates a LinearForm from a NumPy component array.

from_dual(xp: Vector) LinearForm[source]

Maps a primal vector to its corresponding dual LinearForm.

is_element(x: Any) bool[source]

Checks if an object is a valid element of the dual space.

to_components(x: LinearForm) np.ndarray[source]

Maps a LinearForm to its NumPy component array.

to_dual(x: LinearForm) Any[source]

Maps a dual vector back to its representative in the primal space.

property underlying_space: HilbertSpace

The primal HilbertSpace of which this is the dual.

class pygeoinf.DualMasterCostFunction(data_space: HilbertSpace, property_space: HilbertSpace, model_space: HilbertSpace, G: LinearOperator, T: LinearOperator, model_prior_support: SupportFunction, data_error_support: SupportFunction, observed_data: Vector, q_direction: Vector)[source]

Bases: NonLinearForm

Cost function for the master dual equation (Hilbert form):

h_U(q) = inf_{λ ∈ D}

{ (λ, d̃)_D + σ_B(T* q - G* λ) + σ_V(-λ) }

i.e.

φ(λ; q) = (λ, d̃)_D + σ_B(T* q - G* λ) + σ_V(-λ)

where:
  • σ_B is the support function of the model prior convex set B ⊆ M

  • σ_V is the support function of the data error convex set V ⊆ D

Minimizing φ(λ; q) over λ ∈ D yields h_U(q).

property direction: Vector

Current property direction q ∈ P.

property observed_data: Vector

Observed data vector d̃ ∈ D.

set_direction(q: Vector) None[source]

Update the property direction q and recompute T* q.

value_and_subgradient(lam: Vector) tuple[float, Vector][source]

Compute the value and a subgradient of $varphi(lambda; q)$ in one pass.

Shares the computation of $G^* lambda$ and the support points $v$, $w$ between the value and subgradient evaluations, avoiding redundant work.

The dual master cost function is

\[\varphi(\lambda; q) = \langle \lambda, \tilde{d} \rangle_D + \sigma_B(T^* q - G^* \lambda) + \sigma_V(-\lambda)\]

and a subgradient at $lambda$ is

\[g = \tilde{d} - G v - w,\]

where $v in partial sigma_B(T^* q - G^* lambda)$ and $w in partial sigma_V(-lambda)$.

Parameters:

lam – Dual variable $lambda in D$.

Returns:

A tuple (f, g) where f = φ(λ; q) is the scalar value and g ∂φ(λ; q) is a subgradient vector in $D$.

class pygeoinf.EigenSolver(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1, rtol: float = 1e-12)[source]

Bases: DirectLinearSolver

A direct linear solver based on the eigendecomposition of a symmetric operator.

class pygeoinf.Ellipsoid(domain: HilbertSpace, center: Vector, radius: float, operator: LinearOperator, open_set: bool = False, *, inverse_operator: LinearOperator | None = None, inverse_sqrt_operator: LinearOperator | None = None)[source]

Bases: ConvexSubset, _EllipsoidalGeometry

Represents a solid ellipsoid: E = {x | <A(x-c), x-c> <= r^2}.

property boundary: Subset

Returns the boundary EllipsoidSurface.

directional_bound(direction: Vector) tuple['Vector', float][source]

Returns extreme point in given direction.

For an ellipsoid E(c, r, A) and direction q:

x_max = c + r * (A^{-1} q) / ||A^{-1/2} q|| h(q) = support_function(q)

Parameters:

direction – The direction vector q.

Returns:

(x_max, h(q))

Return type:

tuple[Vector, float]

property normalized: NormalisedEllipsoid

Returns a normalized version of this ellipsoid with radius 1. The operator is scaled by 1/r^2 to represent the same set.

property support_function: SupportFunction

Returns the support function object for this ellipsoid.

This property does not require inverse operators at construction time. The returned SupportFunction will raise errors if missing operators are required for evaluation.

class pygeoinf.EllipsoidSupportFunction(primal_domain: HilbertSpace, center: Vector, radius: float, shape_operator: LinearOperator, inverse_operator: LinearOperator | None = None, inverse_sqrt_operator: LinearOperator | None = None)[source]

Bases: SupportFunction

Support function of an ellipsoid E(c, r, A) defined by:

E = {x : ⟨A(x-c), (x-c)⟩ ≤ r²} with A SPD

Then:

h(q) = ⟨q, c⟩ + r ||A^{-1/2} q||

Parameters:
  • primal_domain – The Hilbert space H.

  • center – The center c of the ellipsoid.

  • radius – The radius r.

  • shape_operator – The SPD operator A.

  • inverse_operator – A^{-1}. Required for support_point and sufficient for computing h(q) through $sqrt{langle q, A^{-1}qrangle}$.

  • inverse_sqrt_operator – A^{-1/2}. Optional direct square-root inverse used for computing h(q).

Note

If neither inverse operator is provided, the support function cannot be evaluated. If inverse_operator is omitted, support_point returns None.

support_point(q: Vector) 'Vector' | None[source]

Return x* = c + r * (A^{-1} q) / ||A^{-1/2} q|| achieving the supremum.

For an ellipsoid E(c, r, A), the extreme point in direction q is found by transforming q through the inverse metric A^{-1} and normalizing.

Returns None if inverse_operator was not provided.

value_and_support_point(q: Vector) tuple[float, Vector | None][source]

Return (h(q), x*(q)) computing $A^{-1} q$ once.

When $A^{-1}$ is available, both the value

\[h(q) = \langle q, c \rangle + r\,\sqrt{\langle q,\, A^{-1} q \rangle}\]

and the support point

\[x^*(q) = c + \frac{r}{\sqrt{\langle q, A^{-1} q \rangle}} A^{-1} q\]

share the intermediate $A^{-1} q$ and the norm $|A^{-1/2} q| = sqrt{langle q, A^{-1} q rangle}$, so only one operator application is needed instead of two.

When $A^{-1}$ is not available, falls back to calling self(q) (which uses $A^{-1/2}$) and returns None for the support point, matching the behaviour of support_point().

Parameters:

q – A vector in the primal domain $H$.

Returns:

(value, point) where point is None when $A^{-1}$ was not supplied at construction.

class pygeoinf.EllipsoidSurface(domain: HilbertSpace, center: Vector, radius: float, operator: LinearOperator)[source]

Bases: LevelSet, _EllipsoidalGeometry

Represents the surface of an ellipsoid: S = {x | <A(x-c), x-c> = r^2}.

property boundary: Subset

Returns EmptySet (manifold without boundary).

property normalized: EllipsoidSurface

Returns a normalized version of this surface with radius 1.

class pygeoinf.EmptySet(domain: HilbertSpace | None = None)[source]

Bases: Subset

Represents the empty set (∅).

property boundary: Subset

The boundary of an empty set is the empty set itself.

property complement: Subset

The complement of the empty set is the whole space (Universal Set).

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns False for any vector.

property is_empty: bool

Returns True, as this is the empty set.

class pygeoinf.EuclideanSpace(dim: int)[source]

Bases: HilbertSpace

An n-dimensional Euclidean space, R^n.

This is a concrete HilbertSpace where vectors are represented directly by NumPy arrays, and the inner product is the standard dot product.

property dim: int

The dimension of the space.

from_components(c: ndarray) ndarray[source]

Returns the component array itself, as it is the vector.

from_dual(xp: LinearForm) np.ndarray[source]

Maps a LinearForm back to a vector via its components.

inner_product(x1: ndarray, x2: ndarray) float[source]

Computes the inner product of two vectors.

Notes

Default implementation overrident for efficiency.

is_element(x: Any) bool[source]

Checks if an object is a valid element of the space.

subspace_projection(indices: int | List[int]) LinearOperator[source]

Returns a projection operator onto specified coordinates.

This creates a linear operator that extracts the components at the given indices, projecting from this space to a lower-dimensional Euclidean space.

Parameters:

indices – Single index or list of indices to project onto (0-indexed).

Returns:

LinearOperator from this space to EuclideanSpace(len(indices)).

Raises:

IndexError – If any index is out of range for this space’s dimension.

to_components(x: ndarray) ndarray[source]

Returns the vector itself, as it is already a component array.

to_dual(x: np.ndarray) LinearForm[source]

Maps a vector x to a LinearForm with the same components.

class pygeoinf.ExactBlockPreconditioningMethod(blocks: list[list[int]], /, *, galerkin: bool = True, incomplete: bool = False, drop_tol: float = 0.0001, fill_factor: float = 10.0, parallel: bool = False, n_jobs: int = -1)[source]

Bases: LinearSolver

A LinearSolver wrapper that generates a sparse block preconditioner using exact matrix-vector evaluations.

Explicitly probes the operator with basis vectors but only retains the entries specified by the interaction blocks. Factorizes the resulting sparse matrix using exact or incomplete LU.

class pygeoinf.FCGSolver(*, rtol: float = 1e-05, atol: float = 1e-08, maxiter: int | None = None, preconditioning_method: LinearSolver | None = None)[source]

Bases: IterativeLinearSolver

Flexible Conjugate Gradient (FCG) solver.

FCG is designed to handle variable preconditioning, such as using an inner iterative solver to approximate the action of M^-1.

solve_linear_system(operator: LinearOperator, preconditioner: LinearOperator | None, y: Vector, x0: Vector | None) Vector[source]

Solves the linear system Ax = y for x.

Parameters:
  • operator (LinearOperator) – The operator A of the linear system.

  • preconditioner (LinearOperator, optional) – The preconditioner.

  • y (Vector) – The right-hand side vector.

  • x0 (Vector, optional) – The initial guess for the solution.

Returns:

The solution vector x.

Return type:

Vector

class pygeoinf.ForwardProblem(forward_operator: NonLinearOperator, /, *, data_error_measure: GaussianMeasure | None = None)[source]

Bases: object

Represents a general forward problem.

An instance is defined by a forward operator that maps from a model space to a data space, and an optional Gaussian measure representing the statistical distribution of errors in the data.

property data_error_measure: GaussianMeasure

The measure from which data errors are drawn.

property data_error_measure_set: bool

True if a data error measure has been set.

property data_space: HilbertSpace

The data space (codomain of the forward operator).

property forward_operator: LinearOperator

The forward operator, mapping from model to data space.

property model_space: HilbertSpace

The model space (domain of the forward operator).

pygeoinf.GMRESMatrixSolver(galerkin: bool = False, **kwargs) ScipyIterativeSolver[source]
class pygeoinf.GaussianMeasure(*, covariance: LinearOperator = None, covariance_factor: LinearOperator = None, expectation: Vector = None, sample: Callable[[], Vector] = None, inverse_covariance: LinearOperator = None, inverse_covariance_factor: LinearOperator = None)[source]

Bases: object

Represents a Gaussian measure on a Hilbert space.

This class generalizes the multivariate normal distribution to abstract, potentially infinite-dimensional, Hilbert spaces. A measure is defined by its expectation (mean vector) and its covariance, which is a LinearOperator on the space.

affine_mapping(*, operator: LinearOperator = None, translation: Vector = None, affine_operator: AffineOperator = None, inverse_solver: LinearSolver = None, inverse_preconditioner: LinearOperator = None) GaussianMeasure[source]

Transforms the measure under an affine map y = A(x) + b.

This method calculates the push-forward measure. It can also construct the implied inverse covariance (precision) using a saddle-point (KKT) system.

Parameters:
  • operator – The linear part of the mapping (A).

  • translation – The translation vector (b).

  • affine_operator – An AffineOperator instance (cannot be used with operator or translation).

  • inverse_solver – A solver used to evaluate the KKT inverse covariance.

  • inverse_preconditioner – A preconditioner for the inverse_solver.

Returns:

A new GaussianMeasure representing the push-forward distribution.

Raises:

ValueError – If mutually exclusive arguments are provided, or if an inverse solve is requested but the prior lacks an inverse covariance.

ambient_ball(probability: float, /, **kwargs)[source]

Shortcut for credible_set(..., geometry='ambient_ball', ...).

as_multivariate_normal(*, parallel: bool = False, n_jobs: int = -1) <scipy.stats._multivariate.multivariate_normal_gen object at 0x7d8aa0531940>[source]

Returns the measure as a scipy.stats.multivariate_normal object.

Parameters:
  • parallel – If True, evaluates the dense covariance matrix concurrently.

  • n_jobs – The number of CPU cores to use if parallel=True.

Returns:

A frozen scipy.stats.multivariate_normal object.

Raises:

NotImplementedError – If the measure is not defined on a EuclideanSpace.

property covariance: LinearOperator

The covariance operator of the measure.

property covariance_factor: LinearOperator

The covariance factor L. Raises AttributeError if not set.

property covariance_factor_set: bool

True if a covariance factor L (such that C = L @ L*) is available.

credible_set(probability: float, /, *, geometry: str = 'ellipsoid', rank: int | None = None, open_set: bool = False, theta: float | None = None, spectrum=None, spectrum_size: int | None = None, radius_method: str = 'auto', quantile_method: str = 'auto', quantile_tol: float = 0.01, fractional_apply: str = 'auto', n_samples: int = 10000, lanczos_size_estimate: int = 50, lanczos_method: Literal['variable', 'fixed'] = 'fixed', lanczos_max_k: int | None = None, lanczos_rtol: float = 0.001, lanczos_atol: float = 1e-08, lanczos_check_interval: int = 5, spectrum_low_rank_kwargs: dict | None = None, rng: Generator | None = None)[source]

Return a probability-calibrated Gaussian credible subset.

Five geometries are supported:

"ellipsoid" / "mahalanobis" / "domain"

The classical Mahalanobis ellipsoid.

"cameron_martin" / "cm" / "ball" / "norm_ball"

The ellipsoid expressed as a unit ball in the Cameron-Martin geometry.

"ambient_ball" / "ambient"

The ambient norm ball ${m : |m - m_0|_H le r_p}$.

"weakened_ellipsoid" / "fractional"

The weakened-covariance ellipsoid ${m : |C^{-theta/2}(m-m_0)|_H le r_p}$.

Parameters:
  • probability – Credible probability $p$, strictly between 0 and 1.

  • geometry – Selects the ball/ellipsoid family (see above).

  • rank – Chi-square degrees of freedom (legacy modes only).

  • open_set – If true, return the open version of the set.

  • theta – Fractional exponent in $(0, 1)$, required for weakened_ellipsoid.

  • spectrum – Covariance spectrum specification.

  • spectrum_size – Truncation length when spectrum is callable or None.

  • radius_method"auto", "spectral", or "sampling".

  • quantile_method – Weighted-chi-square quantile method.

  • quantile_tol – Desired relative accuracy of the weighted-chi-square quantile.

  • fractional_apply – How to apply $C^{-theta/2}$ for the weakened ellipsoid.

  • n_samples – Monte Carlo sample count for sampling radius.

  • lanczos_size_estimate – Initial or fixed Krylov dimension for Lanczos fractional evaluation.

  • lanczos_method – ‘fixed’ or ‘variable’ dynamic convergence for Lanczos.

  • lanczos_max_k – Maximum Krylov dimension if ‘variable’ is used.

  • lanczos_rtol – Relative tolerance for Lanczos convergence.

  • lanczos_atol – Absolute tolerance for Lanczos convergence.

  • lanczos_check_interval – Number of iterations between Lanczos convergence checks.

  • spectrum_low_rank_kwargs – Extra kwargs forwarded to LowRankEig.from_randomized.

  • rng – Optional NumPy generator for Monte Carlo paths.

Returns:

An Ellipsoid or Ball defining the credible subset.

deflated_pointwise_std(rank: int, /, *, size_estimate: int = 0, method: str = 'variable', max_samples: int = None, rtol: float = 0.01, block_size: int = 10, parallel: bool = False, n_jobs: int = -1) Vector[source]

Estimates the pointwise standard deviation field using a deflated Hutchinson’s method.

Parameters:
  • rank – The rank of the deterministic SVD deflation.

  • size_estimate – The initial number of stochastic residual samples.

  • method – ‘variable’ to sample until rtol is met, ‘fixed’ otherwise.

  • max_samples – Hard limit on stochastic residual samples.

  • rtol – Relative tolerance for the stochastic residual phase.

  • block_size – Number of samples added per check in the ‘variable’ method.

  • parallel – If True, draws stochastic samples in parallel.

  • n_jobs – The number of CPU cores to use.

Returns:

A vector representing the pointwise standard deviation field.

deflated_pointwise_variance(rank: int, /, *, size_estimate: int = 0, method: str = 'variable', max_samples: int = None, rtol: float = 0.01, block_size: int = 10, parallel: bool = False, n_jobs: int = -1) Vector[source]

Estimates the pointwise variance field using a deflated Hutchinson’s method.

This combines a deterministic low-rank extraction (via SVD deflation) with a stochastic Hutchinson trace estimator for the residual variance.

Parameters:
  • rank – The rank of the deterministic SVD deflation.

  • size_estimate – The initial number of stochastic residual samples.

  • method – ‘variable’ to sample until rtol is met, ‘fixed’ otherwise.

  • max_samples – Hard limit on stochastic residual samples.

  • rtol – Relative tolerance for the stochastic residual phase.

  • block_size – Number of samples added per check in the ‘variable’ method.

  • parallel – If True, draws stochastic samples in parallel.

  • n_jobs – The number of CPU cores to use.

Returns:

A vector representing the pointwise variance field.

Raises:

NotImplementedError – If the domain is not a HilbertModule.

directional_covariance(d1: Vector, d2: Vector, /) float[source]

Returns the covariance between the scalar projections <x, d1> and <x, d2>.

Parameters:
  • d1 – The first test vector.

  • d2 – The second test vector.

Returns:

The covariance scalar.

directional_statistics(direction: Vector, /) Tuple[float, float][source]

Returns the expectation and variance of the scalar Gaussian <x, direction>.

Parameters:

direction – The test vector.

Returns:

A tuple containing (expectation, variance).

directional_variance(d: Vector, /) float[source]

Returns the variance of the scalar projection <x, d>.

Parameters:

d – The test vector.

Returns:

The variance scalar.

property domain: HilbertSpace

The Hilbert space the measure is defined on.

property expectation: Vector

The expectation (mean) vector of the measure.

static from_covariance_matrix(domain: HilbertSpace, covariance_matrix: np.ndarray, /, *, expectation: Vector = None, rtol: float = 1e-10) GaussianMeasure[source]

Creates a Gaussian measure from a dense covariance matrix.

Parameters:
  • domain – The Hilbert space on which the measure is defined.

  • covariance_matrix – A 2D symmetric positive semi-definite NumPy array.

  • expectation – The mean vector. Defaults to the zero vector.

  • rtol – Relative tolerance for clipping small negative eigenvalues caused by floating-point inaccuracies.

Returns:

A new GaussianMeasure instance.

Raises:

ValueError – If the matrix has significantly negative eigenvalues.

static from_direct_sum(measures: List[GaussianMeasure], /) GaussianMeasure[source]

Constructs a product measure from a list of other measures.

The resulting measure resides on the direct sum of the input domains, with block-diagonal covariance and concatenated expectations.

Parameters:

measures – A list of GaussianMeasure instances.

Returns:

A new GaussianMeasure instance defined on the direct sum space.

static from_samples(domain: HilbertSpace, samples: List[Vector], /) GaussianMeasure[source]

Estimates a Gaussian measure from a collection of sample vectors.

Constructs an empirical mean and an unnormalized sample covariance operator using a tensor product expansion.

Parameters:
  • domain – The Hilbert space the samples belong to.

  • samples – A list of sample vectors.

Returns:

A new GaussianMeasure instance.

Raises:

ValueError – If the list of samples is empty.

static from_standard_deviation(domain: HilbertSpace, standard_deviation: float, /, *, expectation: Vector = None) GaussianMeasure[source]

Creates an isotropic Gaussian measure with a scaled identity covariance.

Parameters:
  • domain – The Hilbert space on which the measure is defined.

  • standard_deviation – The uniform standard deviation for all dimensions.

  • expectation – The mean vector. Defaults to the zero vector.

Returns:

A new GaussianMeasure instance.

static from_standard_deviations(domain: HilbertSpace, standard_deviations: np.ndarray, /, *, expectation: Vector = None) GaussianMeasure[source]

Creates a Gaussian measure with a diagonal covariance operator.

Parameters:
  • domain – The Hilbert space on which the measure is defined.

  • standard_deviations – A 1D NumPy array representing the diagonal entries of the covariance factor.

  • expectation – The mean vector. Defaults to the zero vector.

Returns:

A new GaussianMeasure instance.

Raises:

ValueError – If the size of the array does not match the space dimension.

property has_zero_expectation: bool

True if the measure is internally stored with an exactly zero expectation.

property inverse_covariance: LinearOperator

The inverse covariance (precision) operator. Raises AttributeError if not set.

property inverse_covariance_factor: LinearOperator

The inverse covariance factor. Raises AttributeError if not set.

property inverse_covariance_factor_set: bool

True if an inverse covariance factor is available.

property inverse_covariance_set: bool

True if the inverse covariance (precision) operator is available.

kl_divergence(other: GaussianMeasure, /, *, method: Literal['dense', 'randomized'] = 'dense', hutchinson_size_estimate: int = 10, hutchinson_method: Literal['variable', 'fixed'] = 'variable', max_samples: int | None = None, rtol: float = 0.01, block_size: int = 5, lanczos_size_estimate: int = 40, lanczos_method: Literal['variable', 'fixed'] = 'variable', lanczos_max_k: int | None = None, lanczos_rtol: float = 0.001, lanczos_atol: float = 1e-08, lanczos_check_interval: int = 5, parallel: bool = False, n_jobs: int = -1) float[source]

Computes the exact or approximate Kullback-Leibler (KL) divergence D_KL(self || other).

This calculates the divergence of ‘self’ (P) from the prior/reference measure ‘other’ (Q).

Parameters:
  • other – The reference GaussianMeasure (Q).

  • method – ‘dense’ uses exact dense matrix factorizations (O(N^3)). ‘randomized’ uses matrix-free Stochastic Lanczos Quadrature (SLQ).

  • hutchinson_size_estimate – Initial samples for the randomized trace estimator.

  • hutchinson_method – ‘variable’ to sample until rtol is met, ‘fixed’ otherwise.

  • max_samples – Hard limit on Hutchinson samples.

  • rtol – Relative tolerance for the Hutchinson estimator.

  • block_size – Samples added per check in the ‘variable’ Hutchinson method.

  • lanczos_size_estimate – Initial Krylov dimension for fractional evaluations.

  • lanczos_method – ‘variable’ or ‘fixed’ convergence for Lanczos.

  • lanczos_max_k – Maximum Krylov dimension if ‘variable’ is used.

  • lanczos_rtol – Relative tolerance for Lanczos convergence.

  • lanczos_atol – Absolute tolerance for Lanczos convergence.

  • lanczos_check_interval – Iterations between Lanczos convergence checks.

  • parallel – If True, evaluates the stochastic probes concurrently.

  • n_jobs – Number of CPU cores to use if parallel=True.

Returns:

The calculated KL divergence.

Raises:

ValueError – If the measures reside on different domains, or if the ‘randomized’ method is called without an inverse covariance on the reference measure.

low_rank_approximation(size_estimate: int, /, *, method: str = 'variable', max_rank: int = None, power: int = 2, rtol: float = 0.0001, block_size: int = 10, parallel: bool = False, n_jobs: int = -1) GaussianMeasure[source]

Constructs a low-rank approximation of the measure.

Uses randomized matrix-free algorithms to factorize the covariance.

Parameters:
  • size_estimate – Target rank or initial sample size for the algorithm.

  • method – ‘variable’ to sample dynamically, ‘fixed’ otherwise.

  • max_rank – Upper limit on rank for the ‘variable’ method.

  • power – Number of power iterations to enhance spectral decay.

  • rtol – Relative tolerance for the ‘variable’ method.

  • block_size – Samples drawn per iteration in the ‘variable’ method.

  • parallel – If True, parallelizes the evaluations.

  • n_jobs – Number of CPU cores to use if parallel=True.

Returns:

A new GaussianMeasure backed by a LowRankCholesky covariance factor.

rescale_directional_variance(direction: Vector, std: float, /) GaussianMeasure[source]

Returns a new measure where Var[<x, direction>] is scaled to std^2.

Parameters:
  • direction – The test vector to scale against.

  • std – The target standard deviation for the projection.

Returns:

A variance-scaled GaussianMeasure.

Raises:

ValueError – If the current directional variance is zero or negative.

sample() Vector[source]

Returns a single random sample drawn from the measure.

Returns:

A randomly sampled vector.

Raises:

NotImplementedError – If a sample method is not set for this measure.

sample_expectation(n: int, /, *, parallel: bool = False, n_jobs: int = -1) Vector[source]

Estimates the expectation vector by drawing n Monte Carlo samples.

Parameters:
  • n – The number of samples to use for the estimation.

  • parallel – If True, draws samples concurrently.

  • n_jobs – The number of CPU cores to use if parallel=True.

Returns:

The empirical expectation vector.

sample_pointwise_std(n: int, /, *, parallel: bool = False, n_jobs: int = -1) Vector[source]

Estimates the pointwise standard deviation field by drawing n Monte Carlo samples.

Parameters:
  • n – The number of samples to use.

  • parallel – If True, draws samples concurrently.

  • n_jobs – The number of CPU cores to use if parallel=True.

Returns:

A vector representing the pointwise standard deviation field.

sample_pointwise_variance(n: int, /, *, parallel: bool = False, n_jobs: int = -1) Vector[source]

Estimates the pointwise variance field by drawing n Monte Carlo samples.

Parameters:
  • n – The number of samples to use.

  • parallel – If True, draws samples concurrently.

  • n_jobs – The number of CPU cores to use if parallel=True.

Returns:

A vector representing the pointwise variance field.

Raises:

NotImplementedError – If the domain is not a HilbertModule (which provides pointwise multiplication).

property sample_set: bool

True if a method for drawing random samples is available.

samples(n: int, /, *, parallel: bool = False, n_jobs: int = -1) List[Vector][source]

Returns a list of n independent random samples from the measure.

Parameters:
  • n – The number of samples to draw.

  • parallel – If True, draws samples concurrently.

  • n_jobs – The number of CPU cores to use if parallel=True.

Returns:

A list of sampled vectors.

Raises:

ValueError – If n is less than 1.

two_point_covariance(point: Any, /) Vector[source]

Computes the two-point covariance function radiating from a specific point.

Parameters:

point – The spatial coordinate to evaluate from.

Returns:

The covariance field evaluated at the chosen point.

Raises:

NotImplementedError – If the domain lacks a dirac_representation method.

weakened_ellipsoid(probability: float, /, *, theta: float, **kwargs)[source]

Shortcut for the weakened-covariance ellipsoid mode.

with_dense_covariance(*, parallel: bool = False, n_jobs: int = -1) GaussianMeasure[source]

Forms a new Gaussian measure equivalent to the existing one, but with its covariance matrix stored explicitly in dense form.

Parameters:
  • parallel – If True, computes the dense matrix concurrently.

  • n_jobs – Number of CPU cores to use if parallel=True.

Returns:

A new GaussianMeasure instance backed by a dense matrix.

with_regularized_inverse(solver: LinearSolver, /, *, damping: float = 0.0, preconditioner: LinearOperator | None = None) GaussianMeasure[source]

Returns a new GaussianMeasure with a well-defined precision operator (inverse covariance) computed via Tikhonov regularization.

Parameters:
  • solver – The linear solver used to invert the covariance.

  • damping – Tikhonov regularization parameter added to the diagonal.

  • preconditioner – Optional preconditioner for iterative solvers.

Returns:

A new GaussianMeasure instance equipped with an inverse covariance.

Raises:

ValueError – If the damping parameter is negative.

with_sparse_approximation(*, threshold: float = 0.001, max_nnz: int | None = None, diag_rank: int = 0, diag_samples: int = 0, regularization_fraction: float = 0.0001, parallel: bool = False, n_jobs: int = -1) GaussianMeasure[source]

Creates an approximately equivalent measure with a sparse covariance matrix and an exactly factorized sparse inverse, built entirely matrix-free.

Parameters:
  • threshold – Minimum correlation required to keep an off-diagonal element.

  • max_nnz – Maximum number of non-zero elements allowed per column.

  • diag_rank – Rank of deterministic SVD used to estimate the diagonal.

  • diag_samples – Number of stochastic samples used to estimate the diagonal.

  • regularization_fraction – Tikhonov regularization applied before sparse inversion.

  • parallel – If True, computes the sparse approximations concurrently.

  • n_jobs – Number of CPU cores to use if parallel=True.

Returns:

A new GaussianMeasure backed by sparse operators.

zero_expectation() GaussianMeasure[source]

Returns a new measure with the same covariance, but zero expectation.

Returns:

A mean-shifted GaussianMeasure.

class pygeoinf.HalfSpaceSupportFunction(primal_domain: HilbertSpace, normal_vector: Vector, offset: float, inequality_type: str = '<=', *, parallel_rtol: float = 1e-12, parallel_atol: float = 1e-14, return_min_norm_support_point: bool = True)[source]

Bases: NonLinearForm

Support function of a (closed) half-space in a Hilbert space H.

We support two conventions:

(<=) H = { x ∈ H : ⟨a, x⟩ ≤ b } (>=) H = { x ∈ H : ⟨a, x⟩ ≥ b } which is equivalent to { x : ⟨-a, x⟩ ≤ -b }.

Mathematical facts (extended-real-valued):

For H = {x : ⟨a, x⟩ ≤ b} with a ≠ 0,

σ_H(q) = sup_{x∈H} ⟨q, x⟩
= { α b, if q = α a with α ≥ 0,

+∞, otherwise. }

For H = {x : ⟨a, x⟩ ≥ b},

σ_H(q) = { α b, if q = α a with α ≤ 0,

+∞, otherwise. }

Notes

  • Half-spaces are unbounded, so σ_H is typically +∞.

  • This implementation returns float(‘inf’) for unbounded directions.

  • A support point is not unique when σ_H(q) is finite; the maximizers form the boundary hyperplane ⟨a, x⟩ = b. We optionally return the minimum-norm boundary point as a canonical representative.

property inequality_type: str
property normal_vector: Vector
property offset: float
support_point(q: Vector) 'Vector' | None[source]

Return a canonical maximizer when σ_H(q) is finite.

When finite, the maximizers are all x with ⟨a,x⟩ = b (boundary hyperplane). If return_min_norm_support_point=True, we return the minimum-norm boundary point:

x_min = (b / ||a||^2) a.

Otherwise return None (non-unique support set).

class pygeoinf.HilbertModule[source]

Bases: HilbertSpace, ABC

An ABC for a HilbertSpace where vector multiplication is defined.

This acts as a “mixin” interface, adding the vector_multiply requirement to the HilbertSpace contract.

abstract vector_multiply(x1: Vector, x2: Vector) Vector[source]

Computes the product of two vectors.

Parameters:
  • x1 – The first vector.

  • x2 – The second vector.

Returns:

The product of the two vectors.

abstract vector_sqrt(x: Vector) Vector[source]

Returns the square root of a vector.

class pygeoinf.HilbertSpace[source]

Bases: ABC, HilbertSpaceAxiomChecks

An abstract base class for real Hilbert spaces.

This class provides a mathematical abstraction for a vector space equipped with an inner product. It defines a formal interface that separates abstract vector operations from their concrete representation (e.g., as NumPy arrays). Subclasses must implement all abstract methods to be instantiable.

add(x: Vector, y: Vector) Vector[source]

Computes the sum of two vectors. Defaults to x + y.

ax(a: float, x: Vector) None[source]

Performs in-place scaling x := a*x. Defaults to x *= a.

axpy(a: float, x: Vector, y: Vector) None[source]

Performs in-place operation y := y + a*x. Defaults to y += a*x.

final basis_vector(i: int) Vector[source]

Returns the i-th standard basis vector.

This is the vector whose component array is all zeros except for a one at index i.

Parameters:

i – The index of the basis vector.

Returns:

The i-th basis vector.

property coordinate_inclusion: LinearOperator

The linear operator mapping R^n component vectors into this space.

property coordinate_projection: LinearOperator

The linear operator projecting vectors from this space to R^n.

copy(x: Vector) Vector[source]

Returns a deep copy of a vector. Defaults to x.copy().

abstract property dim: int

The finite dimension of the space.

property dual: HilbertSpace

The dual of this Hilbert space.

The dual space is the space of all continuous linear functionals (i.e., LinearForm objects) that map vectors from this space to real numbers. This implementation returns a DualHilbertSpace wrapper.

duality_product(xp: LinearForm, x: Vector) float[source]

Computes the duality product <xp, x>.

This evaluates the linear functional xp (an element of the dual space) at the vector x (an element of the primal space).

Parameters:
  • xp – The linear functional from the dual space.

  • x – The vector from the primal space.

Returns:

The result of the evaluation xp(x).

abstract from_components(c: ndarray) Vector[source]

Maps a NumPy component array back to a vector in the space.

Parameters:

c – The components of the vector as a NumPy array.

Returns:

The corresponding vector in the space.

abstract from_dual(xp: Any) Vector[source]

Maps a dual vector back to its representative in the primal space.

This is the inverse of the Riesz representation map defined by to_dual.

Parameters:

xp – A vector in the dual space.

Returns:

The corresponding vector in the primal space.

final gram_schmidt(vectors: List[Vector]) List[Vector][source]

Orthonormalizes a list of vectors using the Gram-Schmidt process.

Parameters:

vectors – A list of linearly independent vectors.

Returns:

A list of orthonormalized vectors spanning the same subspace.

Raises:

ValueError – If not all items in the list are elements of the space.

final identity_operator() LinearOperator[source]

Returns the identity operator I on the space.

inner_product(x1: Vector, x2: Vector) float[source]

Computes the inner product of two vectors, (x1, x2).

This is defined via the duality product as <R(x1), x2>, where R is the Riesz map (to_dual).

Parameters:
  • x1 – The first vector.

  • x2 – The second vector.

Returns:

The inner product as a float.

property inverse_riesz: LinearOperator

The inverse Riesz map (primal to dual) as a LinearOperator.

is_element(x: Any) bool[source]

Checks if an object is a valid element of the space.

Note: The default implementation checks the object’s type against the type of the zero vector. This may not be robust for all vector representations and can be overridden if needed.

Parameters:

x – The object to check.

Returns:

True if the object is an element of the space, False otherwise.

multiply(a: float, x: Vector) Vector[source]

Computes scalar multiplication. Defaults to a * x.

negative(x: Vector) Vector[source]

Computes the additive inverse of a vector. Defaults to -1 * x.

final norm(x: Vector) float[source]

Computes the norm of a vector, ||x||.

Parameters:

x – The vector.

Returns:

The norm of the vector.

random() Vector[source]

Generates a random vector from the space.

The vector’s components are drawn from a standard normal distribution.

Returns:

A new random vector.

property riesz: LinearOperator

The Riesz map (dual to primal) as a LinearOperator.

final sample_expectation(vectors: List[Vector]) Vector[source]

Computes the sample mean of a list of vectors.

Parameters:

vectors – A list of vectors from the space.

Returns:

The sample mean (average) vector.

Raises:

TypeError – If not all items in the list are elements of the space.

final squared_norm(x: Vector) float[source]

Computes the squared norm of a vector, ||x||^2.

Parameters:

x – The vector.

Returns:

The squared norm of the vector.

subtract(x: Vector, y: Vector) Vector[source]

Computes the difference of two vectors. Defaults to x - y.

abstract to_components(x: Vector) ndarray[source]

Maps a vector to its representation as a NumPy component array.

Parameters:

x – A vector in the space.

Returns:

The components of the vector as a NumPy array.

abstract to_dual(x: Vector) Any[source]

Maps a vector to its canonical dual vector (a linear functional).

This method, along with from_dual, defines the Riesz representation map and implicitly defines the inner product of the space.

Parameters:

x – A vector in the primal space.

Returns:

The corresponding vector in the dual space.

property zero: Vector

The zero vector (additive identity) of the space.

final zero_operator(codomain: HilbertSpace | None = None) LinearOperator[source]

Returns the zero operator 0 from this space to a codomain.

Parameters:

codomain – The target space of the operator. If None, the operator maps to this space itself.

Returns:

The zero linear operator.

class pygeoinf.HilbertSpaceDirectSum(spaces: List[HilbertSpace])[source]

Bases: HilbertSpace

A Hilbert space formed from the direct sum of a list of other spaces.

A vector in this space is represented as a list of vectors, where the i-th element of the list is a vector from the i-th component subspace. The inner product is the sum of the inner products of the components.

add(xs: List[Any], ys: List[Any]) List[Any][source]

Computes the sum of two vectors. Defaults to x + y.

ax(a: float, xs: List[Any]) None[source]

Performs in-place scaling x := a*x. Defaults to x *= a.

axpy(a: float, xs: List[Any], ys: List[Any]) None[source]

Performs in-place operation y := y + a*x. Defaults to y += a*x.

canonical_dual_inverse_isomorphism(xp: LinearForm) List[LinearForm][source]

Maps a dual vector on the sum space to a list of dual vectors.

This is the inverse of the canonical isomorphism, projecting the action of a dual vector onto each subspace.

Parameters:

xp (LinearForm) – A dual vector on the direct sum space.

canonical_dual_isomorphism(xps: List[LinearForm]) LinearForm[source]

Maps a list of dual vectors to a single dual vector on the sum space.

This is the canonical isomorphism from the direct sum of the dual spaces to the dual of the direct sum space.

Parameters:

xps (List[LinearForm]) – A list of dual vectors, one for each subspace.

copy(xs: List[Any]) List[Any][source]

Returns a deep copy of a vector. Defaults to x.copy().

property dim: int

Returns the dimension of the direct sum space.

from_components(c: ndarray) List[Any][source]

Maps a NumPy component array back to a vector in the space.

Parameters:

c – The components of the vector as a NumPy array.

Returns:

The corresponding vector in the space.

from_dual(xp: LinearForm) List[Any][source]

Maps a dual vector back to its representative in the primal space.

This is the inverse of the Riesz representation map defined by to_dual.

Parameters:

xp – A vector in the dual space.

Returns:

The corresponding vector in the primal space.

is_element(xs: Any) bool[source]

Checks if a list of vectors is a valid element of the direct sum space.

multiply(a: float, xs: List[Any]) List[Any][source]

Computes scalar multiplication. Defaults to a * x.

property number_of_subspaces: int

Returns the number of subspaces in the direct sum.

subspace(i: int) HilbertSpace[source]

Returns the i-th subspace.

Parameters:

i (int) – The index of the subspace to retrieve.

subspace_inclusion(i: int) LinearOperator[source]

Returns the inclusion operator from the i-th subspace into the sum.

Parameters:

i (int) – The index of the subspace to include from.

subspace_projection(i: int) LinearOperator[source]

Returns the projection operator onto the i-th subspace.

Parameters:

i (int) – The index of the subspace to project onto.

property subspaces: List[HilbertSpace]

Returns the list of subspaces that form the direct sum.

subtract(xs: List[Any], ys: List[Any]) List[Any][source]

Computes the difference of two vectors. Defaults to x - y.

to_components(xs: List[Any]) ndarray[source]

Maps a vector to its representation as a NumPy component array.

Parameters:

x – A vector in the space.

Returns:

The components of the vector as a NumPy array.

to_dual(xs: List[Any]) LinearForm[source]

Maps a vector to its canonical dual vector (a linear functional).

This method, along with from_dual, defines the Riesz representation map and implicitly defines the inner product of the space.

Parameters:

x – A vector in the primal space.

Returns:

The corresponding vector in the dual space.

property zero: List[Any]

each subspace contributes its own zero.

Type:

The zero element

class pygeoinf.IdentityPreconditioningMethod[source]

Bases: LinearSolver

A trivial preconditioning method that returns the Identity operator.

This acts as a “no-op” placeholder in the preconditioning framework, useful for benchmarking or default configurations.

class pygeoinf.Intersection(subsets: Iterable[Subset])[source]

Bases: Subset

Represents the generic intersection of multiple subsets: S = S_1 ∩ S_2 …

Used when the subsets cannot be mathematically combined into a single functional (e.g., non-convex sets).

property boundary: Subset

Returns the boundary of the intersection.

The general topological boundary is complex: ∂(A ∩ B) ⊆ (∂A ∩ B) ∪ (A ∩ ∂B). Currently raises NotImplementedError.

property complement: Subset

Returns the complement of the intersection.

Applies De Morgan’s Law: (A ∩ B)^c = A^c ∪ B^c. Returns a Union of the complements.

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if x is in ALL component subsets.

property subsets: List[Subset]

Direct access to the component sets.

class pygeoinf.IterativeLinearSolver(*, preconditioning_method: LinearSolver = None)[source]

Bases: LinearSolver

An abstract base class for iterative linear solvers.

property iterations: int

Returns the number of iterations within the last solve. The value is zero if the solver has yet to be called.

solve_adjoint_linear_system(operator: LinearOperator, adjoint_preconditioner: LinearOperator | None, x: Vector, y0: Vector | None) Vector[source]

Solves the adjoint linear system A*y = x for y.

abstract solve_linear_system(operator: LinearOperator, preconditioner: LinearOperator | None, y: Vector, x0: Vector | None) Vector[source]

Solves the linear system Ax = y for x.

Parameters:
  • operator (LinearOperator) – The operator A of the linear system.

  • preconditioner (LinearOperator, optional) – The preconditioner.

  • y (Vector) – The right-hand side vector.

  • x0 (Vector, optional) – The initial guess for the solution.

Returns:

The solution vector x.

Return type:

Vector

class pygeoinf.IterativePreconditioningMethod(inner_solver: IterativeLinearSolver, max_inner_iter: int = 5, rtol: float = 0.1)[source]

Bases: LinearSolver

Wraps an iterative solver to act as a preconditioner.

This is best used with FCGSolver to handle the potential variability of the inner iterations.

class pygeoinf.JacobiPreconditioningMethod(num_samples: int | None = 20, method: str = 'variable', rtol: float = 0.01, block_size: int = 10, parallel: bool = False, n_jobs: int = -1)[source]

Bases: LinearSolver

A LinearSolver wrapper that generates a Jacobi preconditioner.

class pygeoinf.KKTResult(m: Vector, multipliers: tuple[float, float], converged: bool, num_iterations: int)[source]

Bases: object

Result from PrimalKKTSolver.

m

Optimal primal model vector $u^*$ in the model space.

Type:

Vector

multipliers

KKT multipliers $(lambda^*, mu^*)$. $lambda^*$ enforces the model prior constraint and $mu^*$ enforces the data-fit constraint. Both are non-negative.

Type:

tuple[float, float]

converged

True if the root-finder converged to the required tolerance.

Type:

bool

num_iterations

Number of function evaluations used by the root-finder (or 1 for the closed-form branch).

Type:

int

converged: bool
m: Vector
multipliers: tuple[float, float]
num_iterations: int
class pygeoinf.LUSolver(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1)[source]

Bases: DirectLinearSolver

A direct linear solver based on the LU decomposition of an operator’s dense matrix representation.

class pygeoinf.LanczosOperatorFunction(operator: LinearOperator, func: Callable[[ndarray], ndarray], size_estimate: int, *, method: Literal['variable', 'fixed'] = 'variable', max_k: int | None = None, reorth: str = 'full', rtol: float = 0.001, atol: float = 1e-08, check_interval: int = 5)[source]

Bases: LinearOperator

A matrix-free LinearOperator representing the action of a continuous function applied to a self-adjoint positive operator.

Rather than explicitly computing and storing the dense matrix f(A), this class evaluates the matrix-vector product f(A)x dynamically on the fly using the Lanczos process. This allows for highly efficient evaluations in massive or infinite-dimensional Hilbert spaces.

property base_operator: LinearOperator

Returns the underlying base LinearOperator.

class pygeoinf.LevelSet(form: NonLinearForm, level: float)[source]

Bases: Subset

Represents a level set of a functional: S = {x | f(x) = c}.

property boundary: Subset

Returns the boundary of the level set. Assuming regularity, a level set is a closed manifold without boundary.

property form: NonLinearForm

The defining functional f(x).

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if f(x) is approximately equal to c. Tolerance is scaled by max(1.0, |c|).

property level: float

The scalar value c.

class pygeoinf.LinearBayesianInversion(forward_problem: LinearForwardProblem, model_prior_measure: GaussianMeasure, /, *, formalism: Literal['model_space', 'data_space'] = 'data_space')[source]

Bases: LinearInversion

Solves a linear inverse problem using Bayesian methods.

This class applies to problems of the form d = A(u) + e, where u is a Gaussian random variable representing the model prior, and e is a Gaussian random variable representing observation noise.

It computes the exact posterior Gaussian measure p(u|d), providing access to the posterior expectation, the posterior covariance operator, and an efficient exact-sampling mechanism using the randomize-then-optimize technique.

property data_prior_measure: GaussianMeasure

The prior predictive distribution on the data space. This represents the expected distribution of data before observation.

data_reduced_inversion(reduction_operator: LinearOperator, /, *, reduced_data_error_measure: GaussianMeasure | None = None, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) LinearBayesianInversion[source]

Constructs a surrogate of the Bayesian inversion using a reduced data space.

Parameters:
  • reduction_operator – A LinearOperator mapping from the current data space to the new, reduced data space.

  • reduced_data_error_measure – An optional data error measure defined on the reduced data space.

  • dense – If True, computes and stores operators as dense matrices.

  • parallel – If True, computes the dense matrices in parallel.

  • n_jobs – Number of CPU cores to use. -1 means all available.

  • formalism – An optional override for the formalism of the new inversion.

Returns:

A new LinearBayesianInversion instance operating on the reduced data space.

diagonal_normal_preconditioner(*, blocks: List[List[int]] | None = None, parallel: bool = False, n_jobs: int = -1) LinearOperator[source]

Constructs a diagonal preconditioner specifically for the data-space Bayesian normal operator (A Q A* + R).

This exploits the identity <v, A Q A* v> = <A* v, Q A* v>. If blocks of data indices are provided, it acts on the averaged basis vector for each block to compute a robust representative regional variance, requiring only one adjoint action of the forward operator per block.

Parameters:
  • blocks – An optional list of lists, where each sub-list contains indices of data points grouped together. Must perfectly partition the data space.

  • parallel – If True, computes the adjoint actions in parallel.

  • n_jobs – Number of parallel jobs to use. -1 means all available cores.

Returns:

A DiagonalSparseMatrixLinearOperator representing the inverse of the approximated normal operator.

Raises:

ValueError – If the inversion was initialized with formalism=’model_space’, as this preconditioner is mathematically invalid for that normal operator.

estimate_log_determinant(*, operator_type: Literal['data_space', 'model_space'] = 'data_space', size_estimate: int = 10, method: Literal['variable', 'fixed'] = 'variable', max_samples: int | None = None, rtol: float = 0.01, block_size: int = 5, lanczos_degree: int = 40, lanczos_rtol: float | None = 0.001, parallel: bool = False, n_jobs: int = -1) float[source]

Estimates the log-determinant of the Bayesian normal operator using Stochastic Lanczos Quadrature (SLQ).

This acts as a public interface for computing the log-determinant of either the data-space normal operator (A Q A* + R) or the model-space normal operator (Q^-1 + A* R^-1 A). It securely resolves the correct algebraic space and delegates to the internal matrix-free SLQ engine.

Parameters:
  • operator_type – The target normal operator (‘data_space’ or ‘model_space’).

  • size_estimate – Initial number of Hutchinson samples (probe vectors).

  • method – ‘variable’ to sample until ‘rtol’ is met, ‘fixed’ otherwise.

  • max_samples – Hard limit on the number of Hutchinson samples.

  • rtol – Relative tolerance for the Hutchinson trace estimate.

  • block_size – Number of new samples per iteration in the ‘variable’ method.

  • lanczos_degree – Maximum Krylov dimension (k) per probe vector.

  • lanczos_rtol – Relative tolerance for dynamic Lanczos truncation. If None, uses fixed ‘lanczos_degree’ steps.

  • parallel – If True, evaluates probe vectors in parallel.

  • n_jobs – Number of CPU cores to use if parallel=True.

Returns:

The estimated log-determinant ln(|N|).

Return type:

float

get_normal_equations_rhs(data: Vector) Vector[source]

Computes the exact right-hand side vector (v) of the normal equations N * w = v for a given observed data vector, automatically accounting for non-zero prior and noise expectations.

property joint_prior_measure: GaussianMeasure

The joint prior distribution of both the model and the data.

kalman_operator(solver: LinearSolver, /, *, preconditioner: LinearOperator | None = None) LinearOperator[source]

Constructs the Kalman gain operator K.

The Kalman gain maps data residuals to model space updates.

For ‘data_space’: K = Q A* (A Q A* + R)^-1 For ‘model_space’: K = (Q^-1 + A* R^-1 A)^-1 A* R^-1

Parameters:
  • solver – The LinearSolver used to invert the normal operator.

  • preconditioner – Optional preconditioner for iterative solvers.

Returns:

A LinearOperator representing the Kalman gain.

log_evidence(data: Vector, solver: LinearSolver, /, *, preconditioner: LinearOperator | None = None, size_estimate: int = 10, method: Literal['variable', 'fixed'] = 'variable', max_samples: int | None = None, rtol: float = 0.01, block_size: int = 5, lanczos_degree: int = 40, lanczos_rtol: float | None = 0.001, parallel: bool = False, n_jobs: int = -1) float[source]

Computes the approximate log marginal likelihood (evidence) of the data, ln(p(d)).

The log-evidence is a critical metric for Bayesian model selection, allowing users to quantitatively compare different prior assumptions or forward models. It evaluates the likelihood of the observed data marginalized over all possible model states.

The computation evaluates the Gaussian marginal density equation:

ln p(d) = -0.5 * [ ln|N_d| + <v, N_d^-1 v> + M * ln(2*pi) ]

Parameters:
  • data – The observed data vector ‘d’.

  • solver – The LinearSolver used to invert the normal operator for the Mahalanobis misfit term.

  • preconditioner – Optional preconditioner for the iterative solver.

  • size_estimate – Initial number of SLQ Hutchinson samples.

  • method – ‘variable’ to dynamically bound SLQ error, ‘fixed’ otherwise.

  • max_samples – Hard limit on SLQ Hutchinson samples.

  • rtol – Relative tolerance for the SLQ trace estimate.

  • block_size – Number of new SLQ samples per iteration.

  • lanczos_degree – Maximum Krylov dimension for SLQ.

  • lanczos_rtol – Relative tolerance for dynamic Lanczos truncation.

  • parallel – If True, evaluates SLQ probe vectors in parallel.

  • n_jobs – Number of CPU cores to use if parallel=True.

Returns:

The estimated log-evidence ln(p(d)).

Return type:

float

Raises:

ValueError – If the ‘model_space’ formalism is used but no data error measure has been set on the forward problem.

low_rank_surrogate(*, forward_rank: int | None = None, prior_rank: int | None = None, data_error_rank: int | None = None, forward_kwargs: dict | None = None, prior_kwargs: dict | None = None, data_error_kwargs: dict | None = None) LinearBayesianInversion[source]

Constructs a surrogate Bayesian inversion problem by replacing the exact physics and statistical measures with their low-rank approximations.

This method generates computationally cheap surrogate models to be used in constructing preconditioners for massive, ill-conditioned inverse problems (e.g., using spectral or banded methods). The low-rank approximations are computed using randomized SVD and eigendecomposition algorithms.

Parameters:
  • forward_rank – Target rank for the randomized SVD of the forward operator.

  • prior_rank – Target rank for the randomized eigendecomposition of the prior.

  • data_error_rank – Target rank for the randomized eigendecomposition of the noise.

  • forward_kwargs – Additional kwargs passed directly to LinearOperator.random_svd.

  • prior_kwargs – Additional kwargs passed directly to GaussianMeasure.low_rank_approximation.

  • data_error_kwargs – Additional kwargs passed directly to GaussianMeasure.low_rank_approximation.

Returns:

A LinearBayesianInversion representing the low-rank surrogate problem.

mahalanobis_evidence_term(data: Vector, solver: LinearSolver, /, *, preconditioner: LinearOperator | None = None) float[source]

Computes the data-dependent Mahalanobis term of the log-evidence.

This value represents the optimal, penalty-balanced data misfit. It is equivalent to the unnormalized log-posterior evaluated at the posterior expectation.

Mathematically, for a shifted data residual vector v = d - A(mu_u) - mu_e, the term evaluates the quadratic form:

Misfit = <v, (A Q A* + R)^-1 v>

In the ‘model_space’ formalism, this is computed far more efficiently using the Woodbury matrix identity to bypass the massive data-space inversion:

Misfit = <v, R^-1 v> - <A* R^-1 v, (Q^-1 + A* R^-1 A)^-1 A* R^-1 v>

Parameters:
  • data – The observed data vector ‘d’.

  • solver – The LinearSolver used to invert the normal operator.

  • preconditioner – An optional preconditioner to accelerate iterative solvers.

Returns:

The scalar Mahalanobis distance.

Return type:

float

Raises:

ValueError – If the forward problem lacks a defined data error measure.

model_posterior_measure(data: Vector, solver: LinearSolver, /, *, preconditioner: LinearOperator | None = None) GaussianMeasure[source]

Computes and returns the posterior Gaussian measure p(u|d).

This method applies the Kalman update equations to find the posterior expectation and covariance. If both the prior and data error measures have sampling enabled, it automatically constructs a randomize-then-optimize exact sampling function for the posterior.

Parameters:
  • data – The observed data vector.

  • solver – A linear solver for inverting the normal operator.

  • preconditioner – An optional preconditioner for iterative solvers.

Returns:

A GaussianMeasure representing the posterior distribution.

property model_prior_measure: GaussianMeasure

The prior Gaussian measure on the model space.

property normal_operator: LinearOperator

Constructs the Bayesian Normal operator for the chosen formalism.

For ‘data_space’: Returns N = A Q A* + R For ‘model_space’: Returns N = Q^-1 + A* R^-1 A

Returns:

A LinearOperator representing the normal equations matrix.

normal_residual_callback(data: Vector, /, *, message: str = 'CG Iteration: {iter} | Normal Residual: {res:.3e}', print_progress: bool = True) ResidualTrackingCallback[source]

Generates a ResidualTrackingCallback pre-configured to track the convergence of the Bayesian normal equations for the given data vector.

Parameters:
  • data – The observed data vector.

  • message – The formatting string for printing progress.

  • print_progress – If True, prints the message to stdout at each iteration.

Returns:

A configured ResidualTrackingCallback ready to be passed to a solver.

parameterized_inversion(parameterization: LinearOperator, /, *, parameter_prior: GaussianMeasure | None = None, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) LinearBayesianInversion[source]

Constructs a parameterized surrogate of the Bayesian inversion.

If the target formalism resolves to ‘model_space’ (which is typical for parameterized inversions), the parameter prior’s covariance matrix will be automatically densified to explicitly compute the required precision (inverse covariance) operator.

Parameters:
  • parameterization – A LinearOperator mapping from the parameter space to the full model space.

  • parameter_prior – An optional prior measure on the parameter space. If not provided, the original model prior is pulled back to the parameter space automatically.

  • dense – If True, computes and stores operators as dense matrices.

  • parallel – If True, computes the dense matrices in parallel.

  • n_jobs – Number of CPU cores to use. -1 means all available.

  • formalism – An optional override for the formalism of the new inversion. If None, inherits the formalism of the parent inversion.

Returns:

A new LinearBayesianInversion instance operating on the parameter space.

posterior_expectation_operator(solver: LinearSolver, /, *, preconditioner: LinearOperator | None = None) LinearOperator | AffineOperator[source]

Constructs the operator mapping observed data to the posterior expectation.

The mapping evaluates F(d) = mu_u + K(d - A(mu_u) - mu_e).

If the prior and data error measures both have a zero expectation, this mapping is purely linear and returns the Kalman gain operator directly. Otherwise, it regroups the terms into an AffineOperator: F(d) = K(d) + (mu_u - K(A(mu_u) + mu_e)).

Parameters:
  • solver – The LinearSolver used to invert the normal operator.

  • preconditioner – Optional preconditioner for iterative solvers.

Returns:

A LinearOperator (if expectations are zero) or an AffineOperator.

sparse_localized_preconditioner(interacting_blocks: list[list[int]], rank: int = 10, parallel: bool = False, n_jobs: int = -1) LinearOperator[source]

Builds a sparse preconditioner specifically for the data-space Bayesian normal equations using randomized Nystrom approximations on localized, potentially overlapping sub-blocks.

Parameters:
  • interacting_blocks – A list of lists, where each sub-list contains the indices of data points that strongly couple to each other.

  • rank – The rank of the randomized Nystrom approximation to use per block.

  • parallel – If True, computes the sub-block approximations in parallel.

  • n_jobs – Number of CPU cores to use if parallel=True (-1 uses all cores).

Returns:

A LinearOperator representing the inverse of the sparse approximation.

Raises:

ValueError – If the inversion was initialized with formalism=’model_space’, as this preconditioner is mathematically invalid for that normal operator.

surrogate_inversion(*, alternate_forward_operator: LinearOperator | None = None, alternate_prior_measure: GaussianMeasure | None = None, alternate_data_error_measure: GaussianMeasure | None = None) LinearBayesianInversion[source]

Constructs a surrogate Bayesian inversion problem using simplified physics, priors, or data errors.

This is primarily used to construct robust, computationally cheap surrogate models to use as preconditioners for the full, complex inverse problem.

Parameters:
  • alternate_forward_operator – An optional simplified forward operator.

  • alternate_prior_measure – An optional simplified prior measure.

  • alternate_data_error_measure – An optional simplified data error measure.

Returns:

A new LinearBayesianInversion instance representing the surrogate problem. The surrogate inherits the formalism of the parent problem.

Raises:

ValueError – If the alternative operators/measures exist in incompatible domains/codomains.

surrogate_normal_preconditioner(solver: LinearSolver, /, *, alternate_forward_operator: LinearOperator | None = None, alternate_prior_measure: GaussianMeasure | None = None, alternate_data_error_measure: GaussianMeasure | None = None) LinearOperator[source]

Builds a preconditioner by exactly inverting the normal operator of a simplified surrogate inverse problem.

Parameters:
  • solver – The LinearSolver to use to exactly invert the surrogate normal operator.

  • alternate_forward_operator – An optional simplified forward operator.

  • alternate_prior_measure – An optional simplified prior measure.

  • alternate_data_error_measure – An optional simplified data error measure.

Returns:

A LinearOperator representing the inverse of the surrogate normal equations.

surrogate_woodbury_data_preconditioner(solver: LinearSolver, /, *, alternate_forward_operator: LinearOperator | None = None, alternate_prior_measure: GaussianMeasure | None = None, alternate_data_error_measure: GaussianMeasure | None = None, prior_solver: LinearSolver | None = None, noise_solver: LinearSolver | None = None) LinearOperator[source]

Builds a data-space preconditioner by applying the Woodbury matrix identity to a simplified surrogate inverse problem.

This method chains the construction of the surrogate model with the extraction of the Woodbury inverse in one step.

Note

Ensure that any alternate measures provided have well-defined inverse covariances, or use .with_regularized_inverse() on them before passing them to this method.

Parameters:
  • solver – The LinearSolver used to invert the inner Woodbury operator.

  • alternate_forward_operator – An optional simplified forward operator.

  • alternate_prior_measure – An optional simplified prior measure.

  • alternate_data_error_measure – An optional simplified data error measure.

  • prior_solver – Optional solver for the prior covariance.

  • noise_solver – Optional solver for the noise covariance.

Returns:

A LinearOperator representing the Woodbury-approximated inverse.

surrogate_woodbury_model_preconditioner(solver: LinearSolver, /, *, alternate_forward_operator: LinearOperator | None = None, alternate_prior_measure: GaussianMeasure | None = None, alternate_data_error_measure: GaussianMeasure | None = None) LinearOperator[source]

Builds a model-space preconditioner by applying the Woodbury matrix identity to a simplified surrogate inverse problem.

Parameters:
  • solver – The LinearSolver used to invert the inner Woodbury operator.

  • alternate_forward_operator – An optional simplified forward operator.

  • alternate_prior_measure – An optional simplified prior measure.

  • alternate_data_error_measure – An optional simplified data error measure.

Returns:

A LinearOperator representing the Woodbury-approximated inverse.

with_formalism(formalism: Literal['model_space', 'data_space']) LinearBayesianInversion[source]

Returns a new instance of the Bayesian inversion using the specified formalism.

Parameters:

formalism – The algebraic space in which the normal equations should be assembled and solved. Must be ‘model_space’ or ‘data_space’.

Returns:

A new LinearBayesianInversion instance sharing the exact same forward problem and prior measure, but configured to use the specified formalism.

woodbury_data_preconditioner(solver: LinearSolver, /, *, prior_solver: LinearSolver | None = None, noise_solver: LinearSolver | None = None) LinearOperator[source]

Constructs a data-space preconditioner using the Woodbury matrix identity.

Data Space Normal Operator: N_d = A Q A* + R Woodbury Identity: N_d^-1 = R^-1 - R^-1 A (Q^-1 + A* R^-1 A)^-1 A* R^-1

Note

This method assumes the prior (Q) and noise (R) measures either already have well-defined inverse covariances, or are well-conditioned enough to be inverted by the provided solvers. If your prior is an unbounded operator on a function space, use Q.with_regularized_inverse() before passing it to the inversion.

Parameters:
  • solver – The LinearSolver used to invert the inner Woodbury operator (N_m).

  • prior_solver – An optional solver used to explicitly invert the prior covariance (Q) if its inverse is not already set. Defaults to solver if not provided.

  • noise_solver – An optional solver used to explicitly invert the data error covariance (R) if its inverse is not already set. Defaults to solver if not provided.

Returns:

A LinearOperator representing the Woodbury-approximated inverse.

woodbury_model_preconditioner(solver: LinearSolver, /) LinearOperator[source]

Constructs a model-space preconditioner using the Woodbury matrix identity.

Model Space Normal Operator: N_m = Q^-1 + A* R^-1 A Woodbury Identity: N_m^-1 = Q - Q A* (R + A Q A*)^-1 A Q

Note

Unlike the data-space Woodbury identity, this formulation does not require evaluating the explicit inverses of the prior (Q) or noise (R) covariances, making it highly robust for unbounded or complex measures.

Parameters:

solver – The LinearSolver used to invert the inner Woodbury operator (N_d).

Returns:

A LinearOperator representing the Woodbury-approximated inverse.

class pygeoinf.LinearForm(domain: HilbertSpace, /, *, components: np.ndarray | None = None, mapping: Callable[[Vector], float] | None = None, parallel: bool = False, n_jobs: int = -1)[source]

Bases: NonLinearForm

Represents a linear form as an efficient, component-based functional.

A LinearForm is an element of a dual HilbertSpace and is defined by its action on vectors from its domain. Internally, this action is represented by a component vector. This class provides optimized arithmetic operations and correctly defines the gradient (a constant vector) and the Hessian (the zero operator) for any linear functional.

property as_linear_operator: LinearOperator

Represents the linear form as a LinearOperator.

The resulting operator maps from the form’s original domain to a 1-dimensional EuclideanSpace, where the single component of the output is the scalar result of the form’s action.

property components: ndarray

The component vector of the form.

copy() LinearForm[source]

Creates a deep copy of the linear form.

property domain: HilbertSpace

The Hilbert space on which the form is defined.

static from_linear_operator(operator: LinearOperator) LinearForm[source]

Creates a LinearForm from an operator that maps to a 1D Euclidean space.

class pygeoinf.LinearForwardProblem(forward_operator: LinearOperator, /, *, data_error_measure: GaussianMeasure | None = None)[source]

Bases: ForwardProblem

Represents a linear forward problem of the form d = A(u) + e.

Here, d is the data, A is the linear forward operator, u is the model, and e is a random error drawn from a Gaussian distribution.

chi_squared(model: Vector, data: Vector) float[source]

Calculates the chi-squared statistic for a given model and data.

This measures the misfit between the predicted and observed data.

  • If a data error measure with an inverse covariance C_e^-1 is defined, this is the weighted misfit: (d - A(u))^T * C_e^-1 * (d - A(u)).

  • Otherwise, it is the squared L2 norm of the data residual: ||d - A(u)||^2.

Parameters:
  • model – A vector from the model space.

  • data – An observed data vector from the data space.

Returns:

The chi-squared statistic.

chi_squared_from_residual(residual: Vector) float[source]

Calculates the chi-squared statistic from a residual vector.

Parameters:

residual – The residual vector.

Returns:

The chi-squared statistic.

chi_squared_test(significance_level: float, model: Vector, data: Vector) bool[source]

Performs a chi-squared test for goodness of fit.

Parameters:
  • significance_level – The significance level for the test (e.g., 0.95).

  • model – A vector from the model space.

  • data – An observed data vector from the data space.

Returns:

True if the model is statistically compatible with the data at the specified significance level, False otherwise.

critical_chi_squared(significance_level: float) float[source]

Returns the critical value of the chi-squared statistic.

This value serves as the threshold for the chi-squared test at a given significance level.

Parameters:

significance_level – The desired significance level (e.g., 0.95).

Returns:

The critical chi-squared value.

data_measure_from_model(model: Vector) GaussianMeasure[source]

Returns the Gaussian measure for the data, given a specific model.

The resulting measure has a mean of A(model) and the covariance of the data error.

Parameters:

model – A vector from the model space.

Returns:

The Gaussian measure representing the distribution of possible data.

data_measure_from_model_measure(model_measure: GaussianMeasure) GaussianMeasure[source]

Given a measure for the model space, returns the induced measure on the data space.

data_reduced_problem(reduction_operator: LinearOperator, /, *, reduced_data_error_measure: GaussianMeasure | None = None, dense: bool = False, parallel: bool = False, n_jobs: int = -1) LinearForwardProblem[source]

Creates a new forward problem by applying a reduction (or sketching) operator to the data space.

Parameters:
  • reduction_operator – A LinearOperator mapping from the current data space to the new reduced data space.

  • reduced_data_error_measure – An optional data error measure on the reduced data space. If not provided, the original data error measure is pushed forward automatically.

  • dense – If True, computes and stores operators as dense matrices.

  • parallel – If True, computes the dense matrices in parallel.

  • n_jobs – Number of CPU cores to use. -1 means all available.

Returns:

A new LinearForwardProblem operating in the reduced data space.

static from_direct_sum(forward_problems: List[LinearForwardProblem]) LinearForwardProblem[source]

Forms a joint forward problem from a list of separate problems.

This is a powerful tool for joint inversions, where a single underlying model is observed through multiple, independent measurement systems (e.g., different types of geophysical surveys).

Parameters:

forward_problems – A list of LinearForwardProblem instances that share a common model space.

Returns:

A single LinearForwardProblem where the data space is the direct sum of the individual data spaces.

joint_measure(model_measure: GaussianMeasure) GaussianMeasure[source]

Given a measure for the model space, returns the joint measure for the model and data.

parameterized_problem(parameterization: LinearOperator, /, *, dense: bool = False, parallel: bool = False, n_jobs: int = -1) LinearForwardProblem[source]

Creates a new forward problem based on a model parameterization.

synthetic_data(model: Vector) Vector[source]

Generates a synthetic data vector for a given model.

The data is computed as d = A(model) + e, where e is a random sample from the data error measure.

Parameters:

model – A vector from the model space.

Returns:

A synthetic data vector.

synthetic_model_and_data(prior: GaussianMeasure) Tuple[Vector, Vector][source]

Generates a random model and corresponding synthetic data.

Parameters:

prior – A Gaussian measure on the model space, from which the random model u will be drawn.

Returns:

A tuple (u, d), where u is the random model and d is the corresponding synthetic data.

class pygeoinf.LinearImageSupportFunction(base: SupportFunction, operator: LinearOperator)[source]

Bases: SupportFunction

Support function of the linear image $A(C)$ of a convex set $C$.

For a convex set $C subseteq H$ with support function $h_C$ and a bounded linear operator $A: H to K$, the support function of the image $A(C) subseteq K$ is

\[h_{A(C)}(q) = h_C(A^* q), \quad q \in K,\]

where $A^*: K to H$ is the Hilbert-space adjoint of $A$.

Parameters:
  • base – The support function $h_C$ of the base set $C subseteq H$. Its primal_domain must equal operator.domain.

  • operator – A bounded linear operator $A: H to K$. operator.domain must equal base.primal_domain.

Raises:

ValueError – If operator.domain does not equal base.primal_domain.

Note

The primal_domain of the returned object is operator.codomain (the space $K$ where the image $A(C)$ lives).

Note

Phase 3: support_point() propagates support points from the base, returning $x_C^*(A^* q)$ when available, or None if the base has no support point available.

support_point(q: Vector) 'Vector' | None[source]

Return the support point of the image set $A(C)$ at direction $q in K$.

For the image support function $h_{A(C)}(q) = h_C(A^* q)$, the support point in direction $q$ is obtained by computing the base support point $x_C^*(A^* q)$ and then applying the operator: $x_{A(C)}^*(q) = A(x_C^*(A^* q))$.

Parameters:

q – A vector in the codomain $K$.

Returns:

The support point $A(x_C^*(A^* q)) in K$ if available, or None.

class pygeoinf.LinearLeastSquaresInversion(forward_problem: LinearForwardProblem, /, *, formalism: Literal['model_space', 'data_space'] = 'data_space')[source]

Bases: LinearInversion

Solves a linear inverse problem using Tikhonov-regularized least-squares.

This method finds the model u that minimizes the cost functional:

J(u) = ||A(u) - d||^2_R + damping * ||u||^2

where A is the forward operator, d is the observed data, R is the data covariance (if a data error measure is set), and damping is the Tikhonov regularization parameter.

This class supports two formalisms for constructing the linear system: 1. ‘model_space’: Solves the standard normal equations of size (N x N),

where N is the model dimension. Best for overdetermined problems.

  1. ‘data_space’: Solves the dual formulation of size (M x M), where M is the data dimension. Best for highly underdetermined problems.

data_reduced_inversion(reduction_operator: LinearOperator, /, *, reduced_data_error_measure: GaussianMeasure | None = None, dense: bool = False, parallel: bool = False, n_jobs: int = -1, formalism: Literal['model_space', 'data_space'] | None = None) LinearLeastSquaresInversion[source]

Constructs a surrogate of the least-squares inversion using a reduced data space.

least_squares_operator(damping: float, solver: LinearSolver, /, *, preconditioner: LinearOperator | LinearSolver | None = None) LinearOperator | AffineOperator[source]

Constructs the full operator that maps observed data directly to the least-squares model solution.

This method solves the internal normal equations and applies the necessary algebraic transformations (and affine shifts) to recover the model parameters from the data, seamlessly handling whichever formalism was selected during initialization.

Parameters:
  • damping – The Tikhonov regularization parameter.

  • solver – The LinearSolver instance used to invert the normal operator.

  • preconditioner – An optional LinearOperator, or a LinearSolver factory, used to precondition the normal equations. Only utilized if the provided solver is an IterativeLinearSolver.

Returns:

A LinearOperator (or AffineOperator, if a non-zero data expectation exists) that maps a vector from the data space to the optimal vector in the model space.

Raises:

TypeError – If the provided preconditioner is neither a LinearOperator nor a LinearSolver.

normal_operator(damping: float) LinearOperator[source]

Constructs the regularized normal operator for the chosen formalism.

For ‘model_space’, this returns: A* R^{-1} A + damping * I For ‘data_space’, this returns: A A* + damping * R

Parameters:

damping – The non-negative Tikhonov regularization parameter.

Returns:

A LinearOperator representing the left-hand side of the normal equations.

Raises:

ValueError – If the damping parameter is negative.

normal_residual_callback(damping: float, data: Vector, /, *, message: str = 'Iteration: {iter} | Normal Residual: {res:.3e}', print_progress: bool = True)[source]

Generates a ResidualTrackingCallback pre-configured to track the convergence of the least-squares normal equations for the given data vector.

normal_rhs(data: Vector) Vector[source]

Computes the right-hand side vector for the normal equations.

Prior to construction, the data is shifted by the expected value of the data error measure (i.e., v - z_bar), if applicable.

For ‘model_space’, this returns: A* R^{-1} (v - z_bar) For ‘data_space’, this returns: (v - z_bar)

Parameters:

data – The observed data vector in the data space.

Returns:

The right-hand side Vector for the chosen linear system.

surrogate_inversion(*, alternate_forward_operator: LinearOperator | None = None, alternate_data_error_measure: GaussianMeasure | None = None) LinearLeastSquaresInversion[source]

Constructs a surrogate least-squares inversion problem using simplified physics or data errors.

surrogate_woodbury_data_preconditioner(damping: float, solver: LinearSolver, /, *, alternate_forward_operator: LinearOperator | None = None, alternate_data_error_measure: GaussianMeasure | None = None, noise_solver: LinearSolver | None = None) LinearOperator[source]

Builds a data-space preconditioner by applying the Woodbury matrix identity to a simplified surrogate inverse problem.

Note

Ensure that any alternate measures provided have well-defined inverse covariances, or use .with_regularized_inverse() on them before passing them to this method.

Parameters:
  • damping – The Tikhonov regularization parameter.

  • solver – The LinearSolver used to invert the inner Woodbury operator.

  • alternate_forward_operator – An optional simplified forward operator.

  • alternate_data_error_measure – An optional simplified data error measure.

  • noise_solver – Optional solver for the noise covariance.

Returns:

A LinearOperator representing the Woodbury-approximated inverse.

surrogate_woodbury_model_preconditioner(damping: float, solver: LinearSolver, /, *, alternate_forward_operator: LinearOperator | None = None, alternate_data_error_measure: GaussianMeasure | None = None) LinearOperator[source]

Builds a model-space preconditioner by applying the Woodbury matrix identity to a simplified surrogate inverse problem.

with_formalism(formalism: Literal['model_space', 'data_space']) LinearLeastSquaresInversion[source]

Returns a new instance of the inversion using the specified formalism.

Parameters:

formalism – The algebraic space in which the normal equations should be assembled and solved. Must be ‘model_space’ or ‘data_space’.

Returns:

A new LinearLeastSquaresInversion instance with the updated formalism.

woodbury_data_preconditioner(damping: float, solver: LinearSolver, /, *, noise_solver: LinearSolver | None = None) LinearOperator[source]

Constructs a data-space preconditioner using the Woodbury matrix identity.

Data Space Normal Operator: N_d = A A* + damping * R Woodbury Identity: N_d^-1 = (1/damping) * [R^-1 - R^-1 A (A* R^-1 A + damping * I)^-1 A* R^-1]

Note

This method assumes the noise measure (R) either already has a well-defined inverse covariance, or is well-conditioned enough to be inverted by the provided solver. If it is an unbounded operator on a function space, use R.with_regularized_inverse() before passing it to the inversion.

Parameters:
  • damping – The Tikhonov regularization parameter.

  • solver – The LinearSolver used to invert the inner Woodbury operator (N_m).

  • noise_solver – An optional solver used to explicitly invert the data error covariance (R) if its inverse is not already set. Defaults to solver if not provided.

Returns:

A LinearOperator representing the Woodbury-approximated inverse.

woodbury_model_preconditioner(damping: float, solver: LinearSolver, /) LinearOperator[source]

Constructs a model-space preconditioner using the Woodbury matrix identity.

Model Space Normal Operator: N_m = A* R^-1 A + damping * I Woodbury Identity: N_m^-1 = (1/damping) * [I - A* (damping * R + A A*)^-1 A]

Note

This formulation does not require evaluating the explicit inverse of the noise covariance (R).

Parameters:
  • damping – The Tikhonov regularization parameter.

  • solver – The LinearSolver used to invert the inner Woodbury operator (N_d).

Returns:

A LinearOperator representing the Woodbury-approximated inverse.

class pygeoinf.LinearMinimumNormInversion(forward_problem: LinearForwardProblem, /, *, formalism: Literal['model_space', 'data_space'] = 'data_space')[source]

Bases: LinearInversion

Finds a regularized solution using the discrepancy principle.

This method finds the model u with the smallest norm that fits the data to a statistically acceptable degree (determined by a target chi-squared value and significance level).

This class supports two formalisms for constructing the linear systems: 1. ‘model_space’: Solves the normal equations of size (N x N). 2. ‘data_space’: Solves the dual formulation of size (M x M).

minimum_norm_operator(solver: LinearSolver, /, *, preconditioner: LinearOperator | LinearSolver | None = None, significance_level: float = 0.95, minimum_damping: float = 0.0, maxiter: int = 100, rtol: float = 1e-06, atol: float = 0.0) NonLinearOperator | LinearOperator[source]

Maps data to the minimum-norm solution matching target chi-squared.

The returned NonLinearOperator includes the exact analytical Fréchet derivative of the discrepancy search, complete with its adjoint mapping.

with_formalism(formalism: Literal['model_space', 'data_space']) LinearMinimumNormInversion[source]

Returns a new instance of the inversion using the specified formalism.

Parameters:

formalism – The algebraic space in which the normal equations should be assembled and solved. Must be ‘model_space’ or ‘data_space’.

Returns:

A new LinearMinimumNormInversion instance with the updated formalism.

class pygeoinf.LinearOperator(domain: HilbertSpace, codomain: HilbertSpace, mapping: Callable[[Any], Any], /, *, dual_mapping: Callable[[Any], Any] | None = None, adjoint_mapping: Callable[[Any], Any] | None = None, dual_base: LinearOperator | None = None, adjoint_base: LinearOperator | None = None)[source]

Bases: NonLinearOperator, LinearOperatorAxiomChecks

A linear operator between two Hilbert spaces.

This class represents a linear map L(x) = Ax and provides rich functionality for linear algebraic operations. It specializes NonLinearOperator, with the derivative mapping taking the required form (i.e., the derivative is just the operator itself).

Key features include operator algebra (@, +, *), automatic derivation of adjoint (.adjoint) and dual (.dual) operators, and multiple matrix representations (.matrix()) for use with numerical solvers.

property adjoint: LinearOperator

The adjoint of the operator.

property dual: LinearOperator

The dual of the operator.

extract_diagonal(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) ndarray[source]

Computes the main diagonal of the operator’s matrix representation.

This method is highly parallelizable and memory-efficient, as it avoids forming the full dense matrix.

Parameters:
  • galerkin – If True, computes the diagonal of the Galerkin matrix.

  • parallel – If True, computes the entries in parallel.

  • n_jobs – Number of parallel jobs to use.

Returns:

A NumPy array containing the diagonal entries.

extract_diagonals(offsets: List[int], /, *, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) Tuple[ndarray, List[int]][source]

Computes specified diagonals of the operator’s matrix representation.

This is a memory-efficient and parallelizable method that computes the matrix one column at a time.

Parameters:
  • offsets – A list of diagonal offsets to extract (e.g., [0] for the main diagonal, [-1, 0, 1] for a tridiagonal matrix).

  • galerkin – If True, computes the diagonals of the Galerkin matrix.

  • parallel – If True, computes columns in parallel.

  • n_jobs – Number of parallel jobs to use.

Returns:

  • A NumPy array where each row is a diagonal.

  • The list of offsets.

This format is compatible with scipy.sparse.spdiags.

Return type:

A tuple containing

static from_formal_adjoint(domain: HilbertSpace, codomain: HilbertSpace, operator: LinearOperator) LinearOperator[source]

Constructs an operator on weighted spaces from one on the underlying spaces.

This is a key method for working with MassWeightedHilbertSpace. It takes an operator A that is defined on the simple, unweighted underlying spaces and “lifts” it to be a proper operator on the mass-weighted spaces. It correctly defines the new operator’s adjoint with respect to the weighted inner products.

This method automatically handles cases where the domain and/or codomain are a HilbertSpaceDirectSum, recursively building the necessary block-structured mass operators.

Parameters:
  • domain – The (potentially) mass-weighted domain of the new operator.

  • codomain – The (potentially) mass-weighted codomain of the new operator.

  • operator – The original operator defined on the underlying, unweighted spaces.

Returns:

A new LinearOperator that acts between the mass-weighted spaces.

static from_formally_self_adjoint(domain: HilbertSpace, operator: LinearOperator) LinearOperator[source]

Constructs a self-adjoint operator on a weighted space.

This method takes an operator that is formally self-adjoint on an underlying (unweighted) space and promotes it to a truly self-adjoint operator on the MassWeightedHilbertSpace. It automatically handles HilbertSpaceDirectSum domains.

Parameters:
  • domain (HilbertSpace) – The domain of the operator, which can be a MassWeightedHilbertSpace or a HilbertSpaceDirectSum.

  • operator (LinearOperator) – The operator to be converted.

static from_linear_form(form: LinearForm) LinearOperator[source]

Creates a rank-1 LinearOperator from a single LinearForm.

The resulting operator maps from the form’s domain to a 1-dimensional Euclidean space.

The forward mapping evaluates the form: A(x) = [form(x)]. The dual mapping scales the form: A’(y’) = y’_0 * form. The adjoint mapping is handled automatically by the base class.

Parameters:

form – A LinearForm representing a continuous linear functional.

Returns:

A LinearOperator mapping from ‘form.domain’ to EuclideanSpace(1).

static from_linear_forms(forms: List[LinearForm]) LinearOperator[source]

Creates an operator from a list of linear forms.

The resulting operator maps from the forms’ domain to an N-dimensional Euclidean space, where N is the number of forms.

static from_matrix(domain: HilbertSpace, codomain: HilbertSpace, matrix: np.ndarray | sp.sparray | ScipyLinOp, /, *, galerkin: bool = False) MatrixLinearOperator[source]

Creates the most appropriate LinearOperator from a matrix representation.

This factory method acts as a dispatcher, inspecting the type of the input matrix and returning the most specialized and optimized operator subclass (e.g., Dense, Sparse, or DiagonalSparse). It also handles matrix-free scipy.sparse.linalg.LinearOperator objects.

Parameters:
  • domain – The operator’s domain space.

  • codomain – The operator’s codomain space.

  • matrix – The matrix representation (NumPy ndarray, SciPy sparray, or SciPy LinearOperator).

  • galerkin – If True, the matrix is interpreted in Galerkin form.

Returns:

An instance of the most appropriate MatrixLinearOperator subclass.

static from_tensor_product(domain: HilbertSpace, codomain: HilbertSpace, vector_pairs: List[Tuple[Any, Any]], /, *, weights: List[float] | None = None) LinearOperator[source]

Creates an operator from a weighted sum of tensor products.

The operator represents A(x) = sum_i( w_i * <x, v_i> * u_i ), where vector_pairs are (u_i, v_i).

static from_vector(domain: HilbertSpace, vector: Vector) LinearOperator[source]

Creates a rank-1 LinearOperator from a single domain vector.

The resulting operator maps from the given domain to a 1-dimensional Euclidean space.

The forward mapping evaluates the inner product: A(x) = [<vector, x>]. The adjoint mapping scales the vector: A*(y) = y[0] * vector.

Parameters:
  • domain – The Hilbert space the vector belongs to.

  • vector – A single vector in the domain space.

Returns:

A LinearOperator mapping from ‘domain’ to EuclideanSpace(1).

static from_vectors(domain: HilbertSpace, vectors: List[Vector]) LinearOperator[source]

Creates a LinearOperator from a list of domain vectors.

The resulting operator maps from the given domain to a Euclidean space of dimension n (where n is the number of vectors).

The forward mapping is given by A(x)_i = <vectors[i], x>. The adjoint mapping is given by A*(y) = sum(y_i * vectors[i]).

Parameters:
  • domain – The Hilbert space the vectors belong to.

  • vectors – A list of vectors in the domain space.

Returns:

A LinearOperator mapping from ‘domain’ to EuclideanSpace(len(vectors)).

Raises:

ValueError – If the list of vectors is empty.

property linear: bool

True, as this is a LinearOperator.

matrix(*, dense: bool = False, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) LinearOperator | ndarray[source]

Returns a matrix representation of the operator.

This provides a concrete matrix that represents the operator’s action on the underlying component vectors.

Parameters:
  • dense – If True, returns a dense numpy.ndarray. If False (default), returns a memory-efficient, matrix-free scipy.sparse.linalg.LinearOperator.

  • galerkin – If True, the returned matrix is the Galerkin representation, whose rmatvec corresponds to the adjoint operator. If False (default), the rmatvec corresponds to the dual operator. The Galerkin form is essential for algorithms that rely on symmetry/self-adjointness.

  • parallel – If True and dense=True, computes the matrix columns in parallel.

  • n_jobs – Number of parallel jobs to use. -1 uses all available cores.

Returns:

The matrix representation, either dense or matrix-free.

static self_adjoint(domain: HilbertSpace, mapping: Callable[[Any], Any]) LinearOperator[source]

Creates a self-adjoint operator.

static self_adjoint_from_matrix(domain: HilbertSpace, matrix: np.ndarray | sp.sparray | ScipyLinOp) MatrixLinearOperator[source]

Creates the most appropriate self-adjoint LinearOperator from a matrix.

This factory acts as a dispatcher, returning the most specialized subclass for the given matrix type (e.g., Dense, Sparse).

It ALWAYS assumes the provided matrix is the Galerkin representation of the operator. The user is responsible for ensuring the input matrix is symmetric (or self-adjoint for ScipyLinOp).

Parameters:
  • domain – The operator’s domain and codomain space.

  • matrix – The symmetric matrix representation.

Returns:

An instance of the most appropriate MatrixLinearOperator subclass.

static self_adjoint_from_tensor_product(domain: HilbertSpace, vectors: List[Any], /, *, weights: List[float] | None = None) LinearOperator[source]

Creates a self-adjoint operator from a tensor product sum.

static self_dual(domain: HilbertSpace, mapping: Callable[[Any], Any]) LinearOperator[source]

Creates a self-dual operator.

with_dense_matrix(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) DenseMatrixLinearOperator[source]

Returns a new operator equivalent to the existing one, but with its matrix representation computed and stored internally in dense form.

Parameters:
  • galerkin – If True, the Galerkin representation is used. Default is False.

  • parallel – If True, computes the dense matrix in parallel.

  • n_jobs – Number of CPU cores to use. -1 means all available.

Returns:

A DenseMatrixLinearOperator instance.

class pygeoinf.LinearSolver[source]

Bases: ABC

An abstract base class for linear solvers.

class pygeoinf.LinearSubspace(projector: OrthogonalProjector)[source]

Bases: AffineSubspace

Represents a linear subspace (an affine subspace passing through the origin).

property complement: LinearSubspace

Returns the orthogonal complement of this subspace as a new LinearSubspace.

classmethod from_basis(domain: HilbertSpace, basis_vectors: List[Vector], orthonormalize: bool = True, solver: LinearSolver | None = None, preconditioner: LinearOperator | None = None) LinearSubspace[source]

Constructs a linear subspace from a set of basis vectors.

Parameters:
  • domain – The Hilbert space.

  • basis_vectors – List of vectors spanning the subspace.

  • orthonormalize – Whether to orthonormalize the basis.

  • solver – Optional solver for implicit constraints (see AffineSubspace.from_tangent_basis).

  • preconditioner – Optional preconditioner.

classmethod from_complement_basis(domain: HilbertSpace, basis_vectors: List[Vector], orthonormalize: bool = True) LinearSubspace[source]

Constructs a linear subspace defined by orthogonality to a complement basis. S = {u | <u, v_i> = 0}.

Parameters:
  • domain – The Hilbert space.

  • basis_vectors – Basis vectors for the complement.

  • orthonormalize – Whether to orthonormalize the complement basis.

classmethod from_kernel(operator: LinearOperator, solver: LinearSolver | None = None, preconditioner: LinearOperator | None = None) LinearSubspace[source]

Constructs the subspace corresponding to the kernel (null space) of an operator. K = {u | A(u) = 0}.

Parameters:
  • operator – The operator A.

  • solver – Solver used for the Gram matrix (A A*).

  • preconditioner – Optional preconditioner.

class pygeoinf.LowRankCholesky(l_op: LinearOperator)[source]

Bases: LinearOperator

A LinearOperator representing the Cholesky-like factorization: A ≈ L @ L*.

This class provides a memory-efficient low-rank Cholesky decomposition of a positive semi-definite operator, highly useful for drawing samples from Gaussian measures.

classmethod from_randomized(operator: LinearOperator, size_estimate: int, *, measure: GaussianMeasure | None = None, galerkin: bool = True, method: str = 'variable', max_rank: int | None = None, power: int = 2, rtol: float = 0.0001, block_size: int = 10, parallel: bool = False, n_jobs: int = -1) LowRankCholesky[source]

Computes a robust approximate Cholesky factorization via randomized range finding.

Attempts a direct dense Cholesky factorization on the projected core matrix. If it fails (due to numerical precision issues), it safely falls back to an Eigendecomposition-based square root.

Parameters:
  • operator (LinearOperator) – Positive semi-definite operator to factorize.

  • size_estimate (int) – Target rank or initial block size.

  • measure (GaussianMeasure, optional) – Prior measure for drawing test vectors.

  • galerkin (bool) – Default True. Computes Galerkin representation on fallback.

  • method (str) – {‘variable’, ‘fixed’}.

  • max_rank (int, optional) – Upper limit on rank for ‘variable’ method.

  • power (int) – Number of power iterations.

  • rtol (float) – Relative tolerance for ‘variable’ method.

  • block_size (int) – Samples per iteration.

  • parallel (bool) – Parallelize the sampling/multiplication.

  • n_jobs (int) – CPU cores to utilize.

Returns:

An instantiated operator containing the L factor.

Return type:

LowRankCholesky

Raises:

ValueError – If the operator is not an automorphism.

property l_factor: LinearOperator

The Cholesky factor (L).

Type:

LinearOperator

property rank: int

The rank of the approximation.

Type:

int

class pygeoinf.LowRankEig(u_op: LinearOperator, d_op: DiagonalSparseMatrixLinearOperator)[source]

Bases: LinearOperator

A LinearOperator representing the eigendecomposition: A ≈ U @ D @ U*.

This class encapsulates the components of an Eigendecomposition for a self-adjoint operator, allowing it to act as a LinearOperator while exposing the eigenvectors and eigenvalues.

apply_function(func: Callable[[ndarray], ndarray], *, regularization: float = 0.0) LowRankEig[source]

Applies a function to the spectrum of the operator. Returns a new LowRankEig representing f(A).

property d_factor: DiagonalSparseMatrixLinearOperator

The diagonal matrix of eigenvalues (D).

Type:

DiagonalSparseMatrixLinearOperator

property eigenvalues: ndarray

A 1D array of the computed eigenvalues.

Type:

np.ndarray

classmethod from_randomized(operator: LinearOperator, size_estimate: int, *, measure: GaussianMeasure | None = None, galerkin: bool = True, method: str = 'variable', max_rank: int | None = None, power: int = 2, rtol: float = 0.0001, block_size: int = 10, parallel: bool = False, n_jobs: int = -1) LowRankEig[source]

Computes the Eigendecomposition using a unified randomized range finder.

Parameters:
  • operator (LinearOperator) – The self-adjoint operator to approximate.

  • size_estimate (int) – Target rank or initial block size.

  • measure (GaussianMeasure, optional) – Prior measure for drawing test vectors.

  • galerkin (bool) – Default True for Eig. Computes Galerkin representation on fallback.

  • method (str) – {‘variable’, ‘fixed’}.

  • max_rank (int, optional) – Upper limit on rank for ‘variable’ method.

  • power (int) – Number of power iterations.

  • rtol (float) – Relative tolerance for ‘variable’ method.

  • block_size (int) – Samples per iteration.

  • parallel (bool) – Parallelize the sampling/multiplication.

  • n_jobs (int) – CPU cores to utilize.

Returns:

An instantiated operator containing the U and D factors.

Return type:

LowRankEig

Raises:

ValueError – If the operator is not an automorphism (domain != codomain).

property rank: int

The rank of the approximation.

Type:

int

property u_factor: LinearOperator

The eigenvectors (U).

Type:

LinearOperator

class pygeoinf.LowRankSVD(u_op: LinearOperator, sigma_op: DiagonalSparseMatrixLinearOperator, v_op: LinearOperator)[source]

Bases: LinearOperator

A LinearOperator representing the low-rank SVD: A ≈ U @ Sigma @ V*.

This class encapsulates the components of a Singular Value Decomposition, allowing it to be used directly as a LinearOperator while providing access to the individual low-rank factors.

classmethod from_randomized(operator: LinearOperator, size_estimate: int, *, measure: GaussianMeasure | None = None, galerkin: bool = False, method: str = 'variable', max_rank: int | None = None, power: int = 2, rtol: float = 0.0001, block_size: int = 10, parallel: bool = False, n_jobs: int = -1) LowRankSVD[source]

Computes the SVD using a unified randomized range finder.

Parameters:
  • operator (LinearOperator) – The operator to approximate.

  • size_estimate (int) – For ‘fixed’ method, the exact target rank. For ‘variable’ method, this is the initial rank to sample.

  • measure (GaussianMeasure, optional) – A prior measure used to draw test vectors. If provided, respects the domain’s geometry. If None, falls back to a component-based SciPy LinearOperator representation.

  • galerkin (bool) – If True, computes the Galerkin representation when falling back to components.

  • method (str) – {‘variable’, ‘fixed’}. The rank-determination algorithm to use.

  • max_rank (int, optional) – Hard limit on the rank for the ‘variable’ method.

  • power (int) – Number of power iterations to improve accuracy.

  • rtol (float) – Relative tolerance for the ‘variable’ method.

  • block_size (int) – Number of new vectors to sample per iteration in ‘variable’ method.

  • parallel (bool) – Whether to parallelize the matrix/operator evaluations.

  • n_jobs (int) – Number of cores to use if parallel=True (-1 for all).

Returns:

An instantiated operator containing the U, Sigma, and V factors.

Return type:

LowRankSVD

property rank: int

The rank of the approximation.

Type:

int

property sigma_factor: DiagonalSparseMatrixLinearOperator

The diagonal matrix of singular values (Sigma).

Type:

DiagonalSparseMatrixLinearOperator

property singular_values: ndarray

A 1D array of the computed singular values.

Type:

np.ndarray

property u_factor: LinearOperator

The left singular vectors (U).

Type:

LinearOperator

property v_factor: LinearOperator

The right singular vectors (V).

Type:

LinearOperator

class pygeoinf.MassWeightedHilbertModule(underlying_space: HilbertModule, mass_operator: LinearOperator, inverse_mass_operator: LinearOperator)[source]

Bases: MassWeightedHilbertSpace, HilbertModule

A mass-weighted Hilbert space that also supports vector multiplication.

This class inherits the mass-weighted inner product structure and mixes in the HilbertModule interface, delegating the multiplication operation to the underlying space.

vector_multiply(x1: Vector, x2: Vector) Vector[source]

Computes vector multiplication by delegating to the underlying space.

Note: This assumes the underlying space provided during initialization is itself an instance of HilbertModule.

vector_sqrt(x: Vector) Vector[source]

Computes vector multiplication by delegating to the underlying space.

Note: This assumes the underlying space provided during initialization is itself an instance of HilbertModule.

class pygeoinf.MassWeightedHilbertSpace(underlying_space: HilbertSpace, mass_operator: LinearOperator, inverse_mass_operator: LinearOperator)[source]

Bases: HilbertSpace

A Hilbert space with an inner product weighted by a mass operator.

This class wraps an existing HilbertSpace (let’s call it X) and defines a new inner product for a space (Y) as: (u, v)_Y = (M @ u, v)_X, where M is a self-adjoint, positive-definite mass operator defined on X.

This is a common construction in numerical methods like the Finite Element Method, where the basis functions are not orthonormal.

add(x: Vector, y: Vector) Vector[source]

Computes the sum of two vectors. Defaults to x + y.

ax(a: float, x: Vector) None[source]

Performs in-place scaling x := a*x. Defaults to x *= a.

axpy(a: float, x: Vector, y: Vector) None[source]

Performs in-place operation y := y + a*x. Defaults to y += a*x.

copy(x: Vector) Vector[source]

Returns a deep copy of a vector. Defaults to x.copy().

property dim: int

The dimension of the space.

from_components(c: ndarray) Vector[source]

Delegates vector creation to the underlying space.

from_dual(xp: LinearForm) Vector[source]

Computes the inverse dual mapping R_Y^{-1}(xp) = M^{-1} R_X^{-1}(xp).

inner_product(x1: Vector, x2: Vector) float[source]

Computes the inner product of two vectors.

Notes

Default implementation overrident for efficiency.

property inverse_mass_operator: LinearOperator

The inverse of the mass operator.

is_element(x: Any) bool[source]

Checks if an object is a valid element of the space.

property mass_operator: LinearOperator

The mass operator (M) defining the weighted inner product.

multiply(a: float, x: Vector) Vector[source]

Computes scalar multiplication. Defaults to a * x.

negative(x: Vector) Vector[source]

Computes the additive inverse of a vector. Defaults to -1 * x.

subtract(x: Vector, y: Vector) Vector[source]

Computes the difference of two vectors. Defaults to x - y.

to_components(x: Vector) ndarray[source]

Delegates component mapping to the underlying space.

to_dual(x: Vector) LinearForm[source]

Computes the dual mapping R_Y(x) = R_X(M x).

property underlying_space: HilbertSpace

The underlying Hilbert space (X) without mass weighting.

property zero: Vector

The zero vector (additive identity) of the space.

class pygeoinf.MatrixLinearOperator(domain: HilbertSpace, codomain: HilbertSpace, matrix: np.ndarray | ScipyLinOp, /, *, galerkin=False)[source]

Bases: LinearOperator

A sub-class of LinearOperator for which the operator’s action is defined internally through its matrix representation.

This matrix can be either a dense numpy matrix or a scipy LinearOperator.

extract_diagonal(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) ndarray[source]

Overload for efficiency.

extract_diagonals(offsets: List[int], /, *, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) Tuple[ndarray, List[int]][source]

Overrides the base method for efficiency by extracting diagonals directly from the stored dense matrix when possible.

property is_dense: bool

Returns True if the matrix representation is stored internally in dense form.

property is_galerkin: bool

Returns True if the matrix representation is stored in Galerkin form.

class pygeoinf.MinResSolver(*, preconditioning_method: LinearSolver = None, rtol: float = 1e-05, atol: float = 1e-08, maxiter: int | None = None)[source]

Bases: IterativeLinearSolver

A matrix-free implementation of the MINRES algorithm.

Suitable for symmetric, possibly indefinite or singular linear systems. It minimizes the norm of the residual ||r|| in each step using the Hilbert space’s native inner product.

solve_linear_system(operator: LinearOperator, preconditioner: LinearOperator | None, y: Vector, x0: Vector | None) Vector[source]

Solves the linear system Ax = y for x.

Parameters:
  • operator (LinearOperator) – The operator A of the linear system.

  • preconditioner (LinearOperator, optional) – The preconditioner.

  • y (Vector) – The right-hand side vector.

  • x0 (Vector, optional) – The initial guess for the solution.

Returns:

The solution vector x.

Return type:

Vector

class pygeoinf.MinkowskiSumSupportFunction(left: SupportFunction, right: SupportFunction)[source]

Bases: SupportFunction

Support function of the Minkowski sum $C oplus D$.

For two convex sets $C, D subseteq H$ with support functions $h_C$ and $h_D$ on the same Hilbert space $H$, the support function of their Minkowski sum $C oplus D = {c + d : c in C,, d in D}$ is

\[h_{C \oplus D}(q) = h_C(q) + h_D(q), \quad q \in H.\]
Parameters:
  • left – Support function $h_C$.

  • right – Support function $h_D$. right.primal_domain must equal left.primal_domain.

Raises:

ValueError – If left.primal_domain and right.primal_domain differ.

Note

Phase 3: support_point() conservatively returns the sum of support points only when both operands have support points available. If either operand has no support point, returns None (unavailable).

support_point(q: Vector) 'Vector' | None[source]

Return the support point of the Minkowski sum $C oplus D$ at direction $q$.

If both the left and right operands have support points $x_L^*(q)$ and $x_R^*(q)$ available, return their sum $x_L^*(q) + x_R^*(q)$ (a support point of the sum set). Otherwise return None (conservative: neither is available or computing the sum is unsafe).

Parameters:

q – A vector in the shared primal space $H$.

Returns:

The sum of support points $x_L^*(q) + x_R^*(q)$ if both are available, or None.

class pygeoinf.NonLinearForm(domain: HilbertSpace, mapping: Callable[[Vector], float], /, *, gradient: Callable[[Vector], Vector] | None = None, subgradient: Callable[[Vector], Vector] | None = None, hessian: Callable[[Vector], LinearOperator] | None = None)[source]

Bases: object

Represents a general non-linear functional that maps vectors to scalars.

This class serves as the foundation for all forms. It defines the basic callable interface form(x) and overloads arithmetic operators (+, -, *) to create new forms. It also provides an optional framework for specifying a form’s gradient, Hessian, and subgradient.

For smooth functions, use gradient and hessian. For non-smooth convex functions, use subgradient.

derivative(x: Vector) LinearForm[source]

Computes the derivative of the form at a given point.

Parameters:

x – The vector at which to evaluate the derivative.

Returns:

The derivative of the form as a LinearForm.

Raises:

NotImplementedError – If a gradient function was not provided during initialization.

property domain: HilbertSpace

The Hilbert space on which the form is defined.

gradient(x: Any) Vector[source]

Computes the gradient of the form at a given point.

Parameters:

x – The vector at which to evaluate the gradient.

Returns:

The gradient of the form as a vector in the domain space.

Raises:

NotImplementedError – If a gradient function was not provided during initialization.

property has_gradient: bool

True if the form has a gradient.

property has_hessian: bool

True if the form has a Hessian.

property has_subgradient: bool

True if the form has a subgradient.

hessian(x: Any) LinearOperator[source]

Computes the Hessian of the form at a given point.

Parameters:

x – The vector at which to evaluate the Hessian.

Returns:

The Hessian of the form as a LinearOperator mapping the domain to itself.

Raises:

NotImplementedError – If a Hessian function was not provided during initialization.

subgradient(x: Any) Vector[source]

Computes a subgradient of the form at a given point.

For convex functions, a subgradient g ∈ ∂f(x) satisfies:

f(y) ≥ f(x) + ⟨g, y - x⟩ for all y

At points where the function is differentiable, the subgradient equals the gradient. At non-smooth points, there may be multiple subgradients; this method returns one of them.

Parameters:

x – The vector at which to evaluate the subgradient.

Returns:

A subgradient of the form as a vector in the domain space.

Raises:

NotImplementedError – If a subgradient function was not provided during initialization.

class pygeoinf.NonLinearOperator(domain: HilbertSpace, codomain: HilbertSpace, mapping: Callable[[Vector], Any], /, *, derivative: Callable[[Vector], LinearOperator] = None)[source]

Bases: NonLinearOperatorAxiomChecks

Represents a general non-linear operator that maps vectors to vectors.

This class provides a functional representation for an operator F(x), and includes an interface for its Fréchet derivative, F’(x), which is the linear operator that best approximates F at a given point x. It serves as the base class for the more specialized LinearOperator.

property codomain: HilbertSpace

The codomain of the operator.

derivative(x: Vector) LinearOperator[source]

Computes the Fréchet derivative of the operator at a given point.

The Fréchet derivative is the linear operator that best approximates the non-linear operator in the neighborhood of the point x.

Parameters:

x – The point at which to compute the derivative.

Returns:

The derivative as a LinearOperator.

Raises:

NotImplementedError – If a derivative function was not provided.

property domain: HilbertSpace

The domain of the operator.

property has_derivative: bool

Returns true if the operators derivative is implemented.

property is_automorphism: bool

True if the operator maps a space into itself.

property is_square: bool

True if the operator’s domain and codomain have the same dimension.

class pygeoinf.NormalisedEllipsoid(domain: HilbertSpace, center: Vector, operator: LinearOperator, open_set: bool = False)[source]

Bases: Ellipsoid

Represents a normalised ellipsoid with radius 1: E = {x | <A(x-c), x-c> <= 1}.

class pygeoinf.OrthogonalProjector(domain: HilbertSpace, mapping: Callable[[Any], Any], complement_projector: LinearOperator | None = None)[source]

Bases: LinearOperator

Internal engine for subspace projections. Represents an orthogonal projection operator P = P* = P^2.

property complement: LinearOperator

Returns the projector onto the orthogonal complement (I - P).

If a complement projector was not provided at initialization, one is constructed automatically as the difference between the identity and self.

classmethod from_basis(domain: HilbertSpace, basis_vectors: List[Vector], orthonormalize: bool = True) OrthogonalProjector[source]

Constructs a projector P onto the span of the provided basis vectors.

Parameters:
  • domain – The Hilbert space.

  • basis_vectors – A list of vectors spanning the subspace.

  • orthonormalize – If True, performs Gram-Schmidt orthonormalization on the basis vectors before constructing the projector. If False, assumes the basis is already orthonormal.

Returns:

An OrthogonalProjector instance.

class pygeoinf.PointSupportFunction(primal_domain: HilbertSpace, point: Vector)[source]

Bases: SupportFunction

Support function of the singleton set ${p}$.

For a fixed point $p in H$, the support function of the singleton set ${p}$ is

\[h_{\{p\}}(q) = \langle q, p \rangle, \quad q \in H.\]

The support point is always $p$ (the unique element of the set), so subgradient() is available for all query directions.

Parameters:
  • primal_domain – The Hilbert space $H$ containing the point $p$.

  • point – The fixed point $p$.

Example:

p = np.array([1.0, 2.0])
h = PointSupportFunction(space, p)
h(np.array([3.0, -1.0]))   # returns 3*1 + (-1)*2 = 1.0
support_point(q: Vector) Vector[source]

Return $p$ — the unique maximiser for any query direction.

class pygeoinf.PrimalKKTSolver(B: SupportFunction, V: SupportFunction, G: LinearOperator, d_tilde: Vector, /, *, fsolve_tol: float = 1e-10, fsolve_maxfev: int = 200)[source]

Bases: object

Primal KKT solver via Woodbury identity in abstract Hilbert spaces.

The solver operates on abstract Hilbert-space vectors throughout: the model space $H$ is never discretised. Only the data space $D$ (which is explicitly finite-dimensional) is discretised — and only inside _woodbury_solve(), which builds the $M times M$ Woodbury system.

The Woodbury reduction:

\[u^*(\lambda,\mu) = \frac{1}{\lambda} A_B^{-1} r_H - \frac{1}{\lambda} A_B^{-1} G^* K^{-1} G A_B^{-1} r_H\]

where

\[r_H = c + \lambda A_B u_0 + \mu G^* A_V \tilde{d}, \qquad K = \tfrac{1}{\mu} A_V^{-1} + \tfrac{1}{\lambda} G A_B^{-1} G^*\]

The $M times M$ matrix $P = G A_B^{-1} G^*$ is precomputed once via .matrix(dense=True) which probes the data space only. No model-space to_components or from_components is ever called.

Ball/ball simplification ($A_B = I_H$, $A_V = I_D$):

\[r_H = c + \lambda u_0 + \mu G^* \tilde{d}, \qquad P = G G^*, \qquad K = \tfrac{1}{\mu} I_D + \tfrac{1}{\lambda} G G^*\]

so the only M×M solve is $K z = G w$ and no model-space matrix is ever formed.

Supports BallSupportFunction and EllipsoidSupportFunction for both $B$ and $V$ (four combinations).

Parameters:
  • B – Model prior support function.

  • V – Data error support function (ball or ellipsoid, centered at origin).

  • G – Forward operator $G : H to D$.

  • d_tilde – Observed data vector in $D$.

  • fsolve_tol – Tolerance for scipy.optimize.fsolve.

  • fsolve_maxfev – Maximum function evaluations for fsolve.

solve(c: Vector) KKTResult[source]

Solve for $u^*$ maximising $langle c, u rangle$ over the feasible set.

Uses a two-branch strategy:

  1. Compute the support point $u_{rm ball}$ of $B$ in direction $c$. If the data constraint is satisfied, return immediately.

  2. Otherwise both constraints are active; solve the $2 times 2$ root-finding problem in log-space via scipy.optimize.fsolve, warm-started from $(lambda_{rm prev}, mu_{rm prev})$.

The model space is never discretised during this method.

Parameters:

c – Linear objective in model space $H$.

Returns:

KKTResult with the optimal model vector $u^*$, KKT multipliers, convergence flag, and iteration count.

class pygeoinf.ProgressCallback(message: str = 'Iteration: ')[source]

Bases: object

A simple callback that prints the solver’s iteration count.

finalize() None[source]

Called at the end of a solve to clean up the console output.

reset() None[source]

Resets the state for a new solve.

class pygeoinf.ProximalBundleMethod(oracle: NonLinearForm, /, *, rho0: float = 1.0, rho_factor: float = 2.0, tolerance: float = 1e-06, max_iterations: int = 500, bundle_size: int = 100, store_iterates: bool = False, qp_solver: QPSolver | None = None)[source]

Bases: object

Proximal bundle method for minimising a non-smooth convex function.

Solves:

min_{lambda in D} f(lambda)

where f is a convex function accessible through a value + subgradient oracle (a NonLinearForm with subgradient).

At each iteration the master QP is:

min_{lambda, t} t + (rho / 2) ||lambda - lambda_hat||^2 subject to: f_j + <g_j, lambda - x_j> <= t for all j in bundle

where lambda_hat is the current stability centre and rho > 0 is the proximal weight.

A serious step is taken whenever the new oracle value f(lambda_+) < f(lambda_hat); otherwise a null step occurs and rho is increased to tighten the proximal term.

Parameters:
  • oracle – Non-smooth convex functional with subgradient oracle.

  • rho0 – Initial proximal weight rho > 0.

  • rho_factor – Multiplicative factor applied to rho on null steps (divide on serious steps).

  • tolerance – Convergence tolerance; terminates when the duality gap f_up - f_low <= tolerance.

  • max_iterations – Maximum number of oracle calls.

  • bundle_size – Maximum number of cuts retained in the bundle.

  • store_iterates – If True, all iterates are stored in BundleResult.iterates.

  • qp_solver – QP solver implementing QPSolver. Defaults to SciPyQPSolver if None.

Examples

>>> from pygeoinf.hilbert_space import EuclideanSpace
>>> from pygeoinf.nonlinear_forms import NonLinearForm
>>> import numpy as np
>>> domain = EuclideanSpace(1)
>>> f = lambda x: float(x[0]**2 + 2*x[0])
>>> g = lambda x: np.array([2*x[0] + 2.0])
>>> oracle = NonLinearForm(domain, f, subgradient=g)
>>> solver = ProximalBundleMethod(oracle, tolerance=1e-5)
>>> result = solver.solve(domain.from_components(np.array([2.0])))
>>> np.testing.assert_allclose(domain.to_components(result.x_best), [-1.0], atol=1e-3)
solve(x0: Vector) BundleResult[source]

Run the proximal bundle method starting from x0.

Parameters:

x0 – Initial point in the domain of the oracle.

Returns:

A BundleResult summarising the optimisation run.

class pygeoinf.ResidualTrackingCallback(operator: LinearOperator, y: Vector, print_progress: bool = True, message: str = 'Iteration: {iter} | Residual: {res:.3e}')[source]

Bases: ProgressCallback

A callback that computes and tracks the exact residual norm ||y - A(x)||.

Warning: This evaluates the forward operator once per iteration. For very large problems, this may introduce computational overhead.

class pygeoinf.RowLinearOperator(operators: List[LinearOperator])[source]

Bases: LinearOperator, BlockStructure

An operator that maps from a direct sum space to a single space.

It can be visualized as a row vector of operators, [A_1, A_2, …]. It takes a list of input vectors [x_1, x_2, …] and produces a single output vector y = A_1(x_1) + A_2(x_2) + …. The adjoint of a ColumnLinearOperator is a RowLinearOperator.

block(i: int, j: int) LinearOperator[source]

Returns the operator in the (0, j)-th sub-block.

class pygeoinf.ScaledSupportFunction(base: SupportFunction, alpha: float)[source]

Bases: SupportFunction

Support function of a nonnegatively scaled convex set $alpha C$.

For a convex set $C subseteq H$ with support function $h_C$ and a scalar $alpha geq 0$, the support function of the scaled set is

\[h_{\alpha C}(q) = \alpha\, h_C(q), \quad q \in H.\]

When $alpha = 0$ the set $0 cdot C = {0}$ and $h(q) = 0$ for all $q$.

Parameters:
  • base – The support function $h_C$.

  • alpha – A nonnegative scalar.

Raises:

ValueError – If alpha < 0.

Note

Phase 3: support_point() propagates support points by scaling them. For $alpha > 0$: returns $alpha cdot x_C^*(q)$ if available; For $alpha = 0$: returns the zero vector (support point of ${0}$); If base has no support_point, returns None.

support_point(q: Vector) 'Vector' | None[source]

Return the support point of the scaled set $alpha C$ at direction $q$.

For $alpha > 0$: Returns $alpha cdot x_C^*(q)$ if the base has a support point. For $alpha = 0$: Returns the zero vector (the unique support point of the singleton ${0}$). If the base has no support point and $alpha > 0$, returns None.

Parameters:

q – A vector in the primal space $H$.

Returns:

The scaled support point $alpha cdot x_C^*(q)$, the zero vector for $alpha=0$, or None.

class pygeoinf.ScipyIterativeSolver(method: str, /, *, preconditioning_method: LinearSolver = None, galerkin: bool = False, **kwargs)[source]

Bases: IterativeLinearSolver

A general iterative solver that wraps SciPy’s iterative algorithms.

This class provides a unified interface to SciPy’s sparse iterative solvers like cg, gmres, bicgstab, etc. The specific algorithm is chosen during instantiation, and keyword arguments are passed directly to the chosen SciPy function.

solve_linear_system(operator: LinearOperator, preconditioner: LinearOperator | None, y: Vector, x0: Vector | None) Vector[source]

Solves the linear system Ax = y for x.

Parameters:
  • operator (LinearOperator) – The operator A of the linear system.

  • preconditioner (LinearOperator, optional) – The preconditioner.

  • y (Vector) – The right-hand side vector.

  • x0 (Vector, optional) – The initial guess for the solution.

Returns:

The solution vector x.

Return type:

Vector

class pygeoinf.ScipyUnconstrainedOptimiser(method: str, /, **kwargs: Any)[source]

Bases: object

A wrapper for scipy.optimize.minimize that adapts a NonLinearForm.

Note on derivative-free methods: Internal testing has shown that the ‘Nelder-Mead’ solver can be unreliable for some problems, failing to converge to the correct minimum while still reporting success. The ‘Powell’ method appears to be more robust. Users should exercise caution and verify results when using derivative-free methods.

minimize(form: NonLinearForm, x0: Vector) Vector[source]

Finds the minimum of a NonLinearForm starting from an initial guess.

Parameters:
  • form (NonLinearForm) – The non-linear functional to minimize.

  • x0 (Vector) – The initial guess in the Hilbert space.

Returns:

The vector that minimizes the form.

Return type:

Vector

class pygeoinf.SolutionTrackingCallback(domain: HilbertSpace, message: str = 'Iteration: ', print_progress: bool = True)[source]

Bases: ProgressCallback

A callback that tracks the solution vector at each iteration.

Useful for visualizing the convergence path of the solver or calculating diagnostics post-hoc without slowing down the inversion.

class pygeoinf.SparseMatrixLinearOperator(domain: HilbertSpace, codomain: HilbertSpace, matrix: sp.sparray, /, *, galerkin: bool = False)[source]

Bases: MatrixLinearOperator

A specialization for operators represented by a modern SciPy sparse array.

This class requires a scipy.sparse.sparray object (e.g., csr_array) and provides optimized methods that delegate to efficient SciPy routines.

Upon initialization, the internal array is converted to the CSR (Compressed Sparse Row) format to ensure consistently fast matrix-vector products and row-slicing operations.

extract_diagonal(*, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) ndarray[source]

Overrides the base method to efficiently extract the main diagonal.

extract_diagonals(offsets: List[int], /, *, galerkin: bool = False, parallel: bool = False, n_jobs: int = -1) Tuple[ndarray, List[int]][source]

Overrides the base method for efficiency by extracting diagonals directly from the stored sparse array.

property sparse_array: sparray

Provides public read-only access to the underlying SciPy sparse array.

class pygeoinf.SpectralPreconditioningMethod(*, damping: float | None = None, rank: int = 20, method: str = 'variable', max_rank: int | None = None, power: int = 2, rtol: float = 0.0001, block_size: int = 10, parallel: bool = False, n_jobs: int = -1)[source]

Bases: LinearSolver

A LinearSolver wrapper that generates a spectral (low-rank) preconditioner.

This preconditioner uses a randomized eigendecomposition to invert the dominant modes of the operator. The unresolved tail is regularized using a damping parameter.

class pygeoinf.Sphere(domain: HilbertSpace, center: Vector, radius: float)[source]

Bases: EllipsoidSurface

Represents a sphere in a Hilbert space: S = {x | ||x - c||^2 = r^2}. This is an EllipsoidSurface where A is the Identity operator.

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if ||x - c|| is approximately equal to r.

class pygeoinf.SubgradientDescent(oracle: NonLinearForm, /, *, step_size: float, max_iterations: int = 500, store_iterates: bool = False, stagnation_window: int | None = None)[source]

Bases: object

Basic subgradient descent for minimising non-smooth convex functions.

Algorithm:

x_{k+1} = x_k - α * g_k

where g_k ∈ ∂f(x_k) is a subgradient (obtained via oracle.subgradient(x_k)).

This implementation uses CONSTANT step size α for all k. Convergence is not guaranteed with constant step size; use for learning/testing only.

Parameters:
  • oracle – A NonLinearForm with subgradient() method returning subgradients.

  • step_size – Constant step size α > 0.

  • max_iterations – Maximum number of iterations.

  • store_iterates – Whether to store full history (memory intensive).

  • stagnation_window – Optional number of iterations without improvement to declare convergence.

property domain: HilbertSpace
property oracle: NonLinearForm
solve(x0: Vector) SubgradientResult[source]

Run subgradient descent from initial point x0.

class pygeoinf.SublevelSet(form: NonLinearForm, level: float, open_set: bool = False)[source]

Bases: Subset

Represents a sublevel set defined by a functional: S = {x | f(x) <= c}.

This class serves as a base for sets defined by inequalities. Unlike ConvexSubset, it does not assume the defining functional is convex.

property boundary: Subset

Returns the boundary of the sublevel set. The boundary is typically the LevelSet {x | f(x) = c}.

property form: NonLinearForm

The defining functional f(x).

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if f(x) <= c (or < c). Tolerance is scaled by max(1.0, |c|).

property is_open: bool

True if the set is defined by strict inequality.

property level: float

The scalar upper bound c.

class pygeoinf.Subset(domain: HilbertSpace | None = None)[source]

Bases: ABC

Abstract base class for a subset of a HilbertSpace.

This class defines the minimal interface required for a mathematical set: knowing which space it lives in, determining if a vector belongs to it, accessing its boundary, and performing logical set operations.

abstract property boundary: Subset

Returns the boundary of the subset.

Returns:

A new Subset instance representing ∂S.

Return type:

Subset

property complement: Subset

S^c = {x | x not in S}.

Returns:

A generic Complement wrapper around this set.

Return type:

Complement

Type:

Returns the complement of this set

property domain: HilbertSpace

The underlying Hilbert space.

Returns:

The HilbertSpace instance associated with this subset.

Raises:

ValueError – If the domain was not set during initialization.

intersect(other: Subset) Subset[source]

Returns the intersection of this set and another: S ∩ O.

If both sets are instances of ConvexSubset, this returns a ConvexIntersection, which combines their functionals into a single convex constraint F(x) = max(f1(x), f2(x)).

Parameters:

other – Another Subset instance.

Returns:

A new set representing elements present in both sets.

Return type:

Subset

abstract is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if the vector x lies within the subset.

Parameters:
  • x – A vector from the domain.

  • rtol – Relative tolerance for floating-point comparisons (e.g., checking equality f(x) = c or inequality f(x) <= c).

Returns:

True if x ∈ S, False otherwise.

Return type:

bool

property is_empty: bool

Returns True if the set is known to be empty.

Returns:

True if the set contains no elements, False otherwise. Note that returning False does not guarantee the set is non-empty, only that it is not trivially known to be empty.

Return type:

bool

plot(on_subspace=None, *, bounds=None, grid_size: int = 200, rtol: float = 1e-06, alpha: float = 0.5, cmap: str = 'Blues', color: str = 'steelblue', show_plot: bool = True, ax=None, backend: str = 'auto')[source]

Visualize this subset along a 1D, 2D, or 3D affine subspace.

Delegates to pygeoinf.plot.plot_slice. For EuclideanSpace domains of dimension 1 or 2, on_subspace may be omitted and a canonical full-space AffineSubspace will be constructed automatically. For all other domains, on_subspace is required.

Parameters:
  • on_subspace – The AffineSubspace to slice along. Required unless the domain is a 1D or 2D EuclideanSpace.

  • bounds – Plot bounds passed to plot_slice.

  • grid_size – Samples per axis (passed to plot_slice).

  • rtol – Oracle tolerance (passed to plot_slice).

  • alpha – Fill transparency in [0, 1] (passed to plot_slice).

  • cmap – Colormap for 2D plots (passed to plot_slice).

  • color – Color string for 1D plots (passed to plot_slice).

  • show_plot – Whether to call plt.show() (passed to plot_slice).

  • ax – Optional existing Axes to draw into (passed to plot_slice).

  • backend – Rendering backend — "auto" (default), "matplotlib", or "plotly" (passed to plot_slice).

Returns:

(fig, ax, payload) — identical to plot_slice.

Raises:
  • ValueError – If on_subspace is None and the domain is not a 1D or 2D EuclideanSpace.

  • TypeError – If the domain is not an EuclideanSpace.

union(other: Subset) Union[source]

Returns the union of this set and another: S ∪ O.

Parameters:

other – Another Subset instance.

Returns:

A new set representing elements present in either set.

Return type:

Union

class pygeoinf.SubspaceSlicePlotter(subset: Subset, on_subspace: AffineSubspace, grid_size: int = 200, rtol: float = 1e-06, alpha: float = 0.5, bar_pixel_height: int = 6)[source]

Bases: object

Plotter for visualizing subsets sliced along 1D, 2D, or 3D affine subspaces.

Fully implemented for 1D, 2D, and 3D subspaces via three rendering paths:

  • PolyhedralSet → exact affine slice via scipy.spatial.HalfspaceIntersection + convex hull; payload is vertex array (n_vertices, n_dims).

  • Ball / Ellipsoid → exact quadratic slice via Cholesky-factored pullback metric; no grid sampling is performed:

    • 1D slice: payload is np.array([lo, hi]) — the two interval endpoints.

    • 2D slice: payload is boundary points, shape (N, 2) (N 360).

    • 3D slice: payload is surface points, shape (N_pts, 3).

    An empty or degenerate slice raises ValueError explicitly.

  • All other sets → raster oracle sampling on a grid_size^n grid; payload is boolean membership mask. For 3D, the mask is rendered as filled voxels using Matplotlib’s mpl_toolkits.mplot3d backend (Axes3D.voxels()).

Architecture:

  • Common methods (parse_bounds, embed_point, sample_membership) work for 1D, 2D, and 3D.

  • Dimension-specific _render_*() methods handle visualization.

embed_point(params: float | Tuple[float, ...] | List[float]) object[source]

Map parameter(s) to ambient point using tangent basis.

Universal formula (works for any dimension): x = translation + sum(params[i] * tangent_basis[i])

Parameters:
  • params

  • 1D (-) – Single float

  • 2D (-) – 2-tuple (u, v)

  • 3D (-) – 3-tuple (u, v, w)

Returns:

Ambient point as Vector

parse_bounds(bounds: tuple | List | None) tuple[source]

Parse and validate bounds for current dimension.

Flexible input format handling: - None: Use default [-1, 1] per dimension - 1D: (u_min, u_max) - 2D: (u_min, u_max, v_min, v_max) OR ((u_min, u_max), (v_min, v_max)) - 3D: (u_min, u_max, v_min, v_max, w_min, w_max) OR

((u_min, u_max), (v_min, v_max), (w_min, w_max))

Parameters:

bounds – User-provided bounds or None

Returns:

  • 1D: (u_min, u_max)

  • 2D: (u_min, u_max, v_min, v_max)

  • 3D: (u_min, u_max, v_min, v_max, w_min, w_max)

Return type:

Normalized tuple

Raises:

ValueError – If bounds format doesn’t match dimension

plot(bounds: tuple | List | None = None, cmap: str = 'Blues', color: str = 'steelblue', show_plot: bool = True, ax: Axes | None = None, backend: str = 'auto') tuple[source]

Main plotting method. Orchestrates bounds parsing, grid generation, membership sampling, and dimension-specific rendering.

Parameters:
  • bounds – Plot bounds (format depends on dimension)

  • cmap – Colormap for 2D/3D (ignored for 1D)

  • color – Color for 1D (ignored for 2D/3D)

  • show_plot – Whether to display the plot

  • ax – Optional existing Matplotlib Axes (must be None when backend='plotly').

  • backend – Rendering backend — "auto" (default), "matplotlib", or "plotly". "auto" selects Plotly for 3D when it is installed and falls back to Matplotlib otherwise. 1D/2D always use Matplotlib regardless of the backend value.

Returns:

(fig, ax, payload) tuple.

When the Matplotlib backend is used fig is a matplotlib.figure.Figure and ax is a Matplotlib Axes. When the Plotly backend is used fig is a plotly.graph_objects.Figure and ax is None.

payload semantics depend on the rendering path:

  • PolyhedralSet (exact affine path): vertex array in parameter coords, shape (n_vertices, n_dims).

  • Ball / Ellipsoid (exact quadratic path): interval endpoints [lo, hi] for 1D; boundary points (N, 2) for 2D; surface points (N_pts, 3) for 3D. Empty slices raise ValueError.

  • All other sets (sampled path): boolean membership mask, shape (grid_size,) in 1D, (grid_size, grid_size) in 2D, or (grid_size, grid_size, grid_size) in 3D.

Raises:

ValueError – If ax is not None when backend='plotly'.

sample_membership(param_grid: ndarray | Tuple[ndarray, ...]) ndarray[source]

Evaluate subset membership on parameter grid.

For each grid point, converts parameter coordinates to ambient space via embed_point(), then tests membership using subset.is_element().

Parameters:

param_grid – Parameter grid from _generate_param_grid()

Returns:

  • 1D: shape (grid_size,)

  • 2D: shape (grid_size, grid_size)

  • 3D: shape (grid_size, grid_size, grid_size)

Return type:

Boolean mask array

class pygeoinf.SupportFunction(primal_domain: HilbertSpace)[source]

Bases: NonLinearForm, ABC

Support function of a closed convex set S ⊆ H:

h_S(q) = sup_{x ∈ S} ⟨q, x⟩, q ∈ H

In a Hilbert space, we identify H ≅ H* via the Riesz map, so the functional is defined directly on the primal space H.

The set S is uniquely recovered as:

S = {x : ⟨q, x⟩ ≤ h_S(q) for all q ∈ H}

classmethod callable(primal_domain: HilbertSpace, mapping: Callable[[Vector], float], support_point: Callable[[Vector], Vector] | None = None) CallableSupportFunction[source]

Construct a support function from a user-supplied callable.

Parameters:
  • primal_domain – The Hilbert space H.

  • mapping – A callable q -> float that evaluates $h(q)$.

  • support_point – An optional callable q -> Vector returning $x^*(q) in argmax_{x in C} langle q, x rangle$. When provided, subgradient(q) delegates to it.

Returns:

A CallableSupportFunction instance.

image(operator: LinearOperator) LinearImageSupportFunction[source]

Return the support function of the linear image $A(C)$.

For a bounded linear operator $A$ with A.domain == self.primal_domain, returns the support function of the image set $A(C)$, which lives in A.codomain. Its value is $h_{A(C)}(q) = h_C(A^* q)$.

Parameters:

operator – A bounded linear operator $A: H to K$ with operator.domain equal to self.primal_domain.

Returns:

A LinearImageSupportFunction on operator.codomain.

Raises:

ValueError – If operator.domain != self.primal_domain.

classmethod point(primal_domain: HilbertSpace, point: Vector) PointSupportFunction[source]

Construct the support function of the singleton set ${p}$.

For a fixed point $p in H$, the support function is $h(q) = langle q, p rangle$.

Parameters:
  • primal_domain – The Hilbert space H containing $p$.

  • point – The fixed point $p$.

Returns:

A PointSupportFunction instance.

property primal_domain: HilbertSpace

The Hilbert space H in which the underlying convex set lives.

scale(alpha: float) ScaledSupportFunction[source]

Return the support function of the scaled set $alpha C$.

Scaling satisfies $h_{alpha C}(q) = alpha, h_C(q)$ for $alpha geq 0$.

Parameters:

alpha – A nonnegative scalar.

Returns:

A ScaledSupportFunction on the same space.

Raises:

ValueError – If alpha < 0.

subgradient(q: Vector) Vector[source]

Return a subgradient of the support function at q.

support_point(q: Vector) 'Vector' | None[source]

Optional: return x*(q) ∈ argmax_{x∈S} ⟨q, x⟩ if available/computable. Default: not provided (returns None). This is the subgradient of h_S at q.

translate(point: Vector) MinkowskiSumSupportFunction[source]

Return the support function of the translated set $C + p$.

Translation by $p in H$ satisfies $h_{C+p}(q) = h_C(q) + langle q, p rangle$.

Parameters:

point – The translation vector $p in H$ (same space as primal_domain).

Returns:

A MinkowskiSumSupportFunction on the same space.

value_and_support_point(q: Vector) tuple[float, Vector | None][source]

Return (h(q), x*(q)) sharing intermediate work where possible.

For a support function $h_S(q) = sup_{x in S} langle q, x rangle$, the scalar value and the maximiser $x^*(q)$ often share intermediate computations (e.g. a norm or an operator application). This method provides a single entry point so that overriding subclasses can exploit that sharing.

The default implementation simply calls self(q) and self.support_point(q) separately and is always correct. Concrete subclasses should override this when a fused computation is cheaper.

Parameters:

q – A vector in the primal domain $H$.

Returns:

A tuple (value, point) where

  • value is the float $h(q)$, always present;

  • point is $x^*(q) in argmax_{x in S} langle q, x rangle$ as a Vector, or None when a support point is unavailable.

class pygeoinf.Union(subsets: Iterable[Subset])[source]

Bases: Subset

Represents the union of multiple subsets: S = S_1 ∪ S_2 …

property boundary: Subset

Returns the boundary of the union. Currently raises NotImplementedError.

property complement: Subset

Returns the complement of the union.

Applies De Morgan’s Law: (A ∪ B)^c = A^c ∩ B^c. Returns an Intersection of the complements.

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True if x is in ANY of the component subsets.

property subsets: List[Subset]

Direct access to the component sets.

class pygeoinf.UniversalSet(domain: HilbertSpace | None = None)[source]

Bases: Subset

Represents the entire domain (Ω).

property boundary: Subset

The boundary of the entire topological space is empty.

property complement: Subset

The complement of the universe is the empty set.

is_element(x: Vector, /, *, rtol: float = 1e-06) bool[source]

Returns True for any vector in the domain.

pygeoinf.configure_threading(n_threads: int = 1)[source]

Sets the maximum number of threads used by underlying linear algebra backends (MKL, OpenBLAS, etc.).

Parameters:

n_threads – The number of threads to allow. Set to 1 for serial execution (safe for multiprocessing). Set to -1 or None to use all available cores.

pygeoinf.download_gsn_stations(force: bool = False) None[source]

Fetches the Global Seismograph Network (GSN) stations from the IRIS FDSN API and saves them to a local CSV file in the data/ directory.

pygeoinf.download_usgs_earthquakes(min_magnitude: float = 5.0, start_time: str = None, end_time: str = None, min_depth: float = None, max_depth: float = None, bbox: Tuple[float, float, float, float] = None, limit: int = 2000, force: bool = False, filename: str = 'usgs_events_filtered.csv') None[source]

Fetches a filtered catalog of earthquakes from the USGS API and saves it to a CSV in the centralized DATADIR.

pygeoinf.load_gsn_stations(n_stations: int = None, include_names: bool = False) List[Tuple[float, float]] | List[Tuple[str, float, float]][source]

Loads a representative global set of seismic stations from the GSN.

If the internal CSV file is missing, this function will attempt to automatically download it from IRIS into the pygeoinf/data/ directory.

Parameters:
  • n_stations – If provided, returns a random subsample of this size. If greater than the total available stations, returns all.

  • include_names – If True, returns (Name, Latitude, Longitude). If False, returns pure (Latitude, Longitude) tuples.

Returns:

A list of station tuples in degrees.

pygeoinf.plot_1d_distributions(posterior_measures: GaussianMeasure | Any | List[GaussianMeasure | Any], /, *, prior_measures: GaussianMeasure | Any | List[GaussianMeasure | Any] | None = None, true_value: float | None = None, show_true_value_in_legend: bool = False, ax: Axes | None = None, xlabel: str = 'Property Value', title: str = 'Prior and Posterior Probability Distributions', prior_labels: str | List[str] | None = None, posterior_labels: str | List[str] | None = None, width_scaling: float = 6.0, legend_position: tuple = (0.95, 0.95), fill_density: bool = False, **kwargs) Axes | Tuple[Axes, Axes][source]

Plot 1D probability distributions for prior and posterior measures using dual y-axes.

Parameters:
  • posterior_measures – Single measure or list of measures for posterior distributions

  • prior_measures – Single measure or list of measures for prior distributions (optional)

  • true_value – True value to mark with a vertical line (optional)

  • ax – An existing Matplotlib Axes object. If None, plots to the current active axes.

  • xlabel – Label for x-axis

  • title – Title for the plot

  • prior_labels – Manual labels for prior distributions (optional)

  • posterior_labels – Manual labels for posterior distributions (optional)

  • width_scaling – Width scaling factor in standard deviations (default: 6.0)

  • legend_position – Position of legend as (x, y) tuple (default: (0.95, 0.95))

  • fill_density – Whether to fill the area under the PDF curves (default: False)

  • **kwargs – Additional kwargs (e.g., figsize) safely ignored or forwarded.

Returns:

ax1 (if no priors) or (ax1, ax2) if dual axes are used.

pygeoinf.plot_corner_distributions(posterior_measure: GaussianMeasure, /, *, prior_measure: GaussianMeasure | None = None, true_values: List[float] | ndarray | None = None, show_true_value_in_legend: bool = False, labels: List[str] | None = None, title: str = 'Joint Posterior Distribution', figsize: tuple | None = None, colormap: str = 'Blues', contour_color: str = 'darkblue', parallel: bool = False, n_jobs: int = -1, width_scaling: float = 3.75, legend_position: tuple = (0.9, 0.95), fill_density: bool = False, num_sigmas: int = 3) ndarray[source]

Create a professional corner plot for multi-dimensional posterior distributions.

Parameters:
  • posterior_measure – Multi-dimensional posterior measure (pygeoinf GaussianMeasure)

  • prior_measure – Optional prior measure to plot secondary axes showing prior standard deviations.

  • true_values – True values for each dimension (optional)

  • labels – Labels for each dimension (optional)

  • title – Title for the plot

  • figsize – Figure size tuple (if None, calculated based on dimensions)

  • colormap – Colormap for 2D plots (used when fill_density=True)

  • contour_color – Uniform color for the 2D contour lines (used when fill_density=False)

  • parallel – Compute dense covariance matrix in parallel, default False.

  • n_jobs – Number of cores to use in parallel calculations, default -1.

  • width_scaling – Width scaling factor in standard deviations for default boundaries (default: 3.75)

  • legend_position – Position of legend as (x, y) tuple (default: (0.9, 0.95))

  • fill_density – Whether to fill the 2D contour background with color. False is recommended for sparse truth values.

  • num_sigmas – Minimum number of standard deviation contours to draw (dynamically scales up to enclose true values).

Returns:

An N x N NumPy array of Matplotlib Axes objects.

Return type:

axes

pygeoinf.plot_slice(subset: Subset, on_subspace: AffineSubspace, bounds=None, grid_size: int = 200, rtol: float = 1e-06, alpha: float = 0.5, cmap: str = 'Blues', color: str = 'steelblue', show_plot: bool = True, ax=None, backend: str = 'auto') Tuple[Any, Axes | None, ndarray][source]

Convenience wrapper: slice a subset along a 1D, 2D, or 3D affine subspace and plot.

Thin wrapper over SubspaceSlicePlotter. See that class for full documentation on the bounds format and return-value semantics.

Parameters:
  • subset – The Subset to visualize (domain must be EuclideanSpace).

  • on_subspace – A 1D, 2D, or 3D AffineSubspace to slice along.

  • bounds – Plot bounds — passed directly to SubspaceSlicePlotter.plot().

  • grid_size – Samples per axis (passed to SubspaceSlicePlotter).

  • rtol – Oracle tolerance (passed to SubspaceSlicePlotter).

  • alpha – Fill transparency (passed to SubspaceSlicePlotter).

  • cmap – Colormap for 2D/3D plots.

  • color – Color string for 1D plots.

  • show_plot – Whether to call plt.show().

  • ax – Optional existing Axes (or Axes3D) to draw into.

  • backend – Rendering backend — "auto" (default), "matplotlib", or "plotly". "auto" prefers Plotly for 3D when it is installed and warns then falls back to Matplotlib otherwise; 1D/2D always use Matplotlib.

Returns:

(fig, ax, payload) — identical to SubspaceSlicePlotter.plot().

payload semantics depend on set type and dimension:

  • Sampled path (non-PolyhedralSet): boolean membership mask.

    • 1D: shape (grid_size,)

    • 2D: shape (grid_size, grid_size)

    • 3D: shape (grid_size, grid_size, grid_size)mask[i, j, k] is True when the point at local parameter coordinates (u[i], v[j], w[k]) lies inside the subset.

  • Exact path (PolyhedralSet): vertex array in parameter coordinates.

    • 1D: np.array([u_lo, u_hi]) — interval endpoints

    • 2D: shape (n_vertices, 2) — polygon vertices

    • 3D: shape (n_vertices, 3) — polytope vertices

For 3D subspaces using backend='matplotlib' (or backend='auto' when Plotly is not installed), fig is a matplotlib.figure.Figure and ax is an Axes3D instance. For 3D subspaces using backend='plotly' (or backend='auto' when Plotly is installed), fig is a plotly.graph_objects.Figure and ax is None.

Raises:
  • TypeError – If subset.domain is not an EuclideanSpace.

  • ValueError – If bounds format is incompatible with the subspace dimension, or if grid_size, rtol, or alpha are out of range.

pygeoinf.sample_earthquakes(n_events: int, min_magnitude: float = 5.0) List[Tuple[float, float, float]][source]

Returns a random subsample of real earthquake locations.

If the local cache does not contain enough events to satisfy the request, it automatically fetches a larger catalog from the USGS to rebuild the cache.

Parameters:
  • n_events – The exact number of earthquake locations to return.

  • min_magnitude – The minimum magnitude to use if a new download is required.

Returns:

(Latitude, Longitude, Depth_in_km).

Return type:

A list of tuples

pygeoinf.white_noise_measure(domain: HilbertSpace) GaussianMeasure[source]

Creates a formal N(0, I) “white noise” measure on the given domain.

WARNING: Mathematically, the identity operator is not trace-class in infinite dimensions, meaning this does not define a valid Radon measure on the Hilbert space. It is a cylinder measure.

In this library, it is used strictly as a numerical tool to generate isotropic test vectors for randomized algorithms, ensuring that the sampling perfectly respects the domain’s inner product (e.g., mass matrices) without biasing the range approximation.