Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
fab813d
Draft Wires classes
santisoler Feb 3, 2026
4fe7b77
Add a BlockColumnMatrix custom operator
santisoler Feb 3, 2026
fabbaab
Improve some bits of the Wires and add methods
santisoler Feb 3, 2026
66fbc48
Fix bugs and improve Wires
santisoler Feb 3, 2026
5b825ca
Add support for model_slice to DataMisfit
santisoler Feb 3, 2026
ec3627f
Add notebook trying out gravity inversion with model slice
santisoler Feb 4, 2026
b707be0
Add docstring to Wires
santisoler Feb 4, 2026
2ffc4ab
Add `get_column` method to the `BlockColumnMatrix`
santisoler Feb 4, 2026
a56578f
Support `DataMisfit.hessian_diagonal` when `model_slice` is not None
santisoler Feb 4, 2026
e42130e
Rerun notebook
santisoler Feb 4, 2026
133a6fd
Merge branch 'main' into model-wiring
santisoler Feb 4, 2026
9e25e24
Fix type hint
santisoler Feb 4, 2026
7ce5f65
Improve docstring in DataMisfit
santisoler Feb 4, 2026
da9c49d
Support model_slice in Smallness
santisoler Feb 4, 2026
0915641
Rerun notebook
santisoler Feb 4, 2026
16778c1
Use a slicer matrix in DataMisfit instead of the BlockColumnMatrix
santisoler Feb 5, 2026
5e09873
Make use of the slicer matrix in Smallness
santisoler Feb 5, 2026
b1899e0
Delete the `operators` submodule
santisoler Feb 5, 2026
6f683f8
Implement model_slice in Flatness
santisoler Feb 5, 2026
c24988e
Make _slicer_matrix a property of DataMisfit and regs
santisoler Feb 5, 2026
724eda9
Add a BlockSquareMatrix class
santisoler Feb 10, 2026
3ff0c40
Add expand_array method to Wires
santisoler Feb 10, 2026
3d8ff90
Make use of ModelSlice methods in DataMisfit
santisoler Feb 10, 2026
11fcdf9
Use ModelSlice methods in mesh-based regularizations
santisoler Feb 10, 2026
cbf618b
Fix bug in definition of reference_model
santisoler Feb 10, 2026
90cc096
Rerun notebook
santisoler Feb 10, 2026
d7b832c
Define a support_model_slice decorator
santisoler Feb 10, 2026
c9e7239
Rerun notebook
santisoler Feb 10, 2026
f4f7dbe
Merge branch 'main' into model-wiring-matrix-slicer
santisoler Feb 10, 2026
b2fe79f
Add a HasDiagonal protocol
santisoler Feb 10, 2026
f04ff22
Add a MultiSlice class
santisoler Feb 11, 2026
ddfc411
Add a base class for model slices to avoid repetition
santisoler Feb 11, 2026
0f0bd00
Allow to get MultiSlice from Wires
santisoler Feb 11, 2026
605b6e3
Merge branch 'main' into model-wiring-matrix-slicer
santisoler Feb 11, 2026
0d02ba8
Merge branch 'multi-slicer' into model-wiring-matrix-slicer
santisoler Feb 11, 2026
053b182
Fix style
santisoler Feb 11, 2026
d500b1c
Fix import
santisoler Feb 11, 2026
b263337
Update type hints
santisoler Feb 11, 2026
b0bfa7e
Merge branch 'main' into model-wiring-matrix-slicer
santisoler Feb 12, 2026
e8ffcf6
Use SparseArray type alias in wires.py
santisoler Feb 12, 2026
4b67f24
Remove TODO comment
santisoler Feb 12, 2026
fd3bded
Add more TODO comments
santisoler Feb 12, 2026
ac847de
Fix comment in regularizations
santisoler Feb 12, 2026
27fc395
Merge branch 'main' into model-wiring-matrix-slicer
santisoler Mar 25, 2026
95ec959
Merge branch 'main' into model-wiring-matrix-slicer
santisoler Apr 14, 2026
c5c0026
Move `BlockSquareMatrix` class to `operators`
santisoler Apr 14, 2026
1530478
Instantiate variable as empty diagonal array
santisoler Apr 14, 2026
539b6db
Extend docs for `Wires`
santisoler Apr 14, 2026
ba46fed
Merge branch 'main' into model-wiring-matrix-slicer
santisoler Apr 14, 2026
428d732
Decorate `hessian_approx` and `hessian_diagonal`
santisoler Apr 14, 2026
de7e134
Revert "Decorate `hessian_approx` and `hessian_diagonal`"
santisoler Apr 14, 2026
ef3cdce
Remove decorators from DataMisfit `hessian_approx` and `hessian_diago…
santisoler Apr 14, 2026
7953d5b
Don't crop model or modify output if model is already reduced
santisoler Apr 14, 2026
0bf994c
Improve decorator
santisoler Apr 15, 2026
9f09f97
Update notebook
santisoler Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,089 changes: 1,089 additions & 0 deletions notebooks/50_gravity-inversion-with-model-slice.ipynb

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/inversion_ideas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)
from .regularization import Flatness, Smallness, SparseSmallness, TikhonovZero
from .simulations import wrap_simulation
from .wires import Wires

__all__ = [
"ChiTarget",
Expand All @@ -43,6 +44,7 @@
"SparseSmallness",
"TikhonovZero",
"UpdateSensitivityWeights",
"Wires",
"__version__",
"base",
"conjugate_gradient",
Expand Down
18 changes: 16 additions & 2 deletions src/inversion_ideas/data_misfit.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from .base import Objective
from .operators import get_diagonal
from .typing import Model, SparseArray
from .utils import support_model_slice
from .wires import ModelSlice, MultiSlice


class DataMisfit(Objective):
Expand Down Expand Up @@ -96,6 +98,7 @@ def __init__(
uncertainty: npt.NDArray[np.float64],
simulation,
*,
model_slice: ModelSlice | MultiSlice | None = None,
build_hessian=False,
estimate_hessian_diagonal=False,
):
Expand All @@ -104,23 +107,29 @@ def __init__(
self.data = data
self.uncertainty = uncertainty
self.simulation = simulation
self.model_slice = model_slice
self.build_hessian = build_hessian
self.estimate_hessian_diagonal = estimate_hessian_diagonal
self.set_name("d")

@support_model_slice
def __call__(self, model: Model) -> float:
residual = self.residual(model)
weights_matrix = self.weights_matrix
return residual.T @ weights_matrix.T @ weights_matrix @ residual

@support_model_slice
def gradient(self, model: Model) -> npt.NDArray[np.float64]:
"""
Gradient vector.
"""
jac = self.simulation.jacobian(model)
weights_matrix = self.weights_matrix
return 2 * jac.T @ (weights_matrix.T @ weights_matrix @ self.residual(model))
residual = self.simulation(model) - self.data
gradient = 2 * jac.T @ (weights_matrix.T @ weights_matrix @ residual)
return gradient

@support_model_slice
def hessian(
self, model: Model
) -> npt.NDArray[np.float64] | SparseArray | LinearOperator:
Expand All @@ -143,6 +152,7 @@ def hessian(
weights_matrix = aslinearoperator(self.weights_matrix)
return 2 * jac.T @ weights_matrix.T @ weights_matrix @ jac

@support_model_slice
def hessian_approx(self, model: Model) -> npt.NDArray[np.float64] | SparseArray:
"""
Approximated version of the Hessian.
Expand Down Expand Up @@ -180,6 +190,7 @@ def hessian_approx(self, model: Model) -> npt.NDArray[np.float64] | SparseArray:
return self.hessian(model) # type: ignore[return-value]
return diags_array(self.hessian_diagonal(model))

@support_model_slice
def hessian_diagonal(self, model: Model) -> npt.NDArray[np.float64]:
"""
Get the main diagonal of the Hessian.
Expand Down Expand Up @@ -242,6 +253,8 @@ def n_params(self):
"""
Number of model parameters.
"""
if self.model_slice is not None:
return self.model_slice.full_size
return self.simulation.n_params

@property
Expand All @@ -251,6 +264,7 @@ def n_data(self):
"""
return self.data.size

@support_model_slice(expand_return=False)
def residual(self, model: Model):
r"""
Residual vector.
Expand Down Expand Up @@ -286,7 +300,7 @@ def weights(self) -> npt.NDArray[np.float64]:
return 1 / self.uncertainty**2

@property
def weights_matrix(self) -> dia_array:
def weights_matrix(self) -> dia_array[np.float64]:
"""
Diagonal matrix with the square root of the regularization weights.
"""
Expand Down
105 changes: 104 additions & 1 deletion src/inversion_ideas/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

import numpy as np
import numpy.typing as npt
from scipy.sparse import dia_array
from scipy.sparse.linalg import LinearOperator

from inversion_ideas.typing import HasDiagonal, SparseArray
from .typing import HasDiagonal, SparseArray


def get_diagonal(operator: npt.NDArray | SparseArray | LinearOperator):
Expand Down Expand Up @@ -101,3 +102,105 @@ def _get_standard_basis(ndim: int, dtype=np.float64):
vector = np.zeros(ndim, dtype=dtype)
vector[i] = 1
yield vector


class BlockSquareMatrix(LinearOperator):
r"""
Operator that represents a square matrix with a non-zero block surrounded by zeros.

Use this class to represent hessian matrices that are built from smaller matrices
that operate only on a subset of the entire model. Only a block of that hessian will
be non-zero (the one related to the relevant model elements for that objective
function), while the rest of the matrix will be filled of zeros.

Parameters
----------
block : array, sparse array or LinearOperator
Square block matrix.
slice_matrix : dia_array
Diagonal array to represent the slicing matrix.

Notes
-----
This ``LinearOperator`` represents square matrices like:

.. math::

\mathbf{H} = \begin{bmatrix}
0 & 0 & 0\\
0 & \mathbf{B} & 0\\
0 & 0 & 0
\end{bmatrix}

where :math:`\mathbf{B}` is a non-zero square block matrix that sits in the diagonal
of :math:`\mathbf{H}`. The matrix :math:`\mathbf{H}` can be built as:

.. math::

\mathbf{H} = \mathbf{s}^T \mathbf{B} \mathbf{s}

where :math:`\mathbf{s}` is the *slicing matrix*.
"""

def __init__(
self,
block: npt.NDArray | LinearOperator | SparseArray,
slice_matrix: dia_array,
):
if block.shape[0] != block.shape[1]:
msg = (
f"Invalid block matrix '{block}' with shape '{block.shape}'. "
"It must be a square matrix."
)
raise ValueError(msg)

if slice_matrix.shape[0] != block.shape[1]:
msg = (
f"Invalid block '{block}' and slice_matrix '{slice_matrix}' with "
f"shapes '{block.shape}' and {slice_matrix.shape}."
)
raise ValueError(msg)

if slice_matrix.shape[1] <= block.shape[1]:
# TODO: improve msg
msg = "block is larger than slice matrix"
raise ValueError(msg)

_, full_size = slice_matrix.shape
shape = (full_size, full_size)
super().__init__(shape=shape, dtype=block.dtype)

self._block = block
self._slice_matrix = slice_matrix

@property
def block(self):
return self._block

@property
def slice_matrix(self) -> dia_array:
return self._slice_matrix

def _matvec(self, x):
"""
Dot product between the matrix and a vector.
"""
result = self.slice_matrix.T @ (self.block @ (self.slice_matrix @ x))
return result

def _rmatvec(self, x):
"""
Dot product between the transposed matrix and a vector.
"""
result = self.slice_matrix @ (self.block.T @ (self.slice_matrix.T @ x))
return result

def diagonal(self):
"""
Diagonal of the matrix.
"""
if not isinstance(self.block, HasDiagonal):
# TODO: Add msg
raise TypeError()
diagonal = self.block.diagonal()
return self.slice_matrix.T @ diagonal
4 changes: 4 additions & 0 deletions src/inversion_ideas/recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .preconditioners import JacobiPreconditioner
from .regularization import Flatness, Smallness
from .typing import Model, Preconditioner
from .wires import ModelSlice, MultiSlice


def create_l2_inversion(
Expand Down Expand Up @@ -273,6 +274,7 @@ def create_tikhonov_regularization(
alpha_y: float | None = None,
alpha_z: float | None = None,
reference_model_in_flatness: bool = False,
model_slice: ModelSlice | MultiSlice | None = None,
) -> Combo:
"""
Create a linear combination of Tikhonov (L2) regularization terms.
Expand Down Expand Up @@ -321,13 +323,15 @@ def create_tikhonov_regularization(
active_cells=active_cells,
cell_weights=cell_weights,
reference_model=reference_model,
model_slice=model_slice,
)
if alpha_s is not None:
smallness = alpha_s * smallness

kwargs = {
"active_cells": active_cells,
"cell_weights": cell_weights,
"model_slice": model_slice,
}
if reference_model_in_flatness:
kwargs["reference_model"] = reference_model
Expand Down
Loading
Loading