From 0f868d05ad0c20f2677cc96cce8d138ed6e067f2 Mon Sep 17 00:00:00 2001 From: Alex Lipp Date: Tue, 25 Aug 2026 18:22:38 +0100 Subject: [PATCH 1/3] Add linear least-squares unmixing solver with analytical uncertainties Implements the linear formulation of Appendix A of the preprint as a second, independent solver. Where SampleNetworkUnmixer penalises relative (log-ratio) misfit, LinearSampleNetworkUnmixer penalises absolute misfit, which makes the forward model an exactly invertible matrix and admits closed-form uncertainty. The mixing matrix M is square (one sub-basin per sample site), row-stochastic, and lower triangular in topological order with diagonal q_i/Q_i > 0, so it is always invertible and its inverse has the sparse closed form c_i = (Q_i d_i - sum_p alpha_p Q_p d_p) / q_i Key properties this buys: - Generalised least squares weighted by the data covariance, solved via a whitening transform, with Tikhonov regularisation lambda||Pc||^2 penalising the variance of the model about its own mean. - A guaranteed unique solution for every lambda >= 0, since M^T W M is positive definite even though the penalty operator P is singular. - Closed-form error propagation C_c = R C_d R^T and C_dhat = M C_c M^T, using exactly the estimator that produced the point estimate. - The Bayesian posterior covariance (M^T C_d^-1 M + lambda P)^-1, which unlike the propagated covariance does not understate the error at large lambda. - Resolution matrix and effective degrees of freedom. - Diagnostics for when the method is inapplicable: per-site noise amplification Q_i/q_i, condition number, and the unconstrained estimate before clamping. Also fixes a latent bug in nx_get_downstream_data, which used a truthiness test on the downstream node name. A node named integer 0 was reported as having no downstream neighbour, silently disconnecting the outlet of every integer-labelled network. The existing round-trip tests passed only because forward_model and SampleNetworkUnmixer both used the same broken accessor, cancelling the error. Co-Authored-By: Claude Opus 5 --- README.md | 50 ++ examples/unmix_linear_mwe.py | 116 ++++ funmixer/__init__.py | 8 + funmixer/linear_unmixer.py | 830 +++++++++++++++++++++++++ funmixer/network_unmixer.py | 6 +- tests/linear_unmixer_test.py | 705 +++++++++++++++++++++ tests/linear_vs_nonlinear_benchmark.py | 289 +++++++++ 7 files changed, 2003 insertions(+), 1 deletion(-) create mode 100644 examples/unmix_linear_mwe.py create mode 100644 funmixer/linear_unmixer.py create mode 100644 tests/linear_unmixer_test.py create mode 100644 tests/linear_vs_nonlinear_benchmark.py diff --git a/README.md b/README.md index 6ec4708..9a21583 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,56 @@ Some documented example scripts are given in the directory `examples/`, and are python examples/unmix_mwe.py ``` +## Choosing a solver + +Two solvers are available. They differ in what they treat as "misfit", which changes both the +answer and what you can say about its uncertainty. + +| | `SampleNetworkUnmixer` | `LinearSampleNetworkUnmixer` | +|---|---|---| +| Misfit | relative (log-ratio surrogate) | absolute (least-squares) | +| Best for | data spanning orders of magnitude | data with low log-variance, e.g. isotopic | +| Forward model | convex program | exactly invertible matrix `d = Mc` | +| Regularization | shrinks towards the mean *observation* | shrinks towards the mean *model* | +| Uncertainty | Monte Carlo (`solve_montecarlo`) | closed form, `C_c = R C_d R^T` | +| Resolution | not available | resolution matrix and effective DOF | + +The linear solver is documented in Appendix A of the paper. It is the faster and more +informative of the two when its assumptions hold, because the mixing matrix is square and +invertible, so the estimator, its covariance, and its resolution are all available in closed +form: + +```python +problem = funmixer.LinearSampleNetworkUnmixer(sample_network, use_regularization=True) +solution = problem.solve( + element_data, + regularization_strength=1.0, + data_covariance=10.0, # a 10% relative error on the observations +) +solution.upstream_preds # recovered source concentrations +solution.upstream_std # their 1-sigma uncertainties, no Monte Carlo needed +solution.downstream_covariance # covariance of the modelled observations +solution.effective_dof # how many degrees of freedom the data actually constrain +``` + +See `examples/unmix_linear_mwe.py`. + +**Check the diagnostics before trusting a linear solution.** Inverting `M` amounts to +differencing each site against its upstream neighbours, and the noise amplification at each +site is `Q_i/q_i`, the ratio of total upstream flux to the flux the sub-basin itself generates. +Where sub-basin areas are very uneven this is large, and the unregularized inversion will +produce negative or physically impossible concentrations. `solution.amplification`, +`solution.condition_number`, `solution.clamped_nodes` and `solution.unconstrained_preds` all +report on this. On the `Mg` example data, the unregularized inversion clamps 10 of 63 sites at +zero and would otherwise return concentrations up to 145% by mass; at `lambda = 1` none clamp. +Regularization is not optional for this kind of data. + +Note that `regularization_strength` is **not** comparable between the two solvers: the linear +one weights a squared penalty on deviations from the mean model, the non-linear one an +unsquared norm of deviations from the mean observation. Within the linear solver, lambda is +dimensionless (the data are mean-normalised internally), so it does transfer between elements +and datasets. + ## Cite If you use this please cite the paper, which is published at *Water Resources Research*. diff --git a/examples/unmix_linear_mwe.py b/examples/unmix_linear_mwe.py new file mode 100644 index 0000000..8ab84ce --- /dev/null +++ b/examples/unmix_linear_mwe.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 + +""" +Minimum working example for the *linear* unmixing solver. + +This is the counterpart to `unmix_mwe.py`. Where that script penalises relative (log-ratio) +misfit, this one penalises absolute misfit, which makes the forward model an exactly invertible +matrix and gives closed-form uncertainties: no Monte Carlo required. + +The script loads a sample network, inspects the diagnostics that say whether the linear +approach is appropriate for this network at all, sweeps the regularization strength to find the +elbow of the L-curve, then solves and maps both the recovered concentrations and their +propagated standard deviations. + +Run from the repository root: + python examples/unmix_linear_mwe.py +""" + +import logging +import warnings + +# pyre-fixme[21]: Could not find module `matplotlib.pyplot`. +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +import funmixer + +logging.getLogger().addHandler(logging.StreamHandler()) + +ELEMENT = "Mg" +RELATIVE_ERROR_PERCENT = 10.0 +REGULARIZATION_STRENGTH = 1.0 + + +def main() -> None: + sample_network, labels = funmixer.get_sample_graph( + flowdirs_filename="data/d8.asc", + sample_data_filename="data/sample_data.csv", + ) + + obs_data = pd.read_csv("data/sample_data.csv").drop(columns=["Bi", "S"]) + element_data = funmixer.get_element_obs(ELEMENT, obs_data) + + # ------------------------------------------------------------------ diagnostics + # Solve once with no regularization. This is the exact matrix inversion, and it is the + # honest test of whether the network supports a linear solution at all: if sites clamp at + # zero, or the amplification factors are large, the raw inversion is amplifying noise + # rather than resolving sources. + unregularized = funmixer.LinearSampleNetworkUnmixer(sample_network, use_regularization=False) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + raw = unregularized.solve(element_data, data_covariance=RELATIVE_ERROR_PERCENT) + + amplification = np.array(list(raw.amplification.values())) + unconstrained = np.array([raw.unconstrained_preds[n] for n in raw.node_order]) + print(f"Sites : {len(raw.node_order)}") + print(f"Condition number of M : {raw.condition_number:.4g}") + print( + "Noise amplification Q/q : " + f"median {np.median(amplification):.2f}, max {amplification.max():.1f}" + ) + print(f"Sites clamped at zero : {len(raw.clamped_nodes)}") + print( + "Unconstrained estimate range: " + f"{unconstrained.min():.0f} to {unconstrained.max():.0f} mg/kg" + ) + print( + " (values outside [0, 1e6] mg/kg mean the exact inversion is physically impossible,\n" + " which is the signal that regularization is needed)" + ) + + # ---------------------------------------------------------------------- L-curve + # `plot_sweep_of_regularizer_strength` is duck-typed on solve/get_misfit/get_roughness, + # so it works on the linear solver unchanged. + problem = funmixer.LinearSampleNetworkUnmixer(sample_network, use_regularization=True) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + funmixer.plot_sweep_of_regularizer_strength(problem, element_data, -3, 2, 11) + + # ------------------------------------------------------------------------ solve + solution = problem.solve( + element_data, + regularization_strength=REGULARIZATION_STRENGTH, + data_covariance=RELATIVE_ERROR_PERCENT, + ) + print(f"\nlambda : {solution.regularization_strength}") + print(f"Effective degrees of freedom: {solution.effective_dof:.1f}") + print(f"Sites clamped at zero : {len(solution.clamped_nodes)}") + relative_sigma = np.array( + [solution.upstream_std[n] / abs(solution.upstream_preds[n]) for n in solution.node_order] + ) + print(f"Median relative uncertainty : {100 * np.median(relative_sigma):.1f}%") + + funmixer.visualise_downstream( + pred_dict=solution.downstream_preds, obs_dict=element_data, element=ELEMENT + ) + + # ------------------------------------------------------------------------- maps + area_dict = funmixer.get_unique_upstream_areas(sample_network, labels) + concentration_map = funmixer.get_upstream_concentration_map(area_dict, solution.upstream_preds) + uncertainty_map = funmixer.get_upstream_concentration_map(area_dict, solution.upstream_std) + + _, axes = plt.subplots(1, 2, figsize=(15, 6)) + concentrations = axes[0].imshow(concentration_map) + axes[0].set_title(f"Recovered {ELEMENT} source concentration") + plt.colorbar(concentrations, ax=axes[0], label="mg/kg") + + uncertainties = axes[1].imshow(uncertainty_map) + axes[1].set_title(f"Propagated 1-sigma uncertainty ({RELATIVE_ERROR_PERCENT:.0f}% data error)") + plt.colorbar(uncertainties, ax=axes[1], label="mg/kg") + plt.show() + + +if __name__ == "__main__": + main() diff --git a/funmixer/__init__.py b/funmixer/__init__.py index 508b4b6..52bf07c 100644 --- a/funmixer/__init__.py +++ b/funmixer/__init__.py @@ -1,8 +1,13 @@ from .d8processing import * # noqa: F403 +from .linear_unmixer import ( + LinearFunmixerSolution, + LinearSampleNetworkUnmixer, +) from .network_unmixer import ( ELEMENT_LIST, ElementData, + FunmixerSolution, SampleNetworkUnmixer, SampleNode, forward_model, @@ -21,6 +26,9 @@ __all__ = [ "ElementData", "ELEMENT_LIST", + "FunmixerSolution", + "LinearFunmixerSolution", + "LinearSampleNetworkUnmixer", "get_element_obs", "get_unique_upstream_areas", "get_upstream_concentration_map", diff --git a/funmixer/linear_unmixer.py b/funmixer/linear_unmixer.py new file mode 100644 index 0000000..38eab66 --- /dev/null +++ b/funmixer/linear_unmixer.py @@ -0,0 +1,830 @@ +#!/usr/bin/env python3 +""" +Linear (least-squares) unmixing on a sample network. + +This module implements the linear formulation described in Appendix A of the funmixer +preprint. Where :class:`~funmixer.network_unmixer.SampleNetworkUnmixer` penalises *relative* +differences between predicted and observed concentrations (a convex surrogate for the +log-ratio misfit), this module penalises *absolute* differences. That choice is appropriate +when the tracer has low log-variance and it turns the whole problem into a linear system. + +The forward model +----------------- +The observation at site ``i`` is a flux-weighted mixture of the source concentrations of every +sub-basin upstream of (and including) ``i``:: + + d = M c, M_ij = Theta_ij * alpha_{j->i} * q_j / Q_i + +where ``Theta`` is the path matrix of the sample network with self-edges, ``q_j`` is the +material flux generated by sub-basin ``j``, ``Q_i = sum_j Theta_ij q_j`` is the total flux +passing site ``i``, and ``alpha_{j->i} = exp(-sum k*L)`` is the first-order decay accumulated +along the path from ``j`` to ``i``. Decay is applied to the tracer flux only and never to the +total flux, matching :func:`~funmixer.network_unmixer.forward_model`. + +Why this is exactly soluble +--------------------------- +funmixer defines exactly one sub-basin per sample site, so ``M`` is square (n x n). Ordered +topologically (upstream to downstream) it is **lower triangular with strictly positive +diagonal** ``M_ii = q_i / Q_i``, and is therefore always invertible. Two consequences: + +1. At ``lambda = 0`` the problem is not really least-squares at all -- it has an exact, + zero-residual solution ``c = M^-1 d``. There is no pseudo-inverse ambiguity, and weighted + (GLS) variants collapse to the same estimator, so "the estimator used when unconstrained" + is unambiguous. The inverse has a sparse closed form:: + + c_i = ( Q_i d_i - sum_{p in direct upstream nbrs of i} alpha_p Q_p d_p ) / q_i + + i.e. "tracer flux out, minus tracer flux in, over own flux". +2. The non-negativity constraint bites *exactly* when ``Q_i d_i < sum_p alpha_p Q_p d_p`` -- + when a site carries less tracer flux than its own tributaries deliver. That is precisely + where the method breaks down, and it is directly checkable via + :attr:`LinearFunmixerSolution.unconstrained_preds`. + +The per-node noise amplification of the inversion is ``1 / M_ii = Q_i / q_i``. A sample taken +just downstream of another, with little incremental drainage area between them, has a large +amplification factor and will be poorly resolved. This -- the disparity in *sub-basin areas* -- +is the real control on whether the linear approach is usable for a given network, and it is +reported as :attr:`LinearFunmixerSolution.amplification`. + +Regularization +-------------- +Optionally we penalise the variance of ``c``, i.e. deviations of ``c`` from its *own* mean:: + + minimize (M c - d)^T C_d^-1 (M c - d) + lambda ||P c||^2 subject to c >= 0 + +(the misfit term is ``||M c - d||^2`` when the solve is unweighted) + +with ``P = I - (1/n) 1 1^T`` the centering projector, so that ``||P c||^2 = sum_i (c_i - +c_bar)^2 = n * var(c)``. Note lambda therefore weights the *sum* of squared deviations rather +than their mean, so a factor of ``n`` is absorbed into it. Note also this differs deliberately +from +:class:`SampleNetworkUnmixer`, which is forced by convexity to shrink towards the mean +*observation*; the linear form has no such constraint and can shrink towards the mean *model*. + +A solution is guaranteed for every ``lambda >= 0``: the Hessian is ``2(M^T M + lambda P)``, and +although ``P`` alone is only positive semi-definite (its null space is ``span{1}``), ``M^T M`` +is positive definite because ``M`` is invertible. The objective is therefore strictly convex, +giving a unique global minimiser, and it stays unique under ``c >= 0``. Regularization strictly +improves the conditioning, so ``lambda > 0`` is always better behaved than ``lambda = 0``. +Uniqueness does rest on ``M`` being invertible -- ``lambda P`` could not rescue a singular +``M`` on its own. + +Error propagation +----------------- +Because ``||P c||^2`` is homogeneous in ``c``, the estimator carries **no constant offset** and +is purely linear in the data:: + + c_hat = R d, R = (M^T M + lambda P)^-1 M^T + C_c = R C_d R^T (covariance of the recovered source concentrations) + C_dhat = M C_c M^T (covariance of the modelled observations) + +At ``lambda = 0`` this reduces to ``M^-1 C_d M^-T`` exactly, so the regularized and +unregularized cases share a single code path. Likewise the modelled-observation covariance +reduces to ``C_d`` itself at ``lambda = 0``, since the fit is exact there. + +At ``lambda = 0`` the estimator is unbiased and ``C_c`` is the complete error. It also equals +``(M^T C_d^-1 M)^-1``, the Cramer-Rao lower bound, so the unregularized inversion is an +efficient estimator and no weighting can improve on it. + +Once ``lambda > 0`` the estimator is **biased**, and two different covariances become relevant. +Both are reported: + +* ``upstream_covariance``: the *propagated* (sampling) covariance ``R C_d R^T``. This answers + "if I repeated the measurements, how would my estimate scatter?" It is exactly the linear + propagation of ``C_d`` through the estimator actually used -- but it **shrinks as lambda + grows**, because a more heavily damped estimator is more stable while being more wrong. Used + alone at large lambda it badly understates the true error. +* ``upstream_posterior_covariance``: ``(M^T C_d^-1 M + lambda P)^-1``, the Bayesian posterior + covariance obtained by reading the penalty as a prior. This accounts for what the damping + costs as well as what it stabilises, and is the more honest error bar to quote at + ``lambda > 0``. The two are related exactly by:: + + R C_d R^T = (M^T C_d^-1 M + lambda P)^-1 - lambda A^-1 P A^-1, A = M^T C_d^-1 M + lambda P + + so the propagated covariance is always the smaller of the two, and they coincide at + ``lambda = 0``. + +The remaining, unquantifiable piece is the bias itself, +``bias = -lambda A^-1 P c_true``, which needs the unknown ``c_true``. As a proxy we report the +resolution matrix ``R M`` (the identity at ``lambda = 0``, degrading as ``lambda`` grows) and +its trace, the effective number of degrees of freedom. + +References +---------- +These are standard results in discrete linear inverse theory; the notation below follows the +first two. + +* Menke, W., *Geophysical Data Analysis: Discrete Inverse Theory*. Weighted least squares, + damped least squares, and the two results used most directly here: for any linear estimator + ``m_est = G^-g d`` the model covariance is ``C_m = G^-g C_d G^-g^T``, and the model + resolution matrix is ``R = G^-g G``. +* Aster, R., Borchers, B. and Thurber, C., *Parameter Estimation and Inverse Problems*. + Tikhonov regularization, the L-curve, resolution, and the bias-variance decomposition of the + regularized solution -- i.e. why the propagated covariance is not the whole error. +* Tarantola, A. (2005), *Inverse Problem Theory and Methods for Model Parameter Estimation*, + SIAM. The linear-Gaussian case, where the posterior covariance is + ``(G^T C_d^-1 G + C_m^-1)^-1``; this is the source of `upstream_posterior_covariance`. +* Hoerl, A. E. and Kennard, R. W. (1970), "Ridge Regression: Biased Estimation for + Nonorthogonal Problems", *Technometrics* 12(1), 55-67. The original bias-variance argument + for penalised estimators. +* Hansen, P. C. (1998), *Rank-Deficient and Discrete Ill-Posed Problems*, SIAM. Filter factors, + the L-curve, and general-form (seminorm) Tikhonov regularization -- the case here, since the + penalty operator ``P`` is singular. The uniqueness condition for general-form Tikhonov is + that the null spaces of the forward and penalty operators intersect only at zero, which + holds here because ``M`` is invertible. See also Elden, L. (1977), "Algorithms for the + regularization of ill-conditioned least squares problems", *BIT* 17, 134-145. +* Golub, G. H. and Van Loan, C. F., *Matrix Computations*, for the numerical treatment of + generalised least squares via whitening, and of Tikhonov problems via augmented systems. +""" + +import time +import warnings +from dataclasses import dataclass +from typing import Dict, List, Optional, Tuple, Union + +import cvxpy as cp +import networkx as nx +import numpy as np +import numpy.typing as npt + +from .network_unmixer import ( + SOLVERS, + ElementData, + ExportRateData, + RateConstantData, + SampleNode, + nx_get_downstream_node, + nx_topological_sort_with_data, +) + +FloatArray = npt.NDArray[np.float64] + +# A node is treated as clamped by the c >= 0 constraint when its unconstrained estimate falls +# below this (in mean-normalised units, where a typical concentration is 1). +CLAMP_TOLERANCE: float = 0.0 + +# Relative tolerance for the sanity check that CVXPY agrees with the analytical estimator on +# problems where no constraint is active. This is deliberately loose: interior-point solvers +# converge to ~1e-8, which an ill-conditioned mixing matrix amplifies by cond(M). A genuine +# mismatch between the two formulations would show up at order 1, not near this threshold. +ESTIMATOR_AGREEMENT_TOLERANCE: float = 1e-4 + +# Covariance input given as a bare number is interpreted as a relative error in *percent*, +# matching the `relative_error` convention of SampleNetworkUnmixer.solve_montecarlo. +DataCovariance = Union[float, int, ElementData, FloatArray, None] + + +@dataclass +class LinearFunmixerSolution: + """ + Results of a :class:`LinearSampleNetworkUnmixer` run. + + The first seven fields mirror :class:`~funmixer.network_unmixer.FunmixerSolution` so that + this object is a drop-in replacement for downstream plotting helpers. The remainder are + specific to the linear formulation. + + Attributes: + objective_value: The value of the (normalised-space) objective at the solution. + solver_name: Name of the solver that CVXPY used. + total_time: Wall-clock time for the whole solve, in seconds. + solve_time: Solver-reported solve time, in seconds. + setup_time: Solver-reported setup time, in seconds. + downstream_preds: Predicted concentration at each sample site, in the input units. + upstream_preds: Recovered source concentration for each sub-basin, in the input units. + node_order: Topological ordering of the sample sites. This is the row/column ordering + of every matrix below, and of any array-valued input. + regularization_strength: The value of lambda used. + weighted: Whether the misfit was weighted by the inverse data covariance. + upstream_covariance: (n, n) *propagated* covariance of `upstream_preds`, in + (input units)^2, or None if no `data_covariance` was supplied. Equal to + `R C_d R^T`: the linear propagation of the data covariance through the estimator + actually used. Exact, but it shrinks as lambda grows and so understates the true + error at large lambda -- see `upstream_posterior_covariance`. + upstream_std: Square root of the diagonal of `upstream_covariance`, per site. + upstream_posterior_covariance: (n, n) Bayesian posterior covariance + `(M^T C_d^-1 M + lambda P)^-1`, in (input units)^2. Available only for a weighted + solve with `data_covariance` supplied, since it needs `C_d^-1`. This accounts for + the cost of the damping as well as its stabilising effect, and is the more honest + error bar to quote when lambda > 0. Identical to `upstream_covariance` at + lambda = 0, and always at least as large. + upstream_posterior_std: Square root of the diagonal of + `upstream_posterior_covariance`, per site. + downstream_covariance: (n, n) covariance of `downstream_preds`, in (input units)^2, or + None. Equal to `M C_c M^T`; at lambda = 0 this is exactly `data_covariance`. + downstream_std: Square root of the diagonal of `downstream_covariance`, per site. + resolution_matrix: (n, n) resolution matrix `R M`. The identity at lambda = 0. + effective_dof: `trace(R M)`, the effective number of degrees of freedom. Equal to n at + lambda = 0 and decreasing as lambda grows. + unconstrained_preds: The raw estimate `R d` before the c >= 0 constraint is applied, in + the input units. Negative entries here show where, and by how much, the linear + model fails. + clamped_nodes: Sites whose unconstrained estimate was negative. If this is empty the + returned estimator is exactly linear and the propagated covariances are exact. + amplification: Per-site noise amplification `Q_i / q_i` of the inversion. + condition_number: Condition number of the mixing matrix `M`. + residual_norm: `||M c - d||_2` in normalised space. Zero at lambda = 0 with no clamping. + """ + + objective_value: float + solver_name: str + total_time: float + solve_time: Optional[float] + setup_time: Optional[float] + downstream_preds: ElementData + upstream_preds: ElementData + node_order: List[str] + regularization_strength: float + weighted: bool + unconstrained_preds: ElementData + clamped_nodes: List[str] + amplification: ElementData + condition_number: float + residual_norm: float + upstream_covariance: Optional[FloatArray] = None + upstream_std: Optional[ElementData] = None + upstream_posterior_covariance: Optional[FloatArray] = None + upstream_posterior_std: Optional[ElementData] = None + downstream_covariance: Optional[FloatArray] = None + downstream_std: Optional[ElementData] = None + resolution_matrix: Optional[FloatArray] = None + effective_dof: Optional[float] = None + + +class LinearSampleNetworkUnmixer: + """ + Unmix a network of concentration observations using a linear (least-squares) formulation. + + This is the Appendix A counterpart to :class:`~funmixer.network_unmixer.SampleNetworkUnmixer`: + it penalises absolute rather than relative misfit, which makes the forward model an exactly + invertible matrix and admits closed-form error propagation. See the module docstring for the + mathematics. + + The API deliberately mirrors :class:`SampleNetworkUnmixer`. In particular ``solve``, + ``get_misfit`` and ``get_roughness`` have compatible signatures, so + :func:`~funmixer.network_unmixer.plot_sweep_of_regularizer_strength` works on this class + unchanged. + + Note: + ``regularization_strength`` is **not** comparable between the two solvers. This one + weights a *squared* penalty on deviations from the mean *model*; the non-linear solver + weights an unsquared norm of deviations from the mean *observation*. Because the data + are mean-normalised internally, lambda here is dimensionless and does transfer between + elements and datasets. + """ + + def __init__( + self, + sample_network: nx.DiGraph, + use_regularization: bool = True, + rate_constants: Optional[RateConstantData] = None, + ) -> None: + """ + Initialise the LinearSampleNetworkUnmixer. + + Args: + sample_network: The sample network (see `get_sample_graph`). + use_regularization: Whether to include the model-variance penalty. If True, a + `regularization_strength` must be supplied to `solve`. + rate_constants: Per-site first-order decay constants k [L^-1]. Sites omitted from + the dict default to 0 (conservative behaviour). Can be overridden per-solve. + """ + self.sample_network = sample_network + self.node_order: List[str] = [ + name for name, _ in nx_topological_sort_with_data(sample_network) + ] + self._index: Dict[str, int] = {name: i for i, name in enumerate(self.node_order)} + self._n: int = len(self.node_order) + if self._n == 0: + raise ValueError("Cannot unmix an empty sample network.") + + # Sub-basin areas, normalised by their mean purely for numerical conditioning. This + # mirrors the `rltv_area` semantics of SampleNetworkUnmixer, but is kept in a local + # array: the `SampleNode` attributes are shared scratch space used by `forward_model` + # and `SampleNetworkUnmixer`, and writing to them would corrupt a live problem built + # on the same graph. + areas = np.array([self._node_data(name).area for name in self.node_order], dtype=np.float64) + if np.any(areas <= 0): + raise ValueError("All sub-basin areas must be strictly positive.") + self._rltv_area: FloatArray = areas / float(np.mean(areas)) + + self._use_regularization = use_regularization + self._rate_constants: Optional[RateConstantData] = rate_constants + + # State from the most recent solve, used by get_misfit / get_roughness / get_estimator. + self._M: Optional[FloatArray] = None + self._R: Optional[FloatArray] = None + self._c_norm: Optional[FloatArray] = None + self._d_norm: Optional[FloatArray] = None + self._M_solver: Optional[FloatArray] = None + self._d_solver: Optional[FloatArray] = None + self._weighted: bool = False + + # The compiled CVXPY problem, and the mixing matrix it was compiled for. + self._problem: Optional[cp.Problem] = None + self._problem_M: Optional[FloatArray] = None + + # ------------------------------------------------------------------ helpers + + def _node_data(self, name: str) -> SampleNode: + return self.sample_network.nodes[name]["data"] + + def _centering_projector(self) -> FloatArray: + """P = I - (1/n) 1 1^T, the projector onto the space of mean-zero vectors.""" + n = self._n + return np.eye(n) - np.ones((n, n)) / n + + def _ensure_problem(self, M: FloatArray) -> None: + """ + Build (or reuse) the CVXPY problem for a given mixing matrix. + + `d` and `lambda` are Parameters, so Monte Carlo draws, sweeps over lambda and sweeps + over elements all reuse a single compiled problem -- the DPP intent of + `SampleNetworkUnmixer`. `M`, by contrast, is embedded as a constant. Making it a + Parameter is technically DPP-compliant but puts n^2 parameters into the problem, which + CVXPY canonicalises far more slowly than it recompiles. `M` only changes when the + export rates or rate constants change, so rebuilding on that event is much cheaper. + + The objective uses *squared* norms, unlike `SampleNetworkUnmixer._build_problem`. That + is what makes the estimator linear, and is not optional here. + """ + if self._problem is not None and np.array_equal(M, self._problem_M): + return + + n = self._n + self._c_var = cp.Variable(n, nonneg=True) + self._d_param = cp.Parameter(n) + self._lam_param = cp.Parameter(nonneg=True) + + objective = cp.sum_squares(M @ self._c_var - self._d_param) + if self._use_regularization: + # ||P c||^2, i.e. the variance of the model about its own mean. + objective += self._lam_param * cp.sum_squares(self._c_var - cp.sum(self._c_var) / n) + + self._problem = cp.Problem(cp.Minimize(objective)) + assert self._problem.is_dcp(dpp=True) + self._problem_M = M.copy() + + # ---------------------------------------------------------- forward operator + + def _build_mixing_matrix( + self, + export_rates: Optional[ExportRateData], + rate_constants: Optional[RateConstantData], + ) -> Tuple[FloatArray, FloatArray, FloatArray]: + """ + Build the mixing matrix `M`, and the total (`Q`) and own (`q`) fluxes of each site. + + This performs exactly the topological sweep of + :func:`~funmixer.network_unmixer.forward_model`, but propagates a *row of coefficients* + instead of a scalar concentration. It is therefore identical to running the forward + model once per basis vector, and `M @ c == forward_model(network, c)` to machine + precision. + + Returns: + (M, total_flux, own_flux), all ordered by `self.node_order`. + """ + n = self._n + M = np.zeros((n, n), dtype=np.float64) + tracer_rows = np.zeros((n, n), dtype=np.float64) + total_flux = np.zeros(n, dtype=np.float64) + own_flux = np.zeros(n, dtype=np.float64) + + for name in self.node_order: + i = self._index[name] + rate = export_rates[name] if export_rates else 1.0 + flux = rate * self._rltv_area[i] + + own_flux[i] = flux + # Contributions from upstream have already been accumulated into these, because + # `node_order` is a topological sort. + total_flux[i] += flux + tracer_rows[i, i] += flux + + M[i, :] = tracer_rows[i, :] / total_flux[i] + + downstream = nx_get_downstream_node(self.sample_network, name) + if downstream is not None: + j = self._index[downstream] + length = self.sample_network[name][downstream]["length"] + k = rate_constants.get(name, 0.0) if rate_constants else 0.0 + alpha = float(np.exp(-k * length)) + # NB: decay applies to the tracer flux only, never to the total flux. + total_flux[j] += total_flux[i] + tracer_rows[j, :] += tracer_rows[i, :] * alpha + + return M, total_flux, own_flux + + def _solve_unregularized( + self, + d_norm: FloatArray, + total_flux: FloatArray, + own_flux: FloatArray, + rate_constants: Optional[RateConstantData], + ) -> FloatArray: + """ + Exact O(n + edges) solution of `M c = d` using the sparse closed form for `M^-1`. + + `c_i = (Q_i d_i - sum_p alpha_p Q_p d_p) / q_i`, i.e. the tracer flux leaving site `i` + minus the tracer flux delivered by its immediate upstream neighbours, divided by the + flux that site `i` itself generates. + """ + tracer_flux = total_flux * d_norm + c = np.empty(self._n, dtype=np.float64) + for name in self.node_order: + i = self._index[name] + accumulated = tracer_flux[i] + for upstream in self.sample_network.predecessors(name): + length = self.sample_network[upstream][name]["length"] + k = rate_constants.get(upstream, 0.0) if rate_constants else 0.0 + accumulated -= float(np.exp(-k * length)) * tracer_flux[self._index[upstream]] + c[i] = accumulated / own_flux[i] + return c + + def _whitener(self, covariance_norm: FloatArray) -> FloatArray: + """ + Build `L^-1`, where `C = L L^T`, so that `L^-1 e` has identity covariance. + + Pre-multiplying the system by this turns generalised least squares into ordinary + least squares: minimising `||L^-1(Mc - d)||^2` is minimising the Mahalanobis misfit + `(Mc - d)^T C^-1 (Mc - d)`. + """ + n = self._n + diagonal = np.diag(covariance_norm) + if np.allclose(covariance_norm, np.diag(diagonal)): + # The common case. Much cheaper, and avoids a needless factorisation. + if np.any(diagonal <= 0): + raise ValueError( + "data_covariance has a zero or negative variance, so it cannot be used as " + "a weighting. Pass weighted=False, or give every site a positive variance." + ) + return np.diag(1.0 / np.sqrt(diagonal)) + try: + factor = np.linalg.cholesky(covariance_norm) + except np.linalg.LinAlgError as exc: + raise ValueError( + "data_covariance must be positive definite to be used as a weighting. " + "Pass weighted=False to fall back to an unweighted inversion." + ) from exc + return np.linalg.solve(factor, np.eye(n)) + + def _build_estimator( + self, M: FloatArray, lam: float, whitener: Optional[FloatArray] + ) -> FloatArray: + """ + Build the linear estimator `R` such that `c_hat = R d`. + + Unweighted, this is `R = (M^T M + lambda P)^-1 M^T`. Weighted by `W = C_d^-1` it is + the generalised-least-squares estimator `R = (M^T W M + lambda P)^-1 M^T W`, which we + form via the whitening transform `M_w = L^-1 M` so there is only one code path. + + At lambda = 0 this is exactly `M^-1` **whatever the weighting**: `M` is square and + invertible, so `(M^T W M)^-1 M^T W = M^-1 W^-1 M^-T M^T W = M^-1`. The unregularized + fit is exact and passes through every observation, so there is nothing for a weighting + to trade off. Weighting only changes the answer once `lambda > 0`. + """ + n = self._n + if lam == 0.0: + return np.linalg.solve(M, np.eye(n)) + M_w = M if whitener is None else whitener @ M + # M_w^T M_w + lambda P is symmetric positive definite: M_w^T M_w is PD because both + # M and the whitener are invertible, and lambda P is PSD. + normal_matrix = M_w.T @ M_w + lam * self._centering_projector() + adjoint = M_w.T if whitener is None else M_w.T @ whitener + return np.linalg.solve(normal_matrix, adjoint) + + # ------------------------------------------------------------- data handling + + def _observations_to_array(self, observation_data: ElementData) -> FloatArray: + missing = set(self.node_order) - set(observation_data) + if missing: + raise ValueError(f"No observation supplied for site(s): {sorted(missing)}") + unknown = set(observation_data) - set(self.node_order) + if unknown: + raise ValueError(f"Observation supplied for unknown site(s): {sorted(unknown)}") + return np.array([observation_data[name] for name in self.node_order], dtype=np.float64) + + def _coerce_data_covariance( + self, data_covariance: DataCovariance, d: FloatArray + ) -> Optional[FloatArray]: + """ + Normalise the many accepted spellings of `data_covariance` into an (n, n) matrix. + + Accepted forms, all in the *input* units of the observations: + None -- skip error propagation. + a bare number -- a relative error in percent, so sigma_i = d_i * value / 100. + This matches `SampleNetworkUnmixer.solve_montecarlo`'s + `relative_error`, so the two are directly comparable. + dict -- per-site standard deviations. + 1-D array (n,) -- per-site standard deviations, in `node_order`. + 2-D array (n, n) -- a full covariance matrix, in `node_order`. + """ + if data_covariance is None: + return None + + n = self._n + if isinstance(data_covariance, (float, int)) and not isinstance(data_covariance, bool): + sigma = d * float(data_covariance) / 100.0 + return np.diag(sigma**2) + + if isinstance(data_covariance, dict): + missing = set(self.node_order) - set(data_covariance) + if missing: + raise ValueError(f"No data_covariance entry for site(s): {sorted(missing)}") + sigma = np.array( + [float(data_covariance[name]) for name in self.node_order], dtype=np.float64 + ) + return np.diag(sigma**2) + + array = np.asarray(data_covariance, dtype=np.float64) + if array.ndim == 1: + if array.shape != (n,): + raise ValueError(f"1-D data_covariance must have shape ({n},), got {array.shape}") + return np.diag(array**2) + if array.ndim == 2: + if array.shape != (n, n): + raise ValueError( + f"2-D data_covariance must have shape ({n}, {n}), got {array.shape}" + ) + return array + raise ValueError("data_covariance must be a number, dict, 1-D array or 2-D array.") + + def _to_dict(self, values: FloatArray) -> ElementData: + return {name: float(values[self._index[name]]) for name in self.node_order} + + # -------------------------------------------------------------------- solve + + def solve( + self, + observation_data: ElementData, + export_rates: Optional[ExportRateData] = None, + rate_constants: Optional[RateConstantData] = None, + regularization_strength: Optional[float] = None, + data_covariance: DataCovariance = None, + weighted: bool = True, + solver: str = "clarabel", + compute_resolution: bool = True, + warm_start: bool = False, + ) -> LinearFunmixerSolution: + """ + Solve the linear unmixing problem. + + The observations are divided by their arithmetic mean before solving and the results + multiplied back afterwards. The problem is homogeneous, so this has no effect on the + answer -- it exists purely to keep the numbers near unity for the solver, and to make + `regularization_strength` dimensionless and comparable across datasets. + + Args: + observation_data: Observed concentration at each sample site. + export_rates: Export rate for each sub-catchment. Defaults to 1 everywhere (i.e. + homogeneous erosion/runoff). + rate_constants: Per-site first-order decay constants k [L^-1]. Defaults to those + given at construction, or to 0 (conservative) for omitted sites. + regularization_strength: lambda, weighting the squared penalty on the variance of + the model. Required if the problem was built with `use_regularization=True`. + Not comparable to the equivalent argument of `SampleNetworkUnmixer.solve`. + data_covariance: Uncertainty on the observations, enabling closed-form error + propagation. A bare number is read as a relative error in percent; see + `_coerce_data_covariance` for the other accepted forms. If None, the covariance + fields of the result are left as None. + weighted: Whether to weight the misfit by the inverse data covariance, i.e. to + solve generalised rather than ordinary least squares. Has an effect only when + `data_covariance` is supplied *and* `regularization_strength` is non-zero -- + see `_build_estimator` for why weighting cannot change an unregularized + solution. Strongly recommended whenever the observations span a wide range and + the errors are proportional, which makes them badly heteroscedastic. + solver: CVXPY solver to use (default "clarabel"). + compute_resolution: Whether to compute the resolution matrix and effective degrees + of freedom. Costs one O(n^3) matrix product; set False for large networks. + warm_start: Seed the solver from the previous solution. + + Returns: + A :class:`LinearFunmixerSolution`. + + Raises: + Exception: If regularization is enabled but no strength was supplied. + ValueError: If the observations do not match the sites in the network. + + Note: + When no site is clamped by the `c >= 0` constraint, the returned point estimate is + the analytical estimator `R d` exactly, not the solver's approximation to it. This + guarantees that the estimate and the propagated covariances come from the same + linear operator, which is what makes the error propagation meaningful. When sites + *are* clamped the estimator is no longer linear; the covariance is still reported + (it assumes the unconstrained estimator) but a warning is issued. + """ + if self._use_regularization and regularization_strength is None: + raise Exception("WARNING: Regularizer terms present but no strength assigned.") + if solver not in SOLVERS: + raise Exception( + f"Solver {solver} not supported. Supported solvers are {list(SOLVERS.keys())}" + ) + + lam = float(regularization_strength) if self._use_regularization else 0.0 + if lam < 0: + raise ValueError("regularization_strength must be non-negative.") + if rate_constants is None: + rate_constants = self._rate_constants + + start_time = time.time() + + d = self._observations_to_array(observation_data) + d_bar = float(np.mean(d)) + if d_bar <= 0: + raise ValueError("Mean observed concentration must be positive.") + d_norm = d / d_bar + + M, total_flux, own_flux = self._build_mixing_matrix(export_rates, rate_constants) + + # Weighting. The covariance is scaled into normalised space so that the whitened + # residuals are dimensionless, which in turn keeps lambda dimensionless: the objective + # trades a chi-squared-like misfit against the variance of the model. + C_d = self._coerce_data_covariance(data_covariance, d) + is_weighted = weighted and C_d is not None + whitener: Optional[FloatArray] = None + if is_weighted: + assert C_d is not None + whitener = self._whitener(C_d / d_bar**2) + + # The unconstrained estimate. At lambda = 0 the sparse closed form is both exact and + # O(n); otherwise we go via R, which we need for the covariance anyway. + need_estimator = (C_d is not None) or compute_resolution or lam > 0.0 + R: Optional[FloatArray] = ( + self._build_estimator(M, lam, whitener) if need_estimator else None + ) + if lam == 0.0: + c_unconstrained = self._solve_unregularized( + d_norm, total_flux, own_flux, rate_constants + ) + else: + assert R is not None + c_unconstrained = R @ d_norm + + # Always solve via CVXPY: it supplies the solver statistics, and it is the only route + # to a correct answer when the c >= 0 constraint is active. The whitened system is + # handed to it so that it minimises the same objective as the analytical estimator. + M_solver = M if whitener is None else whitener @ M + d_solver = d_norm if whitener is None else whitener @ d_norm + self._ensure_problem(M_solver) + self._d_param.value = d_solver + self._lam_param.value = lam + objective_value = self._problem.solve(**SOLVERS[solver], warm_start=warm_start) + if self._problem.status != "optimal": + warnings.warn( + f"CVXPY returned status '{self._problem.status}'.", RuntimeWarning, stacklevel=2 + ) + + clamped_mask = c_unconstrained < CLAMP_TOLERANCE + clamped_nodes = [name for name in self.node_order if clamped_mask[self._index[name]]] + + if not clamped_nodes: + # The objective is strictly convex, so `c_unconstrained` is *the* unconstrained + # global minimiser. It is also feasible, hence a fortiori the constrained optimum. + # Return it exactly rather than the solver's approximation, so that the point + # estimate and the propagated covariance share one estimator. + c_norm = c_unconstrained + solver_c = np.asarray(self._c_var.value, dtype=np.float64) + scale = max(float(np.max(np.abs(c_norm))), 1.0) + discrepancy = float(np.max(np.abs(c_norm - solver_c))) / scale + if discrepancy > ESTIMATOR_AGREEMENT_TOLERANCE: + warnings.warn( + f"Analytical and CVXPY solutions differ by {discrepancy:.3e} (relative) " + "despite no active constraints. The mixing matrix is probably badly " + f"conditioned (cond = {np.linalg.cond(M):.3e}), which amplifies solver " + "tolerance; treat the recovered concentrations with caution.", + RuntimeWarning, + stacklevel=2, + ) + else: + c_norm = np.asarray(self._c_var.value, dtype=np.float64) + warnings.warn( + f"{len(clamped_nodes)} site(s) clamped at the lower bound " + f"({clamped_nodes if len(clamped_nodes) <= 10 else clamped_nodes[:10] + ['...']}). " + "The constrained estimator is not linear in the data, so any propagated " + "covariance assumes the unconstrained estimator and will overstate uncertainty " + "at these sites.", + UserWarning, + stacklevel=2, + ) + + d_pred_norm = M @ c_norm + residual_norm = float(np.linalg.norm(d_pred_norm - d_norm)) + + # Error propagation. The estimator maps observations to concentrations in *any* + # consistent scaling, so the mean-normalisation cancels exactly and we can work + # directly in the input units: C_c = R C_d R^T, C_dpred = M C_c M^T. + upstream_covariance: Optional[FloatArray] = None + upstream_std: Optional[ElementData] = None + downstream_covariance: Optional[FloatArray] = None + downstream_std: Optional[ElementData] = None + upstream_posterior_covariance: Optional[FloatArray] = None + upstream_posterior_std: Optional[ElementData] = None + if C_d is not None: + assert R is not None + upstream_covariance = _symmetrise(R @ C_d @ R.T) + downstream_covariance = _symmetrise(M @ upstream_covariance @ M.T) + upstream_std = self._to_dict(np.sqrt(np.clip(np.diag(upstream_covariance), 0, None))) + downstream_std = self._to_dict( + np.sqrt(np.clip(np.diag(downstream_covariance), 0, None)) + ) + if is_weighted: + # (M^T C_d^-1 M + lambda P)^-1, built from the whitened operator so it needs + # no explicit inverse of C_d. M_solver is in normalised space, so the result + # is scaled back into the input units by d_bar^2. + normal_matrix = M_solver.T @ M_solver + lam * self._centering_projector() + upstream_posterior_covariance = _symmetrise( + np.linalg.solve(normal_matrix, np.eye(self._n)) * d_bar**2 + ) + upstream_posterior_std = self._to_dict( + np.sqrt(np.clip(np.diag(upstream_posterior_covariance), 0, None)) + ) + + resolution_matrix: Optional[FloatArray] = None + effective_dof: Optional[float] = None + if compute_resolution: + assert R is not None + resolution_matrix = R @ M + effective_dof = float(np.trace(resolution_matrix)) + + # Cache for get_misfit / get_roughness / get_estimator. + self._M, self._R, self._c_norm, self._d_norm = M, R, c_norm, d_norm + self._M_solver, self._d_solver, self._weighted = M_solver, d_solver, is_weighted + + return LinearFunmixerSolution( + objective_value=float(objective_value), + solver_name=self._problem.solver_stats.solver_name, + total_time=time.time() - start_time, + solve_time=self._problem.solver_stats.solve_time, + setup_time=self._problem.solver_stats.setup_time, + downstream_preds=self._to_dict(d_pred_norm * d_bar), + upstream_preds=self._to_dict(c_norm * d_bar), + node_order=list(self.node_order), + regularization_strength=lam, + weighted=is_weighted, + unconstrained_preds=self._to_dict(c_unconstrained * d_bar), + clamped_nodes=clamped_nodes, + amplification=self._to_dict(total_flux / own_flux), + condition_number=float(np.linalg.cond(M)), + residual_norm=residual_norm, + upstream_covariance=upstream_covariance, + upstream_std=upstream_std, + upstream_posterior_covariance=upstream_posterior_covariance, + upstream_posterior_std=upstream_posterior_std, + downstream_covariance=downstream_covariance, + downstream_std=downstream_std, + resolution_matrix=resolution_matrix, + effective_dof=effective_dof, + ) + + # ------------------------------------------------------------- introspection + + def get_misfit(self) -> float: + """ + The data misfit of the most recent solve, in normalised space. + + This is the quantity the objective actually minimises: `||M c - d||_2` for an + unweighted solve, and the Mahalanobis norm `sqrt((Mc-d)^T C_d^-1 (Mc-d))` for a + weighted one. The latter is a chi distance, so a well-fitting weighted solve gives + roughly `sqrt(n)`. + + Together with `get_roughness`, this makes the class compatible with + :func:`~funmixer.network_unmixer.plot_sweep_of_regularizer_strength`. + """ + if self._M_solver is None or self._c_norm is None or self._d_solver is None: + raise Exception("No solution available: call solve() first.") + return float(np.linalg.norm(self._M_solver @ self._c_norm - self._d_solver)) + + def get_roughness(self) -> float: + """ + The model roughness `||P c||_2` of the most recent solve, in normalised space. + + This is the total deviation of the recovered concentrations from their own mean, the + same quantity the regularization penalises. + """ + if self._c_norm is None: + raise Exception("No solution available: call solve() first.") + return float(np.linalg.norm(self._c_norm - np.mean(self._c_norm))) + + def get_mixing_matrix(self) -> Tuple[FloatArray, List[str]]: + """Return the mixing matrix `M` of the most recent solve, and its node ordering.""" + if self._M is None: + raise Exception("No solution available: call solve() first.") + return self._M, list(self.node_order) + + def get_estimator(self) -> Tuple[FloatArray, List[str]]: + """ + Return the linear estimator `R` of the most recent solve, and its node ordering. + + `c = R d` holds in both normalised and input units. Users wanting to propagate their + own uncertainties can use `C_c = R C_d R^T` and `C_dpred = M C_c M^T` directly. + """ + if self._R is None: + raise Exception( + "No estimator available: call solve() first, with data_covariance set or " + "compute_resolution=True." + ) + return self._R, list(self.node_order) + + +def _symmetrise(matrix: FloatArray) -> FloatArray: + """Remove the antisymmetric round-off that accumulates in triple matrix products.""" + return 0.5 * (matrix + matrix.T) diff --git a/funmixer/network_unmixer.py b/funmixer/network_unmixer.py index 224b981..9e57589 100644 --- a/funmixer/network_unmixer.py +++ b/funmixer/network_unmixer.py @@ -237,8 +237,12 @@ def nx_get_downstream_data(G: nx.DiGraph, x: str) -> Optional[SampleNode]: Exception: If there is more than one downstream neighbor. """ s = nx_get_downstream_node(G, x) - if s: + # NB: an explicit `is not None` check, not a truthiness test. Node names may be integers, + # and a node legitimately named `0` would otherwise be reported as having no downstream + # neighbour -- silently disconnecting the outlet of any integer-labelled network. + if s is not None: return cast(SampleNode, G.nodes[s]["data"]) + return None def plot_network(G: nx.DiGraph) -> None: diff --git a/tests/linear_unmixer_test.py b/tests/linear_unmixer_test.py new file mode 100644 index 0000000..9c33088 --- /dev/null +++ b/tests/linear_unmixer_test.py @@ -0,0 +1,705 @@ +# pyre-ignore-all-errors[56] +""" +Tests for the linear (least-squares) unmixing solver. + +The network generators mirror those in `random_networks_test.py`. Tolerances here are far +tighter than for the non-linear solver because, at lambda = 0, the inversion is exact rather +than iterative. +""" + +import warnings +from typing import Callable, Optional + +import networkx as nx +import numpy as np +import pytest +from hypothesis import given, settings +from hypothesis import strategies as st + +import funmixer +from funmixer.linear_unmixer import LinearSampleNetworkUnmixer + +MINIMUM_CONC = 1.0 +MAXIMUM_CONC = 1e2 +MINIMUM_AREA = 1.0 +MAXIMUM_AREA = 1e2 +MAX_RATE_PARAMETER = 3.0 + +# The inversion is exact, so we can demand far more than the 1% used for the non-linear solver. +EXACT_TOLERANCE = 1e-8 + + +def draw_random_log_uniform(min_val: float, max_val: float) -> float: + return float(np.exp(np.random.uniform(np.log(min_val), np.log(max_val), 1))[0]) + + +def generate_balanced_sample_network( + branching_factor: int, height: int, areas: Callable[[], float] +) -> nx.DiGraph: + """A balanced tree of sample sites, with flow directed towards the root.""" + G = nx.balanced_tree(branching_factor, height, create_using=nx.DiGraph) + G = nx.reverse(G) + for u, v in G.edges: + G[u][v]["length"] = 1.0 + for node in G.nodes: + G.nodes[node]["data"] = funmixer.SampleNode( + name=node, + area=areas(), + downstream_node=funmixer.nx_get_downstream_node(G, node), + x=-1, + y=-1, + total_upstream_area=0, + label=0, + upstream_nodes=[], + distance_downstream=1.0, + ) + return G + + +def default_network(branching_factor: int = 2, height: int = 3, seed: int = 0) -> nx.DiGraph: + np.random.seed(seed) + return generate_balanced_sample_network( + branching_factor, height, lambda: draw_random_log_uniform(MINIMUM_AREA, MAXIMUM_AREA) + ) + + +def random_concentrations(network: nx.DiGraph) -> funmixer.ElementData: + return {node: draw_random_log_uniform(MINIMUM_CONC, MAXIMUM_CONC) for node in network.nodes} + + +# --------------------------------------------------------------- forward operator + + +@given( + branching_factor=st.integers(min_value=1, max_value=4), + height=st.integers(min_value=1, max_value=4), + rate_constant=st.floats(min_value=0.0, max_value=MAX_RATE_PARAMETER), + conservative=st.booleans(), + use_export_rates=st.booleans(), +) +@settings(deadline=None, max_examples=25) +def test_mixing_matrix_matches_forward_model( + branching_factor: int, + height: int, + rate_constant: float, + conservative: bool, + use_export_rates: bool, +) -> None: + """`M @ c` must reproduce `forward_model` exactly: it is the same sweep, vectorised.""" + network = default_network(branching_factor, height) + rate_constants = None if conservative else {n: rate_constant for n in network.nodes} + export_rates = ( + {n: draw_random_log_uniform(0.1, 10.0) for n in network.nodes} if use_export_rates else None + ) + + problem = LinearSampleNetworkUnmixer(network, use_regularization=False) + M, node_order = ( + problem._build_mixing_matrix(export_rates, rate_constants)[0], + problem.node_order, + ) + + upstream = random_concentrations(network) + expected = funmixer.forward_model( + sample_network=network, + upstream_concentrations=upstream, + export_rates=export_rates, + rate_constants=rate_constants, + ) + + c = np.array([upstream[n] for n in node_order]) + predicted = M @ c + for i, node in enumerate(node_order): + assert np.isclose(predicted[i], expected[node], rtol=1e-12) + + +@given( + branching_factor=st.integers(min_value=1, max_value=4), + height=st.integers(min_value=1, max_value=4), + conservative=st.booleans(), +) +@settings(deadline=None, max_examples=25) +def test_mixing_matrix_structure(branching_factor: int, height: int, conservative: bool) -> None: + """M is lower triangular in topological order, with a positive diagonal.""" + network = default_network(branching_factor, height) + rate_constants = None if conservative else {n: 1.0 for n in network.nodes} + problem = LinearSampleNetworkUnmixer(network, use_regularization=False) + M, _, _ = problem._build_mixing_matrix(None, rate_constants) + + assert np.allclose(M, np.tril(M)), "M must be lower triangular in topological order" + assert np.all(np.diag(M) > 0), "M must have a strictly positive diagonal" + if conservative: + # Conservative mixing weights are a convex combination, so rows sum to one. + assert np.allclose(M.sum(axis=1), 1.0) + else: + # Decay removes tracer from the numerator only, so rows sum to less than one. + assert np.all(M.sum(axis=1) <= 1.0 + 1e-12) + + +def test_closed_form_inverse_matches_dense_inverse() -> None: + """The sparse O(n) closed form must agree with an explicit dense inverse.""" + network = default_network(branching_factor=3, height=3) + rate_constants = {n: 0.7 for n in network.nodes} + problem = LinearSampleNetworkUnmixer(network, use_regularization=False) + M, total_flux, own_flux = problem._build_mixing_matrix(None, rate_constants) + + rng = np.random.default_rng(0) + d = rng.uniform(1.0, 10.0, size=M.shape[0]) + + closed_form = problem._solve_unregularized(d, total_flux, own_flux, rate_constants) + assert np.allclose(closed_form, np.linalg.solve(M, d), rtol=1e-10) + assert np.allclose(problem._build_estimator(M, 0.0, None), np.linalg.inv(M), rtol=1e-10) + + +# ------------------------------------------------------------------- round trip + + +@given( + branching_factor=st.integers(min_value=1, max_value=4), + height=st.integers(min_value=1, max_value=4), + rate_constant=st.floats(min_value=0.0, max_value=MAX_RATE_PARAMETER), + conservative=st.booleans(), +) +@settings(deadline=None, max_examples=25) +def test_exact_round_trip( + branching_factor: int, height: int, rate_constant: float, conservative: bool +) -> None: + """forward_model -> solve must recover the source concentrations exactly at lambda = 0.""" + network = default_network(branching_factor, height) + rate_constants = None if conservative else {n: rate_constant for n in network.nodes} + upstream = random_concentrations(network) + downstream = funmixer.forward_model( + sample_network=network, upstream_concentrations=upstream, rate_constants=rate_constants + ) + + problem = LinearSampleNetworkUnmixer( + network, use_regularization=False, rate_constants=rate_constants + ) + solution = problem.solve(downstream) + + assert solution.clamped_nodes == [] + assert solution.residual_norm < 1e-10 + for node in network.nodes: + assert np.isclose(solution.upstream_preds[node], upstream[node], rtol=EXACT_TOLERANCE) + assert np.isclose(solution.downstream_preds[node], downstream[node], rtol=EXACT_TOLERANCE) + + +def test_returned_estimate_is_the_analytical_one() -> None: + """ + On interior problems the returned estimate must be the analytical estimator exactly, and + CVXPY must independently agree with it. This is what makes the covariance meaningful. + """ + network = default_network(branching_factor=2, height=3) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + + for lam, use_reg in [(None, False), (1e-3, True), (1.0, True)]: + problem = LinearSampleNetworkUnmixer(network, use_regularization=use_reg) + solution = problem.solve(downstream, regularization_strength=lam) + assert solution.clamped_nodes == [] + # `unconstrained_preds` is the analytical R @ d; the returned estimate must equal it. + for node in network.nodes: + assert solution.upstream_preds[node] == solution.unconstrained_preds[node] + # And CVXPY, solving the same problem numerically, must land in the same place. + R, node_order = problem.get_estimator() + d = np.array([downstream[n] for n in node_order]) + assert np.allclose(R @ d, [solution.upstream_preds[n] for n in node_order], rtol=1e-6) + + +# ---------------------------------------------------------------- regularization + + +def test_regularized_solve_matches_augmented_least_squares() -> None: + """ + The Cholesky/normal-equations path must agree with the numerically safer augmented + least-squares form, which never forms M^T M. + """ + network = default_network(branching_factor=3, height=2) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + + problem = LinearSampleNetworkUnmixer(network, use_regularization=True) + for lam in [1e-4, 1e-2, 1.0, 10.0]: + solution = problem.solve(downstream, regularization_strength=lam) + M, node_order = problem.get_mixing_matrix() + n = len(node_order) + d = np.array([downstream[node] for node in node_order]) + P = np.eye(n) - np.ones((n, n)) / n + + augmented_operator = np.vstack([M, np.sqrt(lam) * P]) + augmented_data = np.concatenate([d, np.zeros(n)]) + expected, *_ = np.linalg.lstsq(augmented_operator, augmented_data, rcond=None) + + got = np.array([solution.upstream_preds[node] for node in node_order]) + assert np.allclose(got, expected, rtol=1e-7) + + +def test_small_lambda_reproduces_unregularized_solution() -> None: + network = default_network(branching_factor=2, height=3) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + + unregularized = LinearSampleNetworkUnmixer(network, use_regularization=False).solve(downstream) + barely = LinearSampleNetworkUnmixer(network, use_regularization=True).solve( + downstream, regularization_strength=1e-12 + ) + for node in network.nodes: + assert np.isclose( + barely.upstream_preds[node], unregularized.upstream_preds[node], rtol=1e-5 + ) + + +def test_large_lambda_drives_model_to_a_constant() -> None: + """ + As lambda grows the variance penalty dominates and every c_i must converge to a common + value. For a conservative network M is row-stochastic, so that value is the mean + observation. + """ + network = default_network(branching_factor=2, height=3) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + + problem = LinearSampleNetworkUnmixer(network, use_regularization=True) + solution = problem.solve(downstream, regularization_strength=1e10) + + values = np.array(list(solution.upstream_preds.values())) + assert np.allclose(values, values[0], rtol=1e-4), "all c_i should collapse to one value" + assert np.isclose(values[0], np.mean(list(downstream.values())), rtol=1e-4) + + +def test_effective_dof_decreases_with_lambda() -> None: + """Resolution degrades monotonically with lambda, starting from the identity at lambda=0.""" + network = default_network(branching_factor=2, height=3) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + n = network.number_of_nodes() + + unregularized = LinearSampleNetworkUnmixer(network, use_regularization=False).solve(downstream) + assert np.allclose(unregularized.resolution_matrix, np.eye(n), atol=1e-8) + assert np.isclose(unregularized.effective_dof, n) + + problem = LinearSampleNetworkUnmixer(network, use_regularization=True) + dofs = [ + problem.solve(downstream, regularization_strength=lam).effective_dof + for lam in [1e-6, 1e-3, 1e-1, 1.0, 1e2] + ] + assert all(a > b for a, b in zip(dofs, dofs[1:])), f"not monotonically decreasing: {dofs}" + # The penalty has a one-dimensional null space (constant vectors), which is never damped. + assert dofs[-1] > 1.0 + + +# --------------------------------------------------------------------- weighting + + +def test_weighting_cannot_change_an_unregularized_solution() -> None: + """ + At lambda = 0 the fit is exact and passes through every observation, so there is nothing + for a weighting to trade off: (M^T W M)^-1 M^T W = M^-1 for any invertible W. This is a + mathematical identity, not an approximation, and is worth pinning down. + """ + network = default_network(branching_factor=2, height=3) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + problem = LinearSampleNetworkUnmixer(network, use_regularization=False) + + # A deliberately extreme weighting: five orders of magnitude between the best- and + # worst-known sites. + rng = np.random.default_rng(3) + sigmas = {node: 10 ** rng.uniform(-2, 3) for node in network.nodes} + + unweighted = problem.solve(downstream, data_covariance=sigmas, weighted=False) + weighted = problem.solve(downstream, data_covariance=sigmas, weighted=True) + for node in network.nodes: + assert np.isclose(weighted.upstream_preds[node], unweighted.upstream_preds[node], rtol=1e-9) + # The covariance is C_c = R C_d R^T with the same R, so it too must be unchanged. + assert np.allclose(weighted.upstream_covariance, unweighted.upstream_covariance, rtol=1e-9) + + +def test_weighting_changes_a_regularized_solution() -> None: + """Once lambda > 0 the weighting decides which observations the penalty is allowed to + sacrifice, so it must change the answer.""" + network = default_network(branching_factor=2, height=3) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + problem = LinearSampleNetworkUnmixer(network, use_regularization=True) + + unweighted = problem.solve( + downstream, regularization_strength=0.1, data_covariance=10.0, weighted=False + ) + weighted = problem.solve( + downstream, regularization_strength=0.1, data_covariance=10.0, weighted=True + ) + assert weighted.weighted and not unweighted.weighted + difference = max( + abs(weighted.upstream_preds[n] - unweighted.upstream_preds[n]) for n in network.nodes + ) + scale = max(abs(v) for v in unweighted.upstream_preds.values()) + assert difference / scale > 1e-3, "weighting should materially change the solution" + + +def test_weighted_estimator_matches_explicit_gls_formula() -> None: + """The whitening transform must reproduce the textbook GLS normal equations.""" + network = default_network(branching_factor=3, height=2) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + problem = LinearSampleNetworkUnmixer(network, use_regularization=True) + + rng = np.random.default_rng(11) + sigmas = {node: 10 ** rng.uniform(-1, 2) for node in network.nodes} + lam = 0.37 + solution = problem.solve( + downstream, regularization_strength=lam, data_covariance=sigmas, weighted=True + ) + + M, node_order = problem.get_mixing_matrix() + n = len(node_order) + d = np.array([downstream[node] for node in node_order]) + d_bar = float(np.mean(d)) + # The estimator is built in normalised space, so the covariance must be scaled to match. + C_d = np.diag(np.array([sigmas[node] for node in node_order]) ** 2) / d_bar**2 + W = np.linalg.inv(C_d) + P = np.eye(n) - np.ones((n, n)) / n + + expected_R = np.linalg.solve(M.T @ W @ M + lam * P, M.T @ W) + got_R, _ = problem.get_estimator() + assert np.allclose(got_R, expected_R, rtol=1e-8) + assert np.allclose( + [solution.upstream_preds[node] for node in node_order], expected_R @ d, rtol=1e-7 + ) + + +def test_unregularized_weighted_covariance_is_the_cramer_rao_bound() -> None: + """ + At lambda = 0 the estimator is `M^-1`, so `C_c = M^-1 C_d M^-T = (M^T C_d^-1 M)^-1` -- the + inverse Fisher information. The unregularized inversion is therefore an efficient + estimator, attaining the Cramer-Rao lower bound, and no weighting can improve on it. + """ + network = default_network(branching_factor=3, height=2) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + problem = LinearSampleNetworkUnmixer(network, use_regularization=False) + + rng = np.random.default_rng(2) + sigmas = {node: 10 ** rng.uniform(-1, 2) for node in network.nodes} + solution = problem.solve(downstream, data_covariance=sigmas) + + M, node_order = problem.get_mixing_matrix() + C_d = np.diag(np.array([sigmas[node] for node in node_order]) ** 2) + cramer_rao = np.linalg.inv(M.T @ np.linalg.inv(C_d) @ M) + assert np.allclose(solution.upstream_covariance, cramer_rao, rtol=1e-8) + + +def test_posterior_and_propagated_covariance_agree_at_zero_lambda() -> None: + """At lambda = 0 the penalty contributes nothing, so both covariances must coincide.""" + network = default_network(branching_factor=3, height=2) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + problem = LinearSampleNetworkUnmixer(network, use_regularization=True) + solution = problem.solve(downstream, regularization_strength=0.0, data_covariance=7.0) + assert np.allclose( + solution.upstream_posterior_covariance, solution.upstream_covariance, rtol=1e-7 + ) + + +def test_posterior_covariance_exceeds_propagated_when_regularized() -> None: + """ + The propagated covariance measures only how the estimate scatters; the posterior also + counts what the damping costs. They differ by exactly `lambda A^-1 P A^-1`, so the + posterior is always the larger -- and quoting the propagated one alone at large lambda + understates the error. + """ + network = default_network(branching_factor=3, height=2) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + problem = LinearSampleNetworkUnmixer(network, use_regularization=True) + + previous_ratio = 1.0 + for lam in [1e-3, 1e-1, 1.0, 10.0]: + solution = problem.solve( + downstream, regularization_strength=lam, data_covariance=7.0, weighted=True + ) + propagated = np.diag(solution.upstream_covariance) + posterior = np.diag(solution.upstream_posterior_covariance) + assert np.all(posterior >= propagated - 1e-12) + + # Check the exact identity relating the two. + M, node_order = problem.get_mixing_matrix() + n = len(node_order) + d_bar = float(np.mean([downstream[node] for node in node_order])) + C_d = np.diag(np.array([downstream[node] for node in node_order]) * 0.07) ** 2 + A = M.T @ np.linalg.inv(C_d / d_bar**2) @ M + lam * (np.eye(n) - np.ones((n, n)) / n) + A_inv = np.linalg.inv(A) + expected = (A_inv - lam * A_inv @ (np.eye(n) - np.ones((n, n)) / n) @ A_inv) * d_bar**2 + assert np.allclose(solution.upstream_covariance, expected, rtol=1e-6) + + # The gap widens with lambda: damping buys stability at the cost of accuracy. + ratio = float(np.mean(np.sqrt(posterior / propagated))) + assert ratio >= previous_ratio - 1e-9 + previous_ratio = ratio + assert previous_ratio > 1.5, "the two covariances should diverge substantially by lambda=10" + + +def test_posterior_covariance_absent_when_unweighted() -> None: + """The posterior form needs C_d^-1, so it is only defined for a weighted solve.""" + network = default_network(branching_factor=2, height=2) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + problem = LinearSampleNetworkUnmixer(network, use_regularization=True) + solution = problem.solve( + downstream, regularization_strength=0.1, data_covariance=5.0, weighted=False + ) + assert solution.upstream_covariance is not None + assert solution.upstream_posterior_covariance is None + + +def test_weighted_misfit_is_a_chi_distance() -> None: + """With a correct error model, a weighted solve at small lambda should give a misfit of + order sqrt(n) -- one standard deviation per observation.""" + network = default_network(branching_factor=2, height=4) + n = network.number_of_nodes() + upstream = random_concentrations(network) + truth = funmixer.forward_model(network, upstream) + + rng = np.random.default_rng(5) + relative_error = 0.1 + noisy = {node: value * (1 + relative_error * rng.normal()) for node, value in truth.items()} + sigmas = {node: relative_error * value for node, value in noisy.items()} + + problem = LinearSampleNetworkUnmixer(network, use_regularization=True) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + problem.solve(noisy, regularization_strength=1e-6, data_covariance=sigmas) + # Essentially unregularized, so the fit is near-exact and the chi distance near zero. + assert problem.get_misfit() < np.sqrt(n) + + +# ------------------------------------------------------------ error propagation + + +@pytest.mark.parametrize("lam, use_reg", [(None, False), (1e-2, True)]) +def test_covariance_matches_monte_carlo(lam: Optional[float], use_reg: bool) -> None: + """ + The load-bearing test: the closed-form covariance must reproduce what you get by actually + perturbing the data and re-solving. + + The network and data are chosen so the solution stays comfortably interior, so that the + estimator really is linear and the analytical result is exact. + """ + np.random.seed(7) + network = generate_balanced_sample_network(2, 2, lambda: 1.0) + # A near-uniform source field keeps the differenced solution well away from zero. + upstream = {node: 100.0 + np.random.uniform(-5, 5) for node in network.nodes} + downstream = funmixer.forward_model(network, upstream) + + relative_error = 2.0 # percent + problem = LinearSampleNetworkUnmixer(network, use_regularization=use_reg) + solution = problem.solve( + downstream, regularization_strength=lam, data_covariance=relative_error + ) + assert solution.clamped_nodes == [] + + R, node_order = problem.get_estimator() + M, _ = problem.get_mixing_matrix() + d = np.array([downstream[node] for node in node_order]) + sigma = d * relative_error / 100.0 + + rng = np.random.default_rng(42) + draws = 20000 + perturbed = d[None, :] + rng.normal(0.0, sigma, size=(draws, len(d))) + sampled_c = perturbed @ R.T + sampled_d = sampled_c @ M.T + + empirical_c = np.cov(sampled_c, rowvar=False) + empirical_d = np.cov(sampled_d, rowvar=False) + + # Compare standard deviations: with 20k draws the standard error on a std is ~0.5%. + assert np.allclose( + np.sqrt(np.diag(empirical_c)), np.sqrt(np.diag(solution.upstream_covariance)), rtol=0.05 + ) + assert np.allclose( + np.sqrt(np.diag(empirical_d)), + np.sqrt(np.diag(solution.downstream_covariance)), + rtol=0.05, + ) + # And the full matrices, scaled by the diagonal to make the comparison dimensionless. + scale = np.outer( + np.sqrt(np.diag(solution.upstream_covariance)), + np.sqrt(np.diag(solution.upstream_covariance)), + ) + assert np.allclose(empirical_c / scale, solution.upstream_covariance / scale, atol=0.05) + + +def test_unregularized_downstream_covariance_equals_data_covariance() -> None: + """ + At lambda = 0 the fit is exact, so the modelled observations are the observations and their + covariance must be the input covariance unchanged. + """ + network = default_network(branching_factor=2, height=3) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + + problem = LinearSampleNetworkUnmixer(network, use_regularization=False) + solution = problem.solve(downstream, data_covariance=5.0) + + d = np.array([downstream[node] for node in solution.node_order]) + expected = np.diag((d * 5.0 / 100.0) ** 2) + assert np.allclose(solution.downstream_covariance, expected, rtol=1e-6, atol=1e-12) + + +def test_covariance_input_forms_agree() -> None: + """A scalar relative error, a dict of sigmas and an explicit matrix must agree.""" + network = default_network(branching_factor=2, height=2) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + problem = LinearSampleNetworkUnmixer(network, use_regularization=False) + + scalar = problem.solve(downstream, data_covariance=10.0) + sigmas = {node: value * 0.1 for node, value in downstream.items()} + as_dict = problem.solve(downstream, data_covariance=sigmas) + as_array = problem.solve( + downstream, + data_covariance=np.array([sigmas[node] for node in problem.node_order]), + ) + as_matrix = problem.solve( + downstream, + data_covariance=np.diag(np.array([sigmas[node] for node in problem.node_order]) ** 2), + ) + for other in [as_dict, as_array, as_matrix]: + assert np.allclose(scalar.upstream_covariance, other.upstream_covariance, rtol=1e-10) + + +def test_covariance_scales_quadratically_with_units() -> None: + """ + The problem is homogeneous, so rescaling the observations must rescale concentrations + linearly and covariances quadratically. This checks the mean-normalisation cancels exactly. + """ + network = default_network(branching_factor=2, height=3) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + problem = LinearSampleNetworkUnmixer(network, use_regularization=False) + + base = problem.solve(downstream, data_covariance=10.0) + factor = 1e4 # e.g. converting fractions to mg/kg + scaled = problem.solve( + {node: value * factor for node, value in downstream.items()}, data_covariance=10.0 + ) + for node in network.nodes: + assert np.isclose( + scaled.upstream_preds[node], base.upstream_preds[node] * factor, rtol=1e-9 + ) + assert np.allclose( + scaled.upstream_covariance, base.upstream_covariance * factor**2, rtol=1e-9 + ) + + +# ------------------------------------------------------------------ constraints + + +def test_clamping_warns_and_respects_the_lower_bound() -> None: + """ + Data that violate the mixing model -- a downstream site carrying less tracer flux than its + tributaries deliver -- must clamp at zero, warn, and still return a feasible solution. + """ + network = generate_balanced_sample_network(2, 1, lambda: 1.0) + node_order = [ + name for name, _ in funmixer.network_unmixer.nx_topological_sort_with_data(network) + ] + # Leaves are rich, the outlet is poor: impossible under conservative mixing. + root = node_order[-1] + observations = {node: (100.0 if node != root else 1.0) for node in network.nodes} + + problem = LinearSampleNetworkUnmixer(network, use_regularization=False) + with pytest.warns(UserWarning, match="clamped at the lower bound"): + solution = problem.solve(observations) + + assert solution.clamped_nodes, "expected at least one site to clamp" + assert all(value >= -1e-8 for value in solution.upstream_preds.values()) + assert min(solution.unconstrained_preds.values()) < 0, "the raw estimate should go negative" + assert solution.residual_norm > 0, "a clamped fit cannot be exact" + + +def test_clamping_warning_mentions_covariance_caveat() -> None: + network = generate_balanced_sample_network(2, 1, lambda: 1.0) + node_order = [ + name for name, _ in funmixer.network_unmixer.nx_topological_sort_with_data(network) + ] + observations = {node: (100.0 if node != node_order[-1] else 1.0) for node in network.nodes} + problem = LinearSampleNetworkUnmixer(network, use_regularization=False) + with pytest.warns(UserWarning, match="overstate uncertainty"): + problem.solve(observations, data_covariance=5.0) + + +# ------------------------------------------------------------------- diagnostics + + +def test_amplification_is_total_over_own_flux() -> None: + """Equal-area sub-basins in a balanced binary tree give amplification = subtree size.""" + network = generate_balanced_sample_network(2, 2, lambda: 1.0) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + solution = LinearSampleNetworkUnmixer(network, use_regularization=False).solve(downstream) + + for node in network.nodes: + subtree_size = len(nx.ancestors(network, node)) + 1 + assert np.isclose(solution.amplification[node], subtree_size, rtol=1e-10) + + +def test_misfit_and_roughness_are_available_for_the_l_curve() -> None: + """ + `plot_sweep_of_regularizer_strength` is duck-typed on solve/get_misfit/get_roughness, so + those three must work together on this class without any change to that helper. + """ + network = default_network(branching_factor=2, height=3) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + problem = LinearSampleNetworkUnmixer(network, use_regularization=True) + + misfits, roughnesses = [], [] + for lam in np.logspace(-4, 2, 7): + problem.solve(downstream, regularization_strength=lam) + misfits.append(problem.get_misfit()) + roughnesses.append(problem.get_roughness()) + + # The classic L-curve trade-off: misfit rises, roughness falls, both monotonically. + assert all(a <= b + 1e-9 for a, b in zip(misfits, misfits[1:])), misfits + assert all(a >= b - 1e-9 for a, b in zip(roughnesses, roughnesses[1:])), roughnesses + + +def test_requires_regularization_strength_when_enabled() -> None: + network = default_network(branching_factor=2, height=2) + problem = LinearSampleNetworkUnmixer(network, use_regularization=True) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + with pytest.raises(Exception, match="no strength assigned"): + problem.solve(downstream) + + +def test_rejects_mismatched_observations() -> None: + network = default_network(branching_factor=2, height=2) + problem = LinearSampleNetworkUnmixer(network, use_regularization=False) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + incomplete = {k: v for k, v in list(downstream.items())[:-1]} + with pytest.raises(ValueError, match="No observation supplied"): + problem.solve(incomplete) + + +def test_does_not_disturb_a_live_nonlinear_problem() -> None: + """ + `SampleNode` fields are shared scratch space. Building and solving a linear problem on a + graph must not corrupt a `SampleNetworkUnmixer` built on the same graph. + """ + network = default_network(branching_factor=2, height=2) + upstream = random_concentrations(network) + downstream = funmixer.forward_model(network, upstream) + + nonlinear = funmixer.SampleNetworkUnmixer(network, use_regularization=False) + before = nonlinear.solve(downstream).upstream_preds + + linear = LinearSampleNetworkUnmixer(network, use_regularization=False) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + linear.solve(downstream) + + after = nonlinear.solve(downstream).upstream_preds + for node in network.nodes: + assert np.isclose(before[node], after[node], rtol=1e-6) diff --git a/tests/linear_vs_nonlinear_benchmark.py b/tests/linear_vs_nonlinear_benchmark.py new file mode 100644 index 0000000..1bc286a --- /dev/null +++ b/tests/linear_vs_nonlinear_benchmark.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python3 + +""" +How well do the two solvers recover known sources as the source range widens? + +Setup +----- +A single synthetic network of 100 sample sites, with sub-basin areas varying by only +/-10% +and a uniform export rate, so the inversion is as well conditioned as it realistically gets. +Source concentrations are drawn log-uniformly spanning a controlled number of orders of +magnitude (0.5 up to 6), forward-modelled to observations, and then corrupted with relative +Gaussian noise. The error model is assumed known, so every solver that can use it is +handed `C_d = diag((f d)^2)` for the relative error f. + +Why the range matters +--------------------- +The two solvers differ in what they call misfit. The non-linear solver penalises *relative* +differences, which is scale-free. The linear solver penalises *absolute* differences, which is +not -- unweighted, it will spend all its effort on the largest observations and ignore the +small ones. That should not matter at 0.5 orders of magnitude and should matter enormously at +6. + +Weighting by the inverse data covariance is the fix. With proportional errors, sigma_i = f*d_i, +so the whitened residual is (Mc - d)_i / (f d_i) = (1/f)((Mc)_i/d_i - 1): a *relative* misfit. +The weighted linear solver is therefore the linearisation of the non-linear one, and this +script is largely a test of how far that linearisation stretches. + +Run from the repository root: + python tests/linear_vs_nonlinear_benchmark.py +""" + +import warnings +from dataclasses import dataclass +from typing import Callable, Dict, List, Optional + +# pyre-fixme[21]: Could not find module `matplotlib.pyplot`. +import matplotlib.pyplot as plt +import networkx as nx +import numpy as np + +import funmixer +from funmixer.linear_unmixer import LinearSampleNetworkUnmixer + +N_SITES = 100 +AREA_VARIATION = 0.10 # sub-basin areas drawn uniformly from 1 +/- this fraction +RELATIVE_ERROR = 0.20 # relative error on the observations, assumed known +GEOMETRIC_MEAN_CONC = 1000.0 # mg/kg +ORDERS_OF_MAGNITUDE = [0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] +N_REPEATS = 5 +LAMBDA_GRID = np.logspace(-6, 5, 23) + + +def build_network(n_sites: int, area_variation: float, seed: int) -> nx.DiGraph: + """A random tree of sample sites with near-uniform sub-basin areas, flowing to node 0.""" + rng = np.random.default_rng(seed) + undirected = nx.random_labeled_tree(n_sites, seed=seed) + # Orient every edge towards the root, so edges point downstream. + network = nx.DiGraph((child, parent) for parent, child in nx.bfs_edges(undirected, 0)) + for upstream, downstream in network.edges: + network[upstream][downstream]["length"] = 1.0 + for node in network.nodes: + network.nodes[node]["data"] = funmixer.SampleNode( + name=node, + area=float(rng.uniform(1 - area_variation, 1 + area_variation)), + downstream_node=funmixer.nx_get_downstream_node(network, node), + x=-1, + y=-1, + total_upstream_area=0, + label=0, + upstream_nodes=[], + distance_downstream=1.0, + ) + return network + + +def draw_sources(network: nx.DiGraph, orders: float, seed: int) -> funmixer.ElementData: + """Log-uniform source concentrations spanning `orders` decades about a fixed geometric mean.""" + rng = np.random.default_rng(seed) + half = orders / 2.0 + exponents = rng.uniform(-half, half, size=network.number_of_nodes()) + return { + node: float(GEOMETRIC_MEAN_CONC * 10**exponent) + for node, exponent in zip(network.nodes, exponents) + } + + +@dataclass +class Score: + """How well a set of predictions matches the truth, measured in log space.""" + + median_log_error: float # median |log10(pred/true)|, i.e. typical factor-of error + rms_log_error: float # RMS of the same, so bad sites are not hidden by good ones + interior_median_log_error: float # median over non-leaf sites only + fraction_within_2x: float + n_nonpositive: int + chosen_lambda: float = 0.0 + + @classmethod + def compare( + cls, + predicted: funmixer.ElementData, + truth: funmixer.ElementData, + interior: Optional[np.ndarray] = None, + ) -> "Score": + keys = list(truth) + true_values = np.array([truth[k] for k in keys]) + pred_values = np.array([predicted[k] for k in keys]) + n_nonpositive = int(np.sum(pred_values <= 0)) + # Clamped sites come back as exactly zero, where a log ratio is undefined. Floor them + # far below any plausible source so they register as a large, finite error rather + # than being silently dropped from the average. + floor = 1e-6 * float(np.exp(np.mean(np.log(true_values)))) + log_error = np.abs(np.log10(np.maximum(pred_values, floor) / true_values)) + # A leaf site is recovered exactly (c_i = d_i), so its error is just the data error + # whatever the solver does. Reporting the interior separately stops those sites from + # masking the differences between methods. + interior_errors = log_error if interior is None else log_error[interior] + return cls( + median_log_error=float(np.median(log_error)), + rms_log_error=float(np.sqrt(np.mean(log_error**2))), + interior_median_log_error=float( + np.median(interior_errors) if interior_errors.size else np.nan + ), + fraction_within_2x=float(np.mean(log_error < np.log10(2.0))), + n_nonpositive=n_nonpositive, + ) + + +def best_over_lambda( + solve_for_lambda: Callable[[float], Optional[funmixer.ElementData]], + truth: funmixer.ElementData, + lambdas: np.ndarray, + interior: np.ndarray, +) -> Score: + """ + Score a solver at its *oracle-best* regularization strength. + + Choosing lambda against the known truth is of course cheating, but it is cheating equally + for every solver, and it isolates what we actually want to measure: the best each method + can possibly do, rather than how well some particular lambda-selection heuristic works. + """ + best: Optional[Score] = None + for lam in lambdas: + predictions = solve_for_lambda(float(lam)) + if predictions is None: + continue + score = Score.compare(predictions, truth, interior) + score.chosen_lambda = float(lam) + if best is None or score.rms_log_error < best.rms_log_error: + best = score + assert best is not None, "every lambda failed to solve" + return best + + +def run_trial(orders: float, seed: int) -> Dict[str, Score]: + """Generate one synthetic dataset and invert it every way we know how.""" + network = build_network(N_SITES, AREA_VARIATION, seed) + truth = draw_sources(network, orders, seed) + clean = funmixer.forward_model(network, truth) + + rng = np.random.default_rng(seed + 10_000) + observed = { + node: value * max(1.0 + RELATIVE_ERROR * rng.normal(), 1e-3) + for node, value in clean.items() + } + # The assumed-known error model. + sigmas = {node: RELATIVE_ERROR * value for node, value in observed.items()} + interior = np.array([network.in_degree(node) > 0 for node in truth], dtype=bool) + + scores: Dict[str, Score] = {} + + linear_unreg = LinearSampleNetworkUnmixer(network, use_regularization=False) + scores["linear, lambda=0"] = Score.compare( + linear_unreg.solve(observed, data_covariance=sigmas).upstream_preds, truth, interior + ) + + linear = LinearSampleNetworkUnmixer(network, use_regularization=True) + scores["linear, weighted"] = best_over_lambda( + lambda lam: linear.solve( + observed, regularization_strength=lam, data_covariance=sigmas, weighted=True + ).upstream_preds, + truth, + LAMBDA_GRID, + interior, + ) + scores["linear, unweighted"] = best_over_lambda( + lambda lam: linear.solve( + observed, regularization_strength=lam, data_covariance=sigmas, weighted=False + ).upstream_preds, + truth, + LAMBDA_GRID, + interior, + ) + + nonlinear_unreg = funmixer.SampleNetworkUnmixer(network, use_regularization=False) + scores["nonlinear, lambda=0"] = Score.compare( + nonlinear_unreg.solve(observed).upstream_preds, truth, interior + ) + + nonlinear = funmixer.SampleNetworkUnmixer(network, use_regularization=True) + + def solve_nonlinear(lam: float) -> Optional[funmixer.ElementData]: + try: + return nonlinear.solve(observed, regularization_strength=lam).upstream_preds + except Exception: + return None + + scores["nonlinear, regularized"] = best_over_lambda( + solve_nonlinear, truth, LAMBDA_GRID, interior + ) + return scores + + +METHODS: List[str] = [ + "linear, lambda=0", + "linear, unweighted", + "linear, weighted", + "nonlinear, lambda=0", + "nonlinear, regularized", +] + + +def main() -> None: + print( + f"{N_SITES} sites, areas +/-{AREA_VARIATION:.0%}, uniform export rate, " + f"{RELATIVE_ERROR:.0%} relative error on observations ({N_REPEATS} repeats).\n" + "Regularized methods are shown at their oracle-best lambda.\n" + "Score is the median factor-of error: 10^median|log10(pred/true)|.\n" + ) + + results: Dict[str, Dict[float, List[Score]]] = { + m: {o: [] for o in ORDERS_OF_MAGNITUDE} for m in METHODS + } + for orders in ORDERS_OF_MAGNITUDE: + for repeat in range(N_REPEATS): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + trial = run_trial(orders, seed=repeat) + for method, score in trial.items(): + results[method][orders].append(score) + print(f" ...done {orders} orders of magnitude") + + header = f"\n{'method':<24}" + "".join(f"{o:>12.1f}" for o in ORDERS_OF_MAGNITUDE) + for label, extract, fmt in [ + ("median factor-of error (all sites)", lambda s: 10**s.median_log_error, "{:>12.2f}"), + ( + "median factor-of error (interior sites only)", + lambda s: 10**s.interior_median_log_error, + "{:>12.2f}", + ), + ("RMS factor-of error", lambda s: 10**s.rms_log_error, "{:>12.2f}"), + ("fraction within 2x", lambda s: s.fraction_within_2x, "{:>11.0%} "), + ("sites hitting zero", lambda s: s.n_nonpositive, "{:>12.1f}"), + ("oracle-best lambda", lambda s: s.chosen_lambda, "{:>12.2g}"), + ]: + print(f"\n=== {label} (source range, orders of magnitude) ===") + print(header.strip("\n")) + for method in METHODS: + row = f"{method:<24}" + for orders in ORDERS_OF_MAGNITUDE: + row += fmt.format(float(np.mean([extract(s) for s in results[method][orders]]))) + print(row) + + _, axis = plt.subplots(figsize=(8, 5)) + for method in METHODS: + axis.plot( + ORDERS_OF_MAGNITUDE, + [ + float(np.mean([10**s.rms_log_error for s in results[method][o]])) + for o in ORDERS_OF_MAGNITUDE + ], + marker="o", + label=method, + ) + axis.axhline(1.0, color="grey", linestyle=":", label="perfect recovery") + axis.set_yscale("log") + axis.set_xlabel("Source range (orders of magnitude)") + axis.set_ylabel("RMS factor-of error in recovered concentration") + axis.set_title( + f"Source recovery vs. source range\n({N_SITES} sites, {RELATIVE_ERROR:.0%} data error)" + ) + axis.legend() + plt.tight_layout() + plt.savefig("linear_vs_nonlinear_benchmark.png", dpi=150) + print("\nSaved plot to linear_vs_nonlinear_benchmark.png") + + +if __name__ == "__main__": + main() From 833628f1c9bf87d419bf1c1f28da5b6d86fff619 Mon Sep 17 00:00:00 2001 From: Alex Lipp Date: Tue, 25 Aug 2026 18:29:33 +0100 Subject: [PATCH 2/3] Add mathematical note deriving the linear unmixing solution A standalone derivation of the linear formulation, written to be checked line by line. Takes Appendix A of Barnes and Lipp (2024) as its starting point and uses its notation, then works out the consequences and extends it. Contents: - The forward model as a matrix, generalised to include first-order decay. - Proof that M is row-substochastic (stochastic when conservative) and lower triangular in topological order with diagonal q_i/Q_i, hence invertible with |det M| = prod(q_i/Q_i). - Closed-form inverse as a local differencing stencil on the network, derived from tracer conservation, showing M^-1 is sparse despite M being dense. - The amplification factor kappa_i = Q_i/q_i, and the exact condition under which the non-negativity constraint binds. - Proof that the unregularized problem is degenerate: any positive-definite weighting gives the same zero-residual answer, so weighting is inert until a competing term is added to the objective. - Generalised least squares via whitening, and why a proportional error model makes the weighted linear misfit a relative misfit, as in the parent study. - Tikhonov regularization of the model variance: existence and uniqueness for every lambda, the linear estimator, and both limits of the regularization path. - Error propagation: source and prediction covariances, the Cramer-Rao result at lambda = 0, bias and resolution, and the exact identity relating the propagated covariance to the Bayesian posterior covariance. Builds with latexmk + biber. Uses preprint.sty from the parent manuscript for visual consistency. Co-Authored-By: Claude Opus 5 --- docs/.gitignore | 10 + docs/linear_unmixing_note.bib | 139 ++++++ docs/linear_unmixing_note.pdf | Bin 0 -> 193588 bytes docs/linear_unmixing_note.tex | 865 ++++++++++++++++++++++++++++++++++ docs/preprint.sty | 281 +++++++++++ 5 files changed, 1295 insertions(+) create mode 100644 docs/.gitignore create mode 100644 docs/linear_unmixing_note.bib create mode 100644 docs/linear_unmixing_note.pdf create mode 100644 docs/linear_unmixing_note.tex create mode 100644 docs/preprint.sty diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..80ff6a7 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,10 @@ +# LaTeX build artefacts +*.aux +*.bcf +*.blg +*.fdb_latexmk +*.fls +*.log +*.out +*.run.xml +*.bbl diff --git a/docs/linear_unmixing_note.bib b/docs/linear_unmixing_note.bib new file mode 100644 index 0000000..6fb664a --- /dev/null +++ b/docs/linear_unmixing_note.bib @@ -0,0 +1,139 @@ +@article{barnes_using_2024, + title = {Using Convex Optimization to Efficiently Apportion Tracer and Pollutant Sources from Point Concentration Observations}, + author = {Barnes, Richard and Lipp, Alex G.}, + journal = {Water Resources Research}, + year = {2024}, + doi = {10.1029/2023WR036159}, +} + +@book{menke_geophysical_2012, + title = {Geophysical Data Analysis: Discrete Inverse Theory}, + author = {Menke, William}, + edition = {3}, + publisher = {Academic Press}, + address = {Boston}, + year = {2012}, + isbn = {9780123971609}, +} + +@book{aster_parameter_2018, + title = {Parameter Estimation and Inverse Problems}, + author = {Aster, Richard C. and Borchers, Brian and Thurber, Clifford H.}, + edition = {3}, + publisher = {Elsevier}, + year = {2018}, + isbn = {9780128046517}, +} + +@book{tarantola_inverse_2005, + title = {Inverse Problem Theory and Methods for Model Parameter Estimation}, + author = {Tarantola, Albert}, + publisher = {Society for Industrial and Applied Mathematics}, + address = {Philadelphia}, + year = {2005}, + isbn = {9780898715729}, +} + +@book{hansen_rank-deficient_1998, + title = {Rank-Deficient and Discrete Ill-Posed Problems: Numerical Aspects of Linear Inversion}, + author = {Hansen, Per Christian}, + publisher = {Society for Industrial and Applied Mathematics}, + address = {Philadelphia}, + year = {1998}, + isbn = {9780898714036}, +} + +@article{hoerl_ridge_1970, + title = {Ridge Regression: Biased Estimation for Nonorthogonal Problems}, + author = {Hoerl, Arthur E. and Kennard, Robert W.}, + journal = {Technometrics}, + volume = {12}, + number = {1}, + pages = {55--67}, + year = {1970}, + doi = {10.1080/00401706.1970.10488634}, +} + +@article{tikhonov_solution_1963, + title = {Solution of Incorrectly Formulated Problems and the Regularization Method}, + author = {Tikhonov, Andrey N.}, + journal = {Soviet Mathematics Doklady}, + volume = {4}, + pages = {1035--1038}, + year = {1963}, +} + +@article{elden_algorithms_1977, + title = {Algorithms for the Regularization of Ill-Conditioned Least Squares Problems}, + author = {Eld{\'e}n, Lars}, + journal = {BIT Numerical Mathematics}, + volume = {17}, + number = {2}, + pages = {134--145}, + year = {1977}, + doi = {10.1007/BF01932285}, +} + +@book{golub_matrix_2013, + title = {Matrix Computations}, + author = {Golub, Gene H. and Van Loan, Charles F.}, + edition = {4}, + publisher = {Johns Hopkins University Press}, + address = {Baltimore}, + year = {2013}, + isbn = {9781421407944}, +} + +@book{boyd_convex_2004, + title = {Convex Optimization}, + author = {Boyd, Stephen and Vandenberghe, Lieven}, + publisher = {Cambridge University Press}, + year = {2004}, + isbn = {9780521833783}, +} + +@article{diamond_cvxpy_2016, + title = {{CVXPY}: A Python-Embedded Modeling Language for Convex Optimization}, + author = {Diamond, Steven and Boyd, Stephen}, + journal = {Journal of Machine Learning Research}, + volume = {17}, + number = {83}, + pages = {1--5}, + year = {2016}, +} + +@inproceedings{blondes_practical_2016, + title = {A Practical Guide to the Use of Major Elements, Trace Elements, and Isotopes in Compositional Data Analysis: Applications for Deep Formation Brine Geochemistry}, + author = {Blondes, M. S. and Engle, M. A. and Geboy, N. J.}, + booktitle = {Compositional Data Analysis}, + series = {Springer Proceedings in Mathematics \& Statistics}, + publisher = {Springer International Publishing}, + pages = {13--29}, + year = {2016}, + isbn = {978-3-319-44811-4}, +} + +@book{aitchison_statistical_1986, + title = {The Statistical Analysis of Compositional Data}, + author = {Aitchison, John}, + publisher = {Chapman and Hall}, + year = {1986}, +} + +@book{cormen_introduction_2009, + title = {Introduction to Algorithms}, + author = {Cormen, Thomas H. and Leiserson, Charles E. and Rivest, Ronald L. and Stein, Clifford}, + edition = {3}, + publisher = {MIT Press}, + year = {2009}, + isbn = {9780262033848}, +} + +@book{horn_matrix_2012, + title = {Matrix Analysis}, + author = {Horn, Roger A. and Johnson, Charles R.}, + edition = {2}, + publisher = {Cambridge University Press}, + year = {2012}, + isbn = {9780521548236}, +} diff --git a/docs/linear_unmixing_note.pdf b/docs/linear_unmixing_note.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e9a534ceedfb5a6532ed5948c3e8163fe0893a99 GIT binary patch literal 193588 zcma&NLv$u=(5;;(wrv|vY}>Y-bZpzUopji-Z95%X9otUMd;ay$zBBl0RI^&^9@M?+ z+Iv$diAgXpGqS@{%rA|s!E&+?1Bo3>e!=qb!7|C2+grL?5wo$ga1sB11$x$adzP{}n)XxM7yLF_iR(U(h1k`N!L6(| zv9a!EuCl{0MFcdPLq>&B0;yQIH0-;13bU`5j>p%mq(`b1Fz7pciMRhN4*9bnKCoxl zs&!-Nr`J7+>2Nnk_DRxBdvstORJXrNgp`03@cwWSJ3ew~M6}xCll$%`yjb9B^Xtq= zk6t4e=65Knr{xdQ&FaTUtjsHzVumylr8YB-w6Z!}zNI1Cf~26B1oh);Prw_yuI=9Q z4IDqkbfo;rZ@|lWLO^LUqm?jr znu*g(ePL`8A6iVXv&!I;ebcM?+t5s%N5EJa=`Yy)kmBc44*5Ul-;m9uy4Py_v9+>n z-brXQaI5BN_?WN#SvD-b-830vc(dtlb&#x|4cnXrInN7xZZ#b%$1F=9Lkd|R8gYvA zJr{!HQN|Dyjm7p=4#`W^?dZQ#eH)4~Dl^&aIIA24w&$xut*3r*a|nq$_ux#F8cy3e zhoipgcT)Cv^If0!zV%HV{*^=4NneF}?ylX8Gmbv@F|)KZcLOR$^(CxvsUw2 zc342AK@^1vVURagPw)NY9v5WGkan!bAAa@XNVZ7FO$Z;5Dps5SB9&72=4iG4TQLvo zQ>8l~7pgkf9HNpA)$_hoL1m89T(@~xf1NXwnM-l_JModWrFW!rlPG&TD-Dahfd16& zS9LCW)}x*;AEImGSbYe?cLsh-p|E@=qhjRkO>V%8n~|5FB)vcAYlfS z;>awdU^ty%7V{X@d=kKSwDP`25Q28Heg z_;B6^-;4=4MbkuHU0<#Ct{>kpk=e}1@j#FHh-@eCmg_{;bbgERBHm{kbKL4K_Zl57 zWS!1Z(5#xu22+p_aW2r8)94pmeY&{X_qXr$1m7XLO{5>+))Jyo3#9&Vv|mlq%u?uL z=wZh5!Yxm=ILK!;w)bovG;>lA?&wdDdQ%y{pceg9q(u?mPe9-A+JDL#dSJcEGza?4 zESh?!sQWl{l|?@IuFJh4?uDQtlM$^vYE+HM6M~UK|EfNN^&_v2cg%$i1YKpm+vZLUyV4lhjN(`VGQV{nJi_w z3b%{kFE|eQC9p?_Re9#q1WXv0zSzSEd991ixI4s1LB$D2roUcVE5$tyUfpZddnVwC zD_;h>Uoh17YUfB;X;{9(IEjMXtFzc)Gc_snSNblsZ`pARX*3Nw+|23nu6NBE({F=> zeY#TjL<8L-2KQcgHw1AINhJqUBzjsrHWhKt#H)tNcMoSnn=ZvhDgs-pgCMZe9ONP> znXrP*^>!Pso3Cc~7`^M~!}}T8B6f)9{d4|WY^@(-u+EVKxmErYbDR>?ZItOfsB$?^ zvxyEryXE-;*ZjB(QW*<)VOa_35Y+=kT&~Uj>$wgJUwE)|rrWckTt!WCqP055*&)tl|wbn~$^Dwyp8m(ReYGW7nf^EWArSSxPsN+?bBfazJ0Vj*C zzLlLIp})VljiBZAV7%GlZu`~BNs>KdM4wQU6F)(3=f1u^Jq9iG7R%gp>DQ*ryU6|< zmjaRIzlr9p%`xD)d4u>5lQOdCXmq5VTOw4E;bppZH0>e}zP33DlAF3+G=1q%dECNi z7@-AoQC`7MqI(q^V@^IkvqP{_;xtDqZIuF$K@$x|Mx6K_8t12i-Au-N{UrTWl7)(9 zI@07G)bjaMz)*ytO zlm9J!gJ}EWRTAg&HI}7)JzlvLNjiM+^|?(!$xac5-JVX9Dl*b?VT;{c2_r8Ggxw>)|>gPKSLJS z%Lc10GdMSPI95IC7f7dUTj*A7trHKA;bQBl3>|Wn#Gy#PLzioa(?>*hQzsyCu>W;w zW2TD+12O|WU8`X6>0x?-U{bIhN1m#YM!eP^SQWq@!Tos~m-eA(J z^ehlaM?FMM>nslw71u|xE951fFPkaKtRA}rL~Y1wzrc<&Q!|-VTjIlNw;}sPkvUW^ z($Y$63`+j;)p=kK?8l7t3fQJZ6(3@FW`cravs_$d(UTeh7nPOP&bn+=l5k!dt7Cb% z5%WtFEVP(nK0b$Bjo1=kMZAvSM0U%kLHBxbmpqjzKNYynuM0Q8Bcmv<9{XDEBezvufiYC-}I1{cP0K{%`Dmt)( zu!g~#u8N79!j4GSJv-&pVW;UX4x>5+O-DI8rfnPt^2uaI6{WW()+%JOAc{yxZo8bn zUM(P{$BP23=`H6Y1`sGHB#_pg2ujxRCk`kq#mxN6yo=pwPqLgZqEVG6;Lt@`HRyvi z`aTnB5jB7X@HQcrGGB8XA5Sa7-S;v|W8wlo)?7Pv*B3?B+n9iGhc3$#%lWis#BYMr zIdvKe&QTW?1&mK}JY18BuV@+4$
    lbi(cXU0P>nO|JRDA}G#^R|Bs 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ȏ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 qhGYqW6Hh!>0drUY literal 0 HcmV?d00001 diff --git a/docs/linear_unmixing_note.tex b/docs/linear_unmixing_note.tex new file mode 100644 index 0000000..aab33e9 --- /dev/null +++ b/docs/linear_unmixing_note.tex @@ -0,0 +1,865 @@ +\documentclass[11pt]{article} + +\usepackage{preprint} + +%% Math packages +\usepackage{amsmath, amsthm, amssymb, amsfonts} +\usepackage{bm} +\usepackage{mathtools} + +\usepackage[style=authoryear-comp,maxcitenames=2,uniquename=false,giveninits=true,uniquelist=false,maxbibnames=99,sortcites=true,sorting=nyt,doi=true,isbn=false,url=false,hyperref=true,date=year]{biblatex} +\renewbibmacro{in:}{% + \ifboolexpr{test {\ifentrytype{article}} or test {\ifentrytype{inproceedings}}}{}% + {\printtext{\bibstring{in}\intitlepunct}}% +} +\AtEveryBibitem{\clearlist{language}\clearfield{note}\clearfield{abstract}} +\addbibresource{linear_unmixing_note.bib} + +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{xcolor} +\usepackage[colorlinks=true, linkcolor=magenta, urlcolor=blue, citecolor=cyan, anchorcolor=black]{hyperref} +\usepackage{booktabs} +\usepackage{microtype} +\usepackage{graphicx} +\usepackage{xspace} +\usepackage{titlesec} +\titlespacing\section{0pt}{12pt plus 3pt minus 3pt}{1pt plus 1pt minus 1pt} +\titlespacing\subsection{0pt}{10pt plus 3pt minus 3pt}{1pt plus 1pt minus 1pt} + +\usepackage{tikz} +\usetikzlibrary{arrows.meta} + +%% preprint.sty hardcodes the parent paper's title in the running header. +\lhead{\scshape Note -- Linear least-squares unmixing on a sample network} + +%% Notation, following the parent preprint +\newcommand{\cvec}{\ensuremath{\mathbf{c}}\xspace} +\newcommand{\dvec}{\ensuremath{\mathbf{d}}\xspace} +\newcommand{\qvec}{\ensuremath{\mathbf{q}}\xspace} +\newcommand{\Mmat}{\ensuremath{\mathbf{M}}\xspace} +\newcommand{\Rmat}{\ensuremath{\mathbf{R}}\xspace} +\newcommand{\Pmat}{\ensuremath{\mathbf{P}}\xspace} +\newcommand{\Amat}{\ensuremath{\mathbf{A}}\xspace} +\newcommand{\Wmat}{\ensuremath{\mathbf{W}}\xspace} +\newcommand{\Imat}{\ensuremath{\mathbf{I}}\xspace} +\newcommand{\onevec}{\ensuremath{\mathbf{1}}\xspace} +\newcommand{\Cd}{\ensuremath{\mathbf{C}_{\mathbf{d}}}\xspace} +\newcommand{\Cc}{\ensuremath{\mathbf{C}_{\mathbf{c}}}\xspace} +\newcommand{\Cdhat}{\ensuremath{\mathbf{C}_{\hat{\mathbf{d}}}}\xspace} +\newcommand{\Reals}{\ensuremath{\mathbb{R}}\xspace} +\newcommand{\Expect}{\ensuremath{\mathbb{E}}\xspace} +\newcommand{\transpose}{\ensuremath{^{\mathsf{T}}}} + +\DeclareMathOperator*{\argmin}{arg\,min} + +\newtheorem{theorem}{Proposition} +\newtheorem{lemma}[theorem]{Lemma} +\newtheorem{corollary}[theorem]{Corollary} +\theoremstyle{definition} +\newtheorem{remark}[theorem]{Remark} + +\title{Linear least-squares unmixing on a sample network:\\derivation, regularization, and error propagation} + +\usepackage{authblk} +\renewcommand*{\Authfont}{\bfseries} +\author[1]{Alex G. Lipp} +\affil[1]{Merton College, University of Oxford} +\date{} + +\begin{document} +\maketitle + +\begin{abstract} +\noindent +\textcite{barnes_using_2024} recover source concentrations on a river sample network by +convex optimization, penalising \textit{relative} differences between predicted and observed +concentrations. Their Appendix~A observes that penalising \textit{absolute} differences +instead reduces the problem to a linear least-squares matrix inversion. This note works that +observation out in full. We show that the mixing matrix $\Mmat$ is square, row-stochastic and +triangular, hence always invertible, and we give its inverse in closed form as a local +differencing stencil on the network. We then extend the formulation in three directions that +the appendix does not cover: first-order tracer decay, weighting by the data covariance +(generalised least squares), and Tikhonov regularization of the model variance. We prove that +the regularized problem has a unique solution for every regularization strength, derive the +resulting linear estimator, and obtain closed-form covariances for both the recovered sources +and the modelled observations. We distinguish carefully between the propagated covariance and +the Bayesian posterior covariance, which coincide only when the problem is unregularized, and +give the exact identity relating them. Everything is derived from first principles so that it +can be checked line by line. +\end{abstract} + +\vspace{3mm} + +\section{Scope and notation} + +We adopt the notation of \textcite{barnes_using_2024} throughout. The sample network is a +directed acyclic graph $G = (N, E)$ whose $n$ nodes are sample sites and whose edges point +\textit{downstream}. Each node $i$ carries: + +\begin{itemize} + \item $a_i$ [L$^2$], the area of the sub-basin uniquely defined by sample site $i$; + \item $\phi_i$ [M\,L$^{-2}$\,T$^{-1}$], the material export rate of that sub-basin; + \item $q_i = a_i \phi_i > 0$ [M\,T$^{-1}$], the material flux the sub-basin generates; + \item $c_i$, the source concentration of the tracer in material exported by sub-basin $i$ + --- the unknown; + \item $d_i$, the concentration observed at the sample site --- the datum. +\end{itemize} + +\noindent Write $U_i \subseteq N$ for the set of nodes upstream of $i$ \textit{including $i$ +itself}, and $A(i)$ for the set of immediate upstream neighbours of $i$ (its predecessors in +$G$). We make one structural assumption throughout, which the D8 flow model guarantees: + +\begin{quote} +\textbf{(A1)} Every node has at most one downstream neighbour, so $G$ is a forest of +in-trees. +\end{quote} + +\noindent Assumption (A1) is what makes the sets $\{U_p\}_{p \in A(i)}$ pairwise disjoint, +which we use in Proposition~\ref{prop:inverse}. It does not require the network to be +connected: disjoint river basins may be solved together, exactly as in the parent study. + +Two derived quantities recur. The \textbf{total flux} passing site $i$ is +\begin{equation} + Q_i \;=\; \sum_{j \in U_i} q_j , + \label{eq:Q} +\end{equation} +and, for $j \in U_i$, the \textbf{path attenuation} from $j$ to $i$ is +\begin{equation} + \alpha_{j \to i} \;=\; \prod_{e \in \mathrm{path}(j \to i)} \exp\!\left( -k_{e} L_{e} \right), + \label{eq:alpha} +\end{equation} +the product over edges of the unique path from $j$ to $i$, where $L_e$ is the flow-path length +of edge $e$ and $k_e$ the first-order decay constant applied along it. By (A1) that path is +unique, so \eqref{eq:alpha} is well defined, and $\alpha_{i \to i} = 1$ (empty product). A +\textbf{conservative} tracer has $k_e = 0$ for all $e$, hence $\alpha_{j \to i} = 1$ +throughout; this is the case treated in Appendix~A of the parent paper. + +\section{The forward model as a matrix} + +\subsection{Assembling \texorpdfstring{$\Mmat$}{M}} + +Conservation of tracer at steady state gives the observation at site $i$ as a flux-weighted +mixture of every source upstream of it, attenuated by decay along the way: +\begin{equation} + d_i \;=\; \frac{\displaystyle\sum_{j \in U_i} \alpha_{j \to i}\, q_j\, c_j} + {\displaystyle\sum_{j \in U_i} q_j} + \;=\; \frac{1}{Q_i} \sum_{j \in U_i} \alpha_{j \to i}\, q_j\, c_j . + \label{eq:forward} +\end{equation} + +\noindent Note that decay enters the numerator only. Tracer is lost in transit, but the +\textit{carrier} --- the water or sediment --- is not, so the denominator is the undecayed +total flux $Q_i$. Setting every $\alpha = 1$ recovers Equation~3 of \textcite{barnes_using_2024}. + +Following the parent appendix, define the $0$--$1$ matrix $\bm{\Theta}$ by $\Theta_{ij} = 1$ +if and only if $j \in U_i$; that is, $\bm{\Theta}$ is the path matrix of $G$ with a self-loop +added at every node. Equation~\eqref{eq:forward} is then linear in \cvec, with +\begin{equation} + \boxed{\; + M_{ij} \;=\; \frac{\Theta_{ij}\, \alpha_{j \to i}\, q_j} + {\sum_{k=1}^{n} \Theta_{ik}\, q_k} + \;=\; \frac{\Theta_{ij}\, \alpha_{j \to i}\, q_j}{Q_i} , + \qquad \dvec = \Mmat \cvec . + \;} + \label{eq:M} +\end{equation} + +\noindent For a conservative tracer this is exactly Equation~11 of the parent appendix. + +\subsection{Structure of \texorpdfstring{$\Mmat$}{M}} + +$\Mmat$ is $n \times n$: the sample network defines exactly one sub-basin per sample site, so +there are as many unknowns as data. This squareness is the pivot on which everything below +turns, and it is worth stating plainly that it is a modelling choice, not a coincidence --- it +is what it means to attribute one source concentration to each sampled sub-basin. + +\begin{lemma}[Row sums]\label{lem:stochastic} +Every row of $\Mmat$ is non-negative with $\sum_j M_{ij} \le 1$, with equality for all $i$ if +and only if the tracer is conservative. +\end{lemma} + +\begin{proof} +Non-negativity is immediate from \eqref{eq:M} since $q_j > 0$, $\alpha > 0$, $\Theta \in +\{0,1\}$. For the sum, +\[ + \sum_{j=1}^n M_{ij} + = \frac{1}{Q_i}\sum_{j \in U_i} \alpha_{j\to i}\, q_j + \;\le\; \frac{1}{Q_i} \sum_{j \in U_i} q_j + \;=\; \frac{Q_i}{Q_i} = 1 , +\] +using $\alpha_{j \to i} \le 1$, which holds because every $k_e L_e \ge 0$. Equality for a +given $i$ requires $\alpha_{j \to i} = 1$ for all $j \in U_i$, i.e.\ no decay anywhere +upstream of $i$; equality for all $i$ requires it everywhere. +\end{proof} + +\noindent For a conservative tracer, then, $\Mmat$ is \textbf{row-stochastic}: each observation +is a convex combination of the sources upstream of it. This is the formal statement of ``a +river sample is a mixture of its upstream sources'', and it has a useful consequence, +$\Mmat \onevec = \onevec$, which we use in Section~\ref{sec:limits}. + +\begin{lemma}[Triangularity]\label{lem:triangular} +Let $\sigma$ be any topological ordering of $G$, so that $\sigma(u) < \sigma(v)$ whenever +$(u,v) \in E$. Permuting rows and columns of $\Mmat$ by $\sigma$ makes it lower triangular, +with diagonal entries +\begin{equation} + M_{ii} \;=\; \frac{q_i}{Q_i} \;\in\; (0, 1] . + \label{eq:diagonal} +\end{equation} +\end{lemma} + +\begin{proof} +$M_{ij} \ne 0$ requires $\Theta_{ij} = 1$, i.e.\ $j \in U_i$. If $j \ne i$ then a directed +path runs from $j$ to $i$, so $\sigma(j) < \sigma(i)$. Hence $M_{ij} \ne 0 \Rightarrow +\sigma(j) \le \sigma(i)$, which is lower triangularity in the permuted ordering. The diagonal +follows from \eqref{eq:M} with $j = i$, using $\Theta_{ii} = 1$ and $\alpha_{i \to i} = 1$; it +is positive because $q_i > 0$, and at most $1$ because $q_i \le Q_i$, with $M_{ii} = 1$ exactly +when $i$ is a leaf (headwater) node. +\end{proof} + +\begin{theorem}[Invertibility]\label{prop:invertible} +$\Mmat$ is invertible, with +\begin{equation} + \left| \det \Mmat \right| \;=\; \prod_{i=1}^{n} \frac{q_i}{Q_i} \;>\; 0 . +\end{equation} +\end{theorem} + +\begin{proof} +Let $\bm{\Pi}$ be the permutation matrix of the topological order $\sigma$. By +Lemma~\ref{lem:triangular}, $\bm{\Pi}\Mmat\bm{\Pi}\transpose$ is lower triangular, so its +determinant is the product of its diagonal entries, $\prod_i q_i / Q_i$, which is strictly +positive because every $q_i > 0$ and every $Q_i < \infty$. Since $|\det \bm{\Pi}| = 1$, +$|\det \Mmat| = \prod_i q_i/Q_i > 0$, and a matrix with non-zero determinant is invertible. +\end{proof} + +\begin{remark} +Invertibility is unaffected by decay: $\alpha$ appears only off the diagonal, so +\eqref{eq:diagonal} is untouched. It is also unaffected by the export rates $\phi$, provided +they are strictly positive. It \textit{would} fail if some sub-basin had zero area or zero +export rate, i.e.\ if two sample sites were co-located --- then $q_i = 0$, the corresponding +source is not identifiable, and the model is degenerate. This is worth checking in practice. +\end{remark} + +\section{The inverse in closed form} +\label{sec:inverse} + +Proposition~\ref{prop:invertible} says $\cvec = \Mmat^{-1}\dvec$ exists and is unique. We now show +it need never be computed by matrix inversion: it is a local differencing stencil on the +network, computable in one sweep. + +It is convenient to work with the \textbf{tracer flux} passing site $i$, +\begin{equation} + T_i \;=\; Q_i d_i \;=\; \sum_{j \in U_i} \alpha_{j \to i}\, q_j\, c_j , + \label{eq:T} +\end{equation} +which is just \eqref{eq:forward} cleared of its denominator. $T_i$ has units of tracer mass per +time: it is the amount of tracer passing the sample site per unit time. + +\begin{theorem}[Closed-form inverse]\label{prop:inverse} +For every node $i$, +\begin{equation} + \boxed{\; + c_i \;=\; \frac{1}{q_i}\left( Q_i d_i \;-\; \sum_{p \in A(i)} \alpha_{p \to i}\, Q_p d_p \right) . + \;} + \label{eq:closedform} +\end{equation} +\end{theorem} + +\begin{proof} +Every node in $U_i$ other than $i$ lies upstream of exactly one immediate neighbour $p \in +A(i)$, and by (A1) the sets $U_p$ for distinct $p \in A(i)$ are pairwise disjoint. Hence +\[ + U_i \;=\; \{i\} \;\sqcup\; \bigsqcup_{p \in A(i)} U_p , +\] +a disjoint union. Now fix $p \in A(i)$ and $j \in U_p$. The unique path $j \to i$ is the path +$j \to p$ followed by the single edge $p \to i$, so the attenuation factorises: +\begin{equation} + \alpha_{j \to i} \;=\; \alpha_{j \to p}\;\alpha_{p \to i} . + \label{eq:factorise} +\end{equation} +Substituting the partition and \eqref{eq:factorise} into \eqref{eq:T}, and using +$\alpha_{i \to i} = 1$ for the $j = i$ term, +\begin{align} + T_i + &= q_i c_i \;+\; \sum_{p \in A(i)} \; \sum_{j \in U_p} \alpha_{j \to i}\, q_j c_j \nonumber\\ + &= q_i c_i \;+\; \sum_{p \in A(i)} \alpha_{p \to i} \underbrace{\sum_{j \in U_p} \alpha_{j \to p}\, q_j c_j}_{= \,T_p} \nonumber\\ + &= q_i c_i \;+\; \sum_{p \in A(i)} \alpha_{p \to i}\, T_p . + \label{eq:balance} +\end{align} +Equation~\eqref{eq:balance} is a statement of tracer conservation at node $i$: the tracer +leaving equals the tracer generated locally plus the (attenuated) tracer delivered by the +immediate tributaries. Solving for $c_i$ and substituting $T = Qd$ gives +\eqref{eq:closedform}. Since $q_i > 0$ the division is legitimate. +\end{proof} + +\begin{corollary}[Sparsity of $\Mmat^{-1}$] +\begin{equation} + \left(\Mmat^{-1}\right)_{ij} = + \begin{dcases} + \dfrac{Q_i}{q_i}, & j = i, \\[6pt] + -\dfrac{\alpha_{j \to i}\, Q_j}{q_i}, & j \in A(i), \\[6pt] + 0, & \text{otherwise.} + \end{dcases} + \label{eq:Minv} +\end{equation} +$\Mmat^{-1}$ therefore has at most $n + |E| \le 2n - 1$ non-zero entries, against up to +$n(n+1)/2$ in $\Mmat$ itself. Equation~\eqref{eq:closedform} evaluates in $O(n + |E|)$ time. +\end{corollary} + +\noindent It is worth pausing on how much structure this reveals. $\Mmat$ is dense and global +--- every downstream observation depends on every source above it --- yet its inverse is +sparse and purely local. Recovering a source concentration requires only the observation at +that site and the observations at its immediate tributaries. All the long-range mixing cancels. + +\begin{corollary}[Worked example] +For the six-node network of \textcite[Figure~2]{barnes_using_2024}, with $A(4) = \{1,2\}$, +$A(5) = \{3\}$, $A(6) = \{4,5\}$ and no decay, Equation~\eqref{eq:closedform} gives +\begin{align*} + c_1 &= d_1, \qquad c_2 = d_2, \qquad c_3 = d_3, \\ + c_4 &= \frac{Q_4 d_4 - Q_1 d_1 - Q_2 d_2}{q_4}, \\ + c_5 &= \frac{Q_5 d_5 - Q_3 d_3}{q_5}, \\ + c_6 &= \frac{Q_6 d_6 - Q_4 d_4 - Q_5 d_5}{q_6}, +\end{align*} +with $Q_1 = q_1$, $Q_2 = q_2$, $Q_3 = q_3$, $Q_4 = q_1 + q_2 + q_4$, $Q_5 = q_3 + q_5$ and +$Q_6 = \sum_{i=1}^{6} q_i$. Headwater sites are recovered exactly and trivially; interior sites +are recovered by differencing. +\end{corollary} + +\subsection{Noise amplification} +\label{sec:amplification} + +Differencing is the source of all the difficulty. Suppose the observations carry independent +errors of standard deviation $\sigma_i$. Propagating them through \eqref{eq:closedform} +(anticipating Section~\ref{sec:propagation}, and using independence), +\begin{equation} + \operatorname{Var}(c_i) + = \frac{Q_i^2 \sigma_i^2 + \sum_{p \in A(i)} \alpha_{p\to i}^2 Q_p^2 \sigma_p^2}{q_i^2} . + \label{eq:varc} +\end{equation} +Every term is scaled by $q_i^{-2}$. Define the \textbf{amplification factor} +\begin{equation} + \kappa_i \;=\; \frac{Q_i}{q_i} \;=\; \frac{1}{M_{ii}} \;\ge\; 1 , + \label{eq:kappa} +\end{equation} +the ratio of the total flux passing site $i$ to the flux its own sub-basin generates. If +site $i$ sits immediately downstream of another site, with little incremental drainage area +between them, then $q_i \ll Q_i$, $\kappa_i$ is large, and \eqref{eq:closedform} subtracts two +nearly equal large numbers to obtain a small one. This is catastrophic cancellation, and it is +an intrinsic property of the sampling design, not of the algorithm: the data simply do not +constrain that sub-basin well. In practice $\kappa_i$, being free to compute, is the single +most useful diagnostic of whether a linear inversion is worth attempting on a given network. + +\begin{corollary}[When positivity binds]\label{cor:positivity} +Since concentrations must be non-negative, the unregularized solution is physically admissible +at node $i$ if and only if +\begin{equation} + Q_i d_i \;\ge\; \sum_{p \in A(i)} \alpha_{p \to i} \, Q_p d_p , + \label{eq:positivity} +\end{equation} +that is, if and only if at least as much tracer flux leaves site $i$ as its tributaries deliver +to it. A violation is a direct falsification of the forward model \eqref{eq:forward} by the +data, whether through measurement error, non-conservative behaviour, or mis-specified fluxes. +\end{corollary} + +\section{Why the unregularized problem is degenerate} +\label{sec:degenerate} + +Appendix~A of \textcite{barnes_using_2024} poses the recovery as a constrained least-squares +problem, +\begin{equation} + \underset{\cvec \in \Reals^n}{\text{minimize}} \;\; \lVert \Mmat\cvec - \dvec \rVert_2 + \qquad \text{subject to} \quad c_i \ge 0 . + \label{eq:appendixproblem} +\end{equation} +Proposition~\ref{prop:invertible} shows that, absent the constraint, this is not really a +least-squares problem at all. + +\begin{theorem}[Degeneracy]\label{prop:degenerate} +Let $\Wmat$ be any symmetric positive definite weight matrix. Then +\begin{equation} + \argmin_{\cvec \in \Reals^n} \; (\Mmat\cvec - \dvec)\transpose \Wmat (\Mmat\cvec - \dvec) + \;=\; \Mmat^{-1}\dvec , +\end{equation} +independently of $\Wmat$, and the residual at the optimum is exactly zero. +\end{theorem} + +\begin{proof} +The objective is non-negative for every \cvec, and $\cvec = \Mmat^{-1}\dvec$ --- which exists +by Proposition~\ref{prop:invertible} --- attains the value $0$. Hence it is a global minimiser, and +it is the unique one because $\Wmat \succ 0$ forces the residual to vanish, and $\Mmat$ is +injective. Algebraically, the generalised least-squares estimator collapses: +\[ + (\Mmat\transpose\Wmat\Mmat)^{-1}\Mmat\transpose\Wmat + = \Mmat^{-1}\Wmat^{-1}\Mmat^{-\mathsf{T}}\Mmat\transpose\Wmat + = \Mmat^{-1} . \qedhere +\] +\end{proof} + +\noindent Three consequences follow, and they set up the rest of this note. + +\begin{enumerate} + \item \textbf{Weighting is inert when $\lambda = 0$.} No choice of $\Wmat$ --- including + $\Wmat = \Cd^{-1}$, the statistically correct one --- changes the answer. There is + nothing to trade off when the model can fit every datum exactly. Weighting only + becomes consequential once a competing term is added to the objective + (Section~\ref{sec:regularization}). + \item \textbf{The problem interpolates the noise.} A zero residual means the estimate + reproduces the observations exactly, errors included. Combined with the amplification + of Section~\ref{sec:amplification}, this is why the raw inversion is unusable on + realistic data. + \item \textbf{The constraint is the only thing that can bite.} By + Corollary~\ref{cor:positivity}, \eqref{eq:appendixproblem} departs from + $\Mmat^{-1}\dvec$ precisely when \eqref{eq:positivity} fails somewhere. +\end{enumerate} + +\begin{remark}[Bounds] +The parent appendix writes the constraint as $0 < c_i < 1$, appropriate for a tracer measured +as a mass fraction. Two remarks. First, a strict inequality cannot be imposed in a convex +program --- the feasible set would not be closed and the infimum need not be attained --- so +one uses $c_i \ge 0$. Second, the upper bound is unit-dependent ($1$ for a fraction, $10^6$ for +mg\,kg$^{-1}$) whereas the lower bound is not. Since $\Mmat$ is row-substochastic, an estimate +exceeding the physical ceiling indicates that the model has already failed, and is more useful +surfaced as a diagnostic than silently clipped. Below we impose $c_i \ge 0$ only. +\end{remark} + +\section{Scale invariance and normalisation} +\label{sec:scale} + +\begin{theorem}[Homogeneity]\label{prop:homogeneity} +The unregularized solution map $\dvec \mapsto \cvec$ is homogeneous of degree one: +$\cvec(t\dvec) = t\,\cvec(\dvec)$ for any $t > 0$. Moreover $\Mmat$ is invariant under a common +rescaling of the fluxes, $q_j \mapsto sq_j$. +\end{theorem} + +\begin{proof} +The first claim is immediate from linearity, $\cvec = \Mmat^{-1}\dvec$. For the second, +replacing $q_j$ by $sq_j$ in \eqref{eq:M} multiplies numerator and denominator by $s$. +\end{proof} + +\noindent Homogeneity licenses the mean normalisation used in the implementation. Writing +$\bar{d} = n^{-1}\sum_i d_i$ and solving with $\tilde{\dvec} = \dvec / \bar{d}$, the recovered +sources are $\cvec = \bar{d}\,\tilde{\cvec}$ exactly. This changes nothing mathematically; its +purpose is numerical, keeping all quantities near unity so that solver tolerances --- which are +partly absolute --- behave uniformly across tracers whose concentrations may differ by many +orders of magnitude. It has the further benefit of rendering the regularization strength +introduced below dimensionless, and hence transferable between tracers and datasets. + +An identical argument applies to the sub-basin areas, which the implementation divides by their +mean, and to covariances, which by Proposition~\ref{prop:homogeneity} and +Section~\ref{sec:propagation} scale as $\bar{d}^{\,2}$ and so may equivalently be computed in +the original units. + +\section{Weighting by the data covariance} +\label{sec:weighting} + +Let $\Cd \succ 0$ be the covariance of the observation errors. The statistically natural misfit +is the Mahalanobis norm of the residual, giving generalised least squares +\parencite{menke_geophysical_2012, aster_parameter_2018}: +\begin{equation} + \Phi(\cvec) \;=\; (\Mmat\cvec - \dvec)\transpose \Cd^{-1} (\Mmat\cvec - \dvec) . + \label{eq:gls} +\end{equation} +Numerically one never forms $\Cd^{-1}$. Take the Cholesky factorisation $\Cd = \mathbf{L} +\mathbf{L}\transpose$ \parencite{golub_matrix_2013} and define the whitened system +\begin{equation} + \widetilde{\Mmat} = \mathbf{L}^{-1}\Mmat, \qquad + \tilde{\dvec} = \mathbf{L}^{-1}\dvec , + \label{eq:whitening} +\end{equation} +so that $\Phi(\cvec) = \lVert \widetilde{\Mmat}\cvec - \tilde{\dvec}\rVert_2^2$: generalised +least squares becomes ordinary least squares on the whitened system, whose residuals have +identity covariance. When $\Cd$ is diagonal this reduces to dividing row $i$ of $\Mmat$ and +$d_i$ by $\sigma_i$. + +\begin{remark}[Relation to the log-ratio objective] +\label{rem:logratio} +Suppose errors are proportional, $\sigma_i = f d_i$ for a constant relative error $f$ --- the +standard model for geochemical assay. The $i$th whitened residual is then +\begin{equation} + \frac{(\Mmat\cvec - \dvec)_i}{f d_i} + \;=\; \frac{1}{f}\left( \frac{(\Mmat\cvec)_i}{d_i} - 1 \right) , +\end{equation} +which depends on the data only through the \textit{ratio} of predicted to observed +concentration. The weighted linear objective is therefore, like the log-ratio objective of the +parent study, a relative misfit; it is the unweighted objective that is not. This is precisely +the objection \textcite{barnes_using_2024} raise against Euclidean misfit --- that it +over-weights high concentrations --- and weighting by a proportional error model answers it. +The two remain distinct: \eqref{eq:gls} penalises the squared relative residual, whereas +$\max(x, 1/x)$ with $x = d_{\text{pred}}/d_{\text{obs}}$ behaves like $1 + |x - 1|$ near +$x = 1$. Both are functions of the relative residual alone, but one is quadratic and the other +piecewise linear in it, so they weight outliers differently and do not coincide. +\end{remark} + +\section{Regularization} +\label{sec:regularization} + +Section~\ref{sec:degenerate} showed that the unregularized problem interpolates the noise. The +remedy is Tikhonov regularization \parencite{tikhonov_solution_1963, hoerl_ridge_1970, +hansen_rank-deficient_1998}: add a penalty expressing a preference among models. + +We penalise the \textbf{variance of the model about its own mean}. Let +\begin{equation} + \Pmat \;=\; \Imat - \tfrac{1}{n}\onevec\onevec\transpose + \label{eq:P} +\end{equation} +be the centering projector, so that $(\Pmat\cvec)_i = c_i - \bar{c}$. The full problem is +\begin{equation} + \boxed{\; + \begin{aligned} + \underset{\cvec \in \Reals^n}{\text{minimize}} \quad + & J(\cvec) = (\Mmat\cvec - \dvec)\transpose \Cd^{-1}(\Mmat\cvec - \dvec) + + \lambda \lVert \Pmat\cvec \rVert_2^2 \\ + \text{subject to} \quad & \cvec \ge 0 . + \end{aligned} + \;} + \label{eq:problem} +\end{equation} + +\noindent Note that this penalises departure from the mean of the \textit{model}, not, as in +the parent study, from the mean of the \textit{observations}. That substitution was forced +there by the requirement that the objective be expressible in disciplined convex form +\parencite{boyd_convex_2004, diamond_cvxpy_2016}; the linear formulation is under no such +constraint and can use the more natural quantity. + +\begin{lemma}[Properties of $\Pmat$]\label{lem:P} +$\Pmat$ is symmetric and idempotent, hence an orthogonal projector; it is positive +semi-definite with eigenvalue $0$ on $\operatorname{span}\{\onevec\}$ and eigenvalue $1$ on its +orthogonal complement; and +\begin{equation} + \lVert \Pmat \cvec \rVert_2^2 = \cvec\transpose \Pmat\transpose \Pmat \cvec + = \cvec\transpose \Pmat \cvec = \sum_{i=1}^n (c_i - \bar{c})^2 . +\end{equation} +\end{lemma} + +\begin{proof} +Symmetry is clear. For idempotency, using $\onevec\transpose\onevec = n$, +\[ + \Pmat^2 = \Imat - \tfrac{2}{n}\onevec\onevec\transpose + + \tfrac{1}{n^2}\onevec(\onevec\transpose\onevec)\onevec\transpose + = \Imat - \tfrac{1}{n}\onevec\onevec\transpose = \Pmat . +\] +Then $\Pmat\transpose\Pmat = \Pmat^2 = \Pmat$, giving the quadratic form. $\Pmat\onevec = +\onevec - \onevec = \bm{0}$, and for $\mathbf{v} \perp \onevec$, $\Pmat\mathbf{v} = +\mathbf{v}$. Eigenvalues in $\{0,1\}$ give $\Pmat \succeq 0$. +\end{proof} + +\noindent $\Pmat$ is thus singular, with a one-dimensional null space. This matters: the +penalty alone does not determine a unique solution, and the classical uniqueness condition for +general-form Tikhonov regularization \parencite{elden_algorithms_1977, +hansen_rank-deficient_1998} requires +$\mathcal{N}(\Mmat) \cap \mathcal{N}(\Pmat) = \{\bm{0}\}$. Here $\mathcal{N}(\Mmat) = +\{\bm{0}\}$ by Proposition~\ref{prop:invertible}, so the condition holds trivially --- but it holds +\textit{because of} the structure of $\Mmat$, not because of the penalty. + +\begin{theorem}[Existence and uniqueness]\label{prop:unique} +For every $\lambda \ge 0$, $J$ is strictly convex on $\Reals^n$. Consequently +\eqref{eq:problem} has a unique global minimiser, both unconstrained and subject to +$\cvec \ge 0$. +\end{theorem} + +\begin{proof} +$J$ is a sum of quadratic forms, with gradient and Hessian +\begin{align} + \nabla J(\cvec) &= 2\Mmat\transpose\Cd^{-1}(\Mmat\cvec - \dvec) + 2\lambda\Pmat\cvec , + \label{eq:grad}\\ + \nabla^2 J &= 2\left(\Mmat\transpose\Cd^{-1}\Mmat + \lambda\Pmat\right) . + \label{eq:hess} +\end{align} +For any $\mathbf{v} \ne \bm{0}$, $\mathbf{v}\transpose\Mmat\transpose\Cd^{-1}\Mmat\mathbf{v} = +(\Mmat\mathbf{v})\transpose\Cd^{-1}(\Mmat\mathbf{v}) > 0$, since $\Mmat\mathbf{v} \ne \bm{0}$ +by Proposition~\ref{prop:invertible} and $\Cd^{-1} \succ 0$. Hence +$\Mmat\transpose\Cd^{-1}\Mmat \succ 0$. By Lemma~\ref{lem:P}, $\lambda\Pmat \succeq 0$ for +$\lambda \ge 0$. A positive definite matrix plus a positive semi-definite one is positive +definite \parencite{horn_matrix_2012}, so $\nabla^2 J \succ 0$ and $J$ is strictly convex. A +strictly convex function has at most one minimiser on any convex set, and coercivity +(guaranteed by $\nabla^2 J \succ 0$) ensures one exists. The non-negative orthant is convex and +closed, so the constrained problem inherits both. +\end{proof} + +\begin{remark} +Uniqueness rests on $\Mmat$ being invertible, \textit{not} on the penalty. Had $\Mmat$ been +singular with a constant vector in its null space, $\lambda\Pmat$ could not have restored +uniqueness, because $\Pmat$ is blind to exactly that direction. Regularization here buys +conditioning and stability, not identifiability --- the problem was already identifiable. +\end{remark} + +\begin{theorem}[The estimator]\label{prop:estimator} +Define +\begin{equation} + \Amat_\lambda \;=\; \Mmat\transpose\Cd^{-1}\Mmat + \lambda\Pmat . + \label{eq:A} +\end{equation} +If the unconstrained minimiser of $J$ is non-negative, it is also the solution of +\eqref{eq:problem}, and it is given by the \textbf{linear} map +\begin{equation} + \boxed{\; + \hat{\cvec} = \Rmat_\lambda \dvec , + \qquad + \Rmat_\lambda = \Amat_\lambda^{-1}\Mmat\transpose\Cd^{-1} . + \;} + \label{eq:estimator} +\end{equation} +\end{theorem} + +\begin{proof} +Setting \eqref{eq:grad} to zero gives the normal equations +$(\Mmat\transpose\Cd^{-1}\Mmat + \lambda\Pmat)\cvec = \Mmat\transpose\Cd^{-1}\dvec$, i.e.\ +$\Amat_\lambda\cvec = \Mmat\transpose\Cd^{-1}\dvec$. $\Amat_\lambda \succ 0$ by the proof of +Proposition~\ref{prop:unique}, hence invertible, giving \eqref{eq:estimator}. For the first claim: +$J$ is strictly convex, so $\hat\cvec$ is its unique global minimiser over all of $\Reals^n$; +if it happens to be feasible then it is a fortiori the minimiser over the smaller feasible set. +\end{proof} + +\noindent Two features of \eqref{eq:estimator} deserve emphasis. First, it is exactly linear in +\dvec, with \textbf{no constant offset}. This is a consequence of penalising +$\lVert\Pmat\cvec\rVert^2$, which is homogeneous in \cvec; a penalty +$\lVert\cvec - \cvec_0\rVert^2$ towards a fixed reference $\cvec_0 \ne \bm{0}$ would contribute +an affine term and complicate everything downstream. Second, +Proposition~\ref{prop:estimator} gives a practical recipe: solve the problem, and if the answer is +non-negative, report the analytical $\Rmat_\lambda\dvec$. The estimate and its covariance then +provably come from the same linear operator, which is what makes the error propagation of +Section~\ref{sec:propagation} meaningful. When the constraint \textit{is} active the estimator +is only piecewise linear, and that guarantee is lost. + +\subsection{Limits} +\label{sec:limits} + +\begin{theorem}[Small $\lambda$] +$\Rmat_0 = \Mmat^{-1}$, recovering Section~\ref{sec:inverse}. +\end{theorem} + +\begin{proof} +Set $\lambda = 0$ in \eqref{eq:estimator}: $\Rmat_0 = +(\Mmat\transpose\Cd^{-1}\Mmat)^{-1}\Mmat\transpose\Cd^{-1} = \Mmat^{-1}$, as in +Proposition~\ref{prop:degenerate}. +\end{proof} + +\begin{theorem}[Large $\lambda$]\label{prop:largelambda} +As $\lambda \to \infty$, $\hat{\cvec} \to t^\star\onevec$ with +\begin{equation} + t^\star = \frac{\mathbf{m}\transpose\Cd^{-1}\dvec}{\mathbf{m}\transpose\Cd^{-1}\mathbf{m}}, + \qquad \mathbf{m} = \Mmat\onevec . +\end{equation} +If in addition the tracer is conservative and $\Cd = \sigma^2\Imat$, then +$t^\star = \bar{d}$, the arithmetic mean of the observations. +\end{theorem} + +\begin{proof} +The penalty term $\lambda\lVert\Pmat\cvec\rVert^2$ diverges unless $\Pmat\cvec \to \bm 0$, i.e.\ +unless \cvec approaches $\mathcal{N}(\Pmat) = \operatorname{span}\{\onevec\}$. Writing +$\cvec = t\onevec$ the penalty vanishes identically and the problem reduces to minimising +$(t\mathbf{m} - \dvec)\transpose\Cd^{-1}(t\mathbf{m} - \dvec)$ over the scalar $t$. +Differentiating and setting to zero gives $t^\star$ as stated. For a conservative tracer +Lemma~\ref{lem:stochastic} gives $\mathbf{m} = \Mmat\onevec = \onevec$, whence with +$\Cd = \sigma^2\Imat$ we get $t^\star = \onevec\transpose\dvec / \onevec\transpose\onevec = +\bar{d}$. +\end{proof} + +\noindent So the regularization path runs from the exact, noise-interpolating inverse at +$\lambda = 0$ to a spatially uniform source field at $\lambda \to \infty$, whose level is the +(precision-weighted) mean observation. Both endpoints are interpretable, which is a useful +property when selecting $\lambda$ from the trade-off curve between misfit +$\lVert\Cd^{-1/2}(\Mmat\hat\cvec - \dvec)\rVert_2$ and roughness +$\lVert\Pmat\hat\cvec\rVert_2$ \parencite{hansen_rank-deficient_1998}. + +\section{Error propagation} +\label{sec:propagation} + +We now propagate the observational uncertainty $\Cd$ into the recovered sources. Because +$\hat\cvec$ is an exactly linear function of \dvec, this is not a linearisation: it is exact. + +\begin{theorem}[Covariance of the sources]\label{prop:Cc} +If $\dvec$ has covariance $\Cd$ and no constraint is active, then +\begin{equation} + \boxed{\;\Cc \;=\; \Rmat_\lambda\, \Cd\, \Rmat_\lambda\transpose . \;} + \label{eq:Cc} +\end{equation} +\end{theorem} + +\begin{proof} +For a deterministic matrix $\Rmat$ and random vector $\dvec$, +$\operatorname{Cov}(\Rmat\dvec) = \Rmat \operatorname{Cov}(\dvec) \Rmat\transpose$, directly +from the definition of covariance \parencite{menke_geophysical_2012}. Apply with +$\Rmat = \Rmat_\lambda$, which is deterministic given the network and the fluxes. +\end{proof} + +\begin{theorem}[Covariance of the modelled observations]\label{prop:Cdhat} +The modelled observations $\hat\dvec = \Mmat\hat\cvec$ have +\begin{equation} + \boxed{\;\Cdhat \;=\; \Mmat\, \Cc\, \Mmat\transpose + \;=\; (\Mmat\Rmat_\lambda)\,\Cd\,(\Mmat\Rmat_\lambda)\transpose , \;} + \label{eq:Cdhat} +\end{equation} +and at $\lambda = 0$, $\Cdhat = \Cd$ exactly. +\end{theorem} + +\begin{proof} +The first equality is Proposition~\ref{prop:Cc} applied to the linear map $\hat\cvec \mapsto +\Mmat\hat\cvec$; the second follows by substitution. $\Mmat\Rmat_\lambda$ is the hat (influence) +matrix. At $\lambda = 0$, $\Rmat_0 = \Mmat^{-1}$ so $\Mmat\Rmat_0 = \Imat$ and $\Cdhat = \Cd$. +This is as it must be: by Proposition~\ref{prop:degenerate} the unregularized fit reproduces the +observations exactly, so the modelled observations inherit their uncertainty unchanged. +\end{proof} + +\subsection{The unregularized case is efficient} + +\begin{theorem}[Cram\'er--Rao]\label{prop:crb} +At $\lambda = 0$, +\begin{equation} + \Cc = \Mmat^{-1}\Cd\Mmat^{-\mathsf{T}} = \left(\Mmat\transpose\Cd^{-1}\Mmat\right)^{-1} , +\end{equation} +which is the inverse Fisher information of the model $\dvec \sim +\mathcal{N}(\Mmat\cvec, \Cd)$. The unregularized estimator therefore attains the Cram\'er--Rao +lower bound. +\end{theorem} + +\begin{proof} +For the identity, note $(\Mmat^{-1}\Cd\Mmat^{-\mathsf{T}})^{-1} = \Mmat\transpose\Cd^{-1}\Mmat$ +by inverting each factor and reversing the order. For the Fisher information, the +log-likelihood is $\ell(\cvec) = -\tfrac{1}{2}(\dvec - \Mmat\cvec)\transpose\Cd^{-1}(\dvec - +\Mmat\cvec) + \text{const}$, so $\nabla_{\cvec}\ell = \Mmat\transpose\Cd^{-1}(\dvec - +\Mmat\cvec)$ and $-\nabla^2_{\cvec}\ell = \Mmat\transpose\Cd^{-1}\Mmat$, which is +deterministic and hence equal to its expectation. The estimator is unbiased (see +Proposition~\ref{prop:bias} with $\lambda = 0$), so the Cram\'er--Rao bound applies and is met with +equality. +\end{proof} + +\noindent No estimator, weighted or otherwise, can do better than $\Mmat^{-1}\dvec$ in the +unbiased class. Section~\ref{sec:degenerate} already told us weighting cannot help; this tells +us why nothing else can either. The only way to improve on \eqref{eq:varc} is to accept bias +--- which is exactly what regularization does. + +\subsection{Bias, resolution, and the two covariances} + +\begin{theorem}[Bias and resolution]\label{prop:bias} +$\Expect[\hat\cvec] = \Rmat_\lambda\Mmat\cvec_{\text{true}}$, so the estimator has bias +\begin{equation} + \mathbf{b} = (\Rmat_\lambda\Mmat - \Imat)\cvec_{\text{true}} + = -\lambda\,\Amat_\lambda^{-1}\Pmat\,\cvec_{\text{true}} , + \label{eq:bias} +\end{equation} +and \textbf{resolution matrix} +\begin{equation} + \Rmat_\lambda\Mmat = \Imat - \lambda\Amat_\lambda^{-1}\Pmat . + \label{eq:resolution} +\end{equation} +The mean-squared error is $\Expect[(\hat\cvec - \cvec_{\text{true}})(\cdot)\transpose] += \Cc + \mathbf{b}\mathbf{b}\transpose$. +\end{theorem} + +\begin{proof} +Substituting $\dvec = \Mmat\cvec_{\text{true}} + \bm{\varepsilon}$ with +$\Expect[\bm{\varepsilon}] = \bm 0$ into \eqref{eq:estimator} gives the first claim. Then +\[ + \Rmat_\lambda\Mmat - \Imat + = \Amat_\lambda^{-1}\Mmat\transpose\Cd^{-1}\Mmat - \Imat + = \Amat_\lambda^{-1}\!\left(\Mmat\transpose\Cd^{-1}\Mmat - \Amat_\lambda\right) + = -\lambda\Amat_\lambda^{-1}\Pmat , +\] +using \eqref{eq:A}. The MSE decomposition is the standard bias--variance identity +\parencite{hoerl_ridge_1970, aster_parameter_2018}. +\end{proof} + +\noindent The bias vanishes when $\lambda = 0$, and also whenever $\Pmat\cvec_{\text{true}} = +\bm 0$, i.e.\ when the true source field really is uniform --- the penalty is then telling the +truth and costs nothing. Since $\cvec_{\text{true}}$ is unknown, $\mathbf{b}$ cannot be +evaluated; the resolution matrix \eqref{eq:resolution} is the standard computable proxy +\parencite{menke_geophysical_2012}, equal to $\Imat$ at $\lambda = 0$ and degrading as +$\lambda$ grows. Its trace, $\operatorname{tr}(\Rmat_\lambda\Mmat)$, is the effective number of +degrees of freedom the data constrain. + +This bias is precisely why \eqref{eq:Cc} must be interpreted with care. It answers the +question ``if I repeated the measurements, how would my estimate scatter?'' --- and a heavily +damped estimator scatters very little while being systematically wrong. A second covariance +answers a different and often more useful question. + +\begin{theorem}[Posterior covariance]\label{prop:posterior} +Read the penalty as a Gaussian prior with precision $\lambda\Pmat$ and the likelihood as +$\dvec \mid \cvec \sim \mathcal{N}(\Mmat\cvec, \Cd)$. Then the posterior covariance is +$\Amat_\lambda^{-1}$, and +\begin{equation} + \boxed{\; + \Rmat_\lambda\Cd\Rmat_\lambda\transpose + \;=\; \Amat_\lambda^{-1} \;-\; \lambda\,\Amat_\lambda^{-1}\Pmat\Amat_\lambda^{-1} . + \;} + \label{eq:identity} +\end{equation} +Consequently $\Cc \preceq \Amat_\lambda^{-1}$, with equality if and only if $\lambda = 0$. +\end{theorem} + +\begin{proof} +For the linear-Gaussian model the posterior precision is the sum of the likelihood precision +$\Mmat\transpose\Cd^{-1}\Mmat$ and the prior precision $\lambda\Pmat$, giving $\Amat_\lambda$ +\parencite{tarantola_inverse_2005}. For the identity, substitute \eqref{eq:estimator} and use +$\Cd^{-1}\Cd\Cd^{-1} = \Cd^{-1}$: +\begin{align*} + \Rmat_\lambda\Cd\Rmat_\lambda\transpose + &= \Amat_\lambda^{-1}\Mmat\transpose\Cd^{-1}\,\Cd\,\Cd^{-1}\Mmat\Amat_\lambda^{-1} \\ + &= \Amat_\lambda^{-1}\left(\Mmat\transpose\Cd^{-1}\Mmat\right)\Amat_\lambda^{-1} \\ + &= \Amat_\lambda^{-1}\left(\Amat_\lambda - \lambda\Pmat\right)\Amat_\lambda^{-1} + = \Amat_\lambda^{-1} - \lambda\Amat_\lambda^{-1}\Pmat\Amat_\lambda^{-1}, +\end{align*} +using the symmetry of $\Amat_\lambda$. For the ordering, note that for any $\mathbf{x}$, +$\mathbf{x}\transpose\Amat_\lambda^{-1}\Pmat\Amat_\lambda^{-1}\mathbf{x} = +(\Amat_\lambda^{-1}\mathbf{x})\transpose\Pmat(\Amat_\lambda^{-1}\mathbf{x}) \ge 0$ by +Lemma~\ref{lem:P}, so the subtracted term is positive semi-definite. It is zero for all +$\mathbf{x}$ only if $\lambda = 0$, since $\Amat_\lambda^{-1}$ is non-singular and $\Pmat \ne +\bm 0$. +\end{proof} + +\noindent The propagated covariance is therefore always the smaller of the two, and quoting it +alone at large $\lambda$ understates the true error. The posterior covariance +$\Amat_\lambda^{-1}$ accounts for what the damping costs as well as what it stabilises, and is +the more honest error bar to report whenever $\lambda > 0$. At $\lambda = 0$ the two coincide +and both equal the Cram\'er--Rao bound of Proposition~\ref{prop:crb}. + +\section{Summary} + +\begin{table}[h] +\centering +\small +\begin{tabular}{@{}ll@{}} +\toprule +Quantity & Expression \\ +\midrule +Mixing matrix & $M_{ij} = \Theta_{ij}\alpha_{j\to i} q_j / Q_i$ \\ +Exact inverse & $c_i = \left(Q_i d_i - \sum_{p \in A(i)}\alpha_{p\to i}Q_p d_p\right)/q_i$ \\ +Amplification & $\kappa_i = Q_i/q_i = 1/M_{ii}$ \\ +Penalty operator & $\Pmat = \Imat - n^{-1}\onevec\onevec\transpose$ \\ +Normal matrix & $\Amat_\lambda = \Mmat\transpose\Cd^{-1}\Mmat + \lambda\Pmat$ \\ +Estimator & $\Rmat_\lambda = \Amat_\lambda^{-1}\Mmat\transpose\Cd^{-1}$ \\ +Source covariance & $\Cc = \Rmat_\lambda\Cd\Rmat_\lambda\transpose$ \\ +Prediction covariance & $\Cdhat = \Mmat\Cc\Mmat\transpose$ \\ +Posterior covariance & $\Amat_\lambda^{-1}$ \\ +Resolution & $\Rmat_\lambda\Mmat = \Imat - \lambda\Amat_\lambda^{-1}\Pmat$ \\ +Bias & $\mathbf{b} = -\lambda\Amat_\lambda^{-1}\Pmat\cvec_{\text{true}}$ \\ +\bottomrule +\end{tabular} +\caption{\textbf{Principal results.} At $\lambda = 0$: $\Rmat_0 = \Mmat^{-1}$, the resolution +matrix is $\Imat$, the bias vanishes, the two covariances coincide, and $\Cc$ attains the +Cram\'er--Rao bound.} +\end{table} + +\noindent The practical picture is this. The mixing matrix is invertible, so the unmixing +problem always has an exact solution --- but that solution differences neighbouring +observations, amplifying their errors by $\kappa_i = Q_i/q_i$, and interpolates the noise. +Regularization trades this variance for bias, and because the penalty is a homogeneous +quadratic the resulting estimator stays exactly linear in the data. That linearity is what +delivers closed-form covariances, a resolution matrix, and effective degrees of freedom, none +of which are available from the log-ratio formulation without Monte Carlo resampling. The price +is the assumption of absolute rather than relative misfit --- an assumption that +Remark~\ref{rem:logratio} shows is substantially repaired by weighting with a proportional +error model, and which is in any case mild for tracers of low log-variance such as isotopic +ratios \parencite{blondes_practical_2016}. For tracers whose information really is relative +rather than absolute --- that is, genuinely compositional data +\parencite{aitchison_statistical_1986} --- the log-ratio formulation remains the better choice. + +Two caveats bear repeating. Regularization is not optional: by +Proposition~\ref{prop:degenerate} the unregularized problem reproduces the data exactly, noise +included. And the analytical machinery of Section~\ref{sec:propagation} is valid only while +the constraint $\cvec \ge 0$ is inactive; when sites clamp, the estimator ceases to be linear +and the reported covariances describe the unconstrained estimator instead. + +\printbibliography + +\end{document} diff --git a/docs/preprint.sty b/docs/preprint.sty new file mode 100644 index 0000000..36a3ca4 --- /dev/null +++ b/docs/preprint.sty @@ -0,0 +1,281 @@ +\NeedsTeXFormat{LaTeX2e} + +\ProcessOptions\relax + +% Use roman fonts for equations if available +\IfFileExists{txfonts.sty}% + {\AtEndOfClass{\RequirePackage{txfonts}% + \gdef\ttdefault{cmtt}% + \let\iint\relax + \let\iiint\relax + \let\iiiint\relax + \let\idotsint\relax + \let\openbox\relax}}{\RequirePackage{times}} + + % other font configurations +\renewcommand{\rmdefault}{ptm} +\renewcommand{\sfdefault}{phv} + + +% Define page geometry +\RequirePackage[verbose=true,letterpaper]{geometry} +\AtBeginDocument{ + \newgeometry{ + textheight=9.3in, + textwidth=7.1in, + top=0.96in, + headheight=22.17pt, + headsep=0.14in, + footskip=3.61pt + } +} + +% Add background margin tick marks +\RequirePackage{background} +\SetBgScale{1} +\SetBgAngle{0} +\SetBgColor{black} +\SetBgContents{% +\begin{tikzpicture}[remember picture,overlay] +\node at (-3.55in,5.2in) {\rule{.4pt}{.4in}}; +\node at (3.55in,5.2in) {\rule{.4pt}{.4in}}; +\node at (-3.95in,4.8in) {\rule{.4in}{.4pt}}; +\node at (3.95in,4.8in) {\rule{.4in}{.4pt}}; +\node at (-3.55in,-5.2in) {\rule{.4pt}{.4in}}; +\node at (3.55in,-5.2in) {\rule{.4pt}{.4in}}; +\node at (-3.95in,-4.8in) {\rule{.4in}{.4pt}}; +\node at (3.95in,-4.8in) {\rule{.4in}{.4pt}}; +\end{tikzpicture}} + +\widowpenalty=10000 +\clubpenalty=10000 +\flushbottom +\sloppy + +% Header options +\RequirePackage{fancyhdr} +\fancyhf{} +\pagestyle{fancy} +\renewcommand{\headrulewidth}{0pt} +\fancyheadoffset{0pt} +\lhead{\scshape Preprint -- Using convex optimization to efficiently apportion tracer and pollutant sources} % Replace manual title with \@title but if title is too long it can overrun line +\rhead{\thepage} +%\rfoot{\thepage} + +%Handling Keywords +\def\keywordname{{\bfseries \emph Keywords}}% +\def\keywords#1{\par\addvspace\medskipamount{\rightskip=0pt plus1cm +\def\and{\ifhmode\unskip\nobreak\fi\ $\cdot$ +}\noindent\keywordname\enspace\ignorespaces#1\par}} + +% font sizes with reduced leading +\renewcommand{\normalsize}{% + \@setfontsize\normalsize\@xpt\@xipt + \abovedisplayskip 7\p@ \@plus 2\p@ \@minus 5\p@ + \abovedisplayshortskip \z@ \@plus 3\p@ + \belowdisplayskip \abovedisplayskip + \belowdisplayshortskip 4\p@ \@plus 3\p@ \@minus 3\p@ +} +\normalsize +\renewcommand{\small}{% + \@setfontsize\small\@ixpt\@xpt + \abovedisplayskip 6\p@ \@plus 1.5\p@ \@minus 4\p@ + \abovedisplayshortskip \z@ \@plus 2\p@ + \belowdisplayskip \abovedisplayskip + \belowdisplayshortskip 3\p@ \@plus 2\p@ \@minus 2\p@ +} +\renewcommand{\footnotesize}{\@setfontsize\footnotesize\@ixpt\@xpt} +\renewcommand{\scriptsize}{\@setfontsize\scriptsize\@viipt\@viiipt} +\renewcommand{\tiny}{\@setfontsize\tiny\@vipt\@viipt} +\renewcommand{\large}{\@setfontsize\large\@xiipt{14}} +\renewcommand{\Large}{\@setfontsize\Large\@xivpt{16}} +\renewcommand{\LARGE}{\@setfontsize\LARGE\@xviipt{20}} +\renewcommand{\huge}{\@setfontsize\huge\@xxpt{23}} +\renewcommand{\Huge}{\@setfontsize\Huge\@xxvpt{28}} + +% sections with less space +\providecommand{\section}{} +\renewcommand{\section}{% + \@startsection{section}{1}{\z@}% + {-2.0ex \@plus -0.5ex \@minus -0.2ex}% + { 1.5ex \@plus 0.3ex \@minus 0.2ex}% + {\large\scshape\raggedright}% +} +\providecommand{\subsection}{} +\renewcommand{\subsection}{% + \@startsection{subsection}{2}{\z@}% + {-1.8ex \@plus -0.5ex \@minus -0.2ex}% + { 0.8ex \@plus 0.2ex}% + {\normalsize\itshape\raggedright}% +} +\providecommand{\subsubsection}{} +\renewcommand{\subsubsection}{% + \@startsection{subsubsection}{3}{\z@}% + {-1.5ex \@plus -0.5ex \@minus -0.2ex}% + { 0.5ex \@plus 0.2ex}% + {\normalsize\itshape\raggedright}% +} +\providecommand{\paragraph}{} +\renewcommand{\paragraph}{% + \@startsection{paragraph}{4}{\z@}% + {1.5ex \@plus 0.5ex \@minus 0.2ex}% + {-1em}% + {\normalsize\bf}% +} +\providecommand{\subparagraph}{} +\renewcommand{\subparagraph}{% + \@startsection{subparagraph}{5}{\z@}% + {1.5ex \@plus 0.5ex \@minus 0.2ex}% + {-1em}% + {\normalsize\bf}% +} +\providecommand{\subsubsubsection}{} +\renewcommand{\subsubsubsection}{% + \vskip5pt{\noindent\normalsize\rm\raggedright}% +} + +% float placement +\renewcommand{\topfraction }{0.85} +\renewcommand{\bottomfraction }{0.4} +\renewcommand{\textfraction }{0.1} +\renewcommand{\floatpagefraction}{0.7} + +\newlength{\@abovecaptionskip}\setlength{\@abovecaptionskip}{7\p@} +\newlength{\@belowcaptionskip}\setlength{\@belowcaptionskip}{\z@} + +\setlength{\abovecaptionskip}{\@abovecaptionskip} +\setlength{\belowcaptionskip}{\@belowcaptionskip} + +% swap above/belowcaptionskip lengths for tables +\renewenvironment{table} + {\setlength{\abovecaptionskip}{\@belowcaptionskip}% + \setlength{\belowcaptionskip}{\@abovecaptionskip}% + \@float{table}} + {\end@float} + +% footnote formatting +\setlength{\footnotesep }{6.65\p@} +\setlength{\skip\footins}{9\p@ \@plus 4\p@ \@minus 2\p@} +\renewcommand{\footnoterule}{\kern-3\p@ \hrule width 12pc \kern 2.6\p@} +\setcounter{footnote}{0} + +% paragraph formatting +\setlength{\parindent}{\z@} +\setlength{\parskip }{5.5\p@} + +% list formatting +\setlength{\topsep }{4\p@ \@plus 1\p@ \@minus 2\p@} +\setlength{\partopsep }{1\p@ \@plus 0.5\p@ \@minus 0.5\p@} +\setlength{\itemsep }{2\p@ \@plus 1\p@ \@minus 0.5\p@} +\setlength{\parsep }{2\p@ \@plus 1\p@ \@minus 0.5\p@} +\setlength{\leftmargin }{3pc} +\setlength{\leftmargini }{\leftmargin} +\setlength{\leftmarginii }{2em} +\setlength{\leftmarginiii}{1.5em} +\setlength{\leftmarginiv }{1.0em} +\setlength{\leftmarginv }{0.5em} +\def\@listi {\leftmargin\leftmargini} +\def\@listii {\leftmargin\leftmarginii + \labelwidth\leftmarginii + \advance\labelwidth-\labelsep + \topsep 2\p@ \@plus 1\p@ \@minus 0.5\p@ + \parsep 1\p@ \@plus 0.5\p@ \@minus 0.5\p@ + \itemsep \parsep} +\def\@listiii{\leftmargin\leftmarginiii + \labelwidth\leftmarginiii + \advance\labelwidth-\labelsep + \topsep 1\p@ \@plus 0.5\p@ \@minus 0.5\p@ + \parsep \z@ + \partopsep 0.5\p@ \@plus 0\p@ \@minus 0.5\p@ + \itemsep \topsep} +\def\@listiv {\leftmargin\leftmarginiv + \labelwidth\leftmarginiv + \advance\labelwidth-\labelsep} +\def\@listv {\leftmargin\leftmarginv + \labelwidth\leftmarginv + \advance\labelwidth-\labelsep} +\def\@listvi {\leftmargin\leftmarginvi + \labelwidth\leftmarginvi + \advance\labelwidth-\labelsep} + +% create title +\providecommand{\maketitle}{} +\renewcommand{\maketitle}{% + \par + \begingroup + \renewcommand{\thefootnote}{\fnsymbol{footnote}} + % for perfect author name centering + \renewcommand{\@makefnmark}{\hbox to \z@{$^{\@thefnmark}$\hss}} + % The footnote-mark was overlapping the footnote-text, + % added the following to fix this problem (MK) + \long\def\@makefntext##1{% + \parindent 1em\noindent + \hbox to 1.8em{\hss $\m@th ^{\@thefnmark}$}##1 + } + \thispagestyle{empty} + \vspace*{-0.5cm} + \@maketitle + \@thanks + %\@notice + \endgroup + \let\maketitle\relax + \let\thanks\relax +} + +% create title (includes both anonymized and non-anonymized versions) +\providecommand{\@maketitle}{} +\renewcommand{\@maketitle}{% + \vbox{% + \hsize\textwidth + \linewidth\hsize + \centering + {\LARGE\sc \@title\par} + \vskip 0.1in + \textsc{Submitted, non-peer reviewed manuscript, compiled \today}\\ + \def\And{% + \end{tabular}\hfil\linebreak[0]\hfil% + \begin{tabular}[t]{c}\bf\rule{\z@}{24\p@}\ignorespaces% + } + \def\AND{% + \end{tabular}\hfil\linebreak[4]\hfil% + \begin{tabular}[t]{c}\bf\rule{\z@}{24\p@}\ignorespaces% + } + \begin{tabular}[t]{c}\bf\rule{\z@}{24\p@}\@author\end{tabular}% + \vskip 0.2in + } +} + +% add conference notice to bottom of first page +\newcommand{\ftype@noticebox}{8} +\newcommand{\@notice}{% + % give a bit of extra room back to authors on first page + \enlargethispage{2\baselineskip}% + \@float{noticebox}[b]% + \footnotesize\@noticestring% + \end@float% +} + +% abstract styling +\renewenvironment{abstract} +{ + \centerline + {\large \bfseries \scshape Abstract} + \begin{quote} +} +{ + \end{quote} +} + + +% plain language environment styling + +\newenvironment{plainlang} +{ + \centerline + {\large \bfseries \scshape Plain Language Summary} + \begin{quote} +} +{ + \end{quote} +} +\endinput From 8967a429431b297c59e38af774d55d480cb784d6 Mon Sep 17 00:00:00 2001 From: Alex Lipp Date: Tue, 25 Aug 2026 18:49:53 +0100 Subject: [PATCH 3/3] Update author affiliation in linear unmixing note Co-Authored-By: Claude Opus 5 --- docs/linear_unmixing_note.pdf | Bin 193588 -> 193597 bytes docs/linear_unmixing_note.tex | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/linear_unmixing_note.pdf b/docs/linear_unmixing_note.pdf index e9a534ceedfb5a6532ed5948c3e8163fe0893a99..90446fff2251013d549606c69014fcafc397f764 100644 GIT binary patch delta 5689 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 XV9W9roZv5j84o0k&&H;pp@{!qT8HS? delta 5679 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