From 5557978145297fb00caa10f15476fdc7c3eda69e Mon Sep 17 00:00:00 2001 From: Lawrence Bird Date: Wed, 17 Jun 2026 06:12:09 +0800 Subject: [PATCH] Update pyissm.inversion docs --- src/pyissm/inversion/__init__.py | 4 +- src/pyissm/inversion/metrics.py | 47 ++++++++++++++++ src/pyissm/inversion/plot.py | 49 +++++++++++++++-- src/pyissm/inversion/sensitivity.py | 84 ++++++++++++++++++++++++----- 4 files changed, 164 insertions(+), 20 deletions(-) diff --git a/src/pyissm/inversion/__init__.py b/src/pyissm/inversion/__init__.py index 1028b37e..55f3bfe9 100644 --- a/src/pyissm/inversion/__init__.py +++ b/src/pyissm/inversion/__init__.py @@ -1,7 +1,7 @@ """ -Inversion module for pyISSM. +Inversion subpackage for pyISSM. -This module contains tools for performing inversions, including sensitivity analyses and convergence diagnostics. +This subpackage contains tools for performing inversions, including sensitivity analyses and convergence diagnostics. """ from . import metrics, plot, sensitivity \ No newline at end of file diff --git a/src/pyissm/inversion/metrics.py b/src/pyissm/inversion/metrics.py index f43a3054..d494b46f 100644 --- a/src/pyissm/inversion/metrics.py +++ b/src/pyissm/inversion/metrics.py @@ -14,6 +14,8 @@ def cost_function_values(md): """ Extract final inversion cost-function values. + Extracts the final cost-function values for each cost function and the total cost from the model results. + Parameters ---------- md : :class: `pyissm.Model` @@ -23,6 +25,12 @@ def cost_function_values(md): ------- :class:`dict` Dictionary containing final cost-function values for each cost function and total cost. + + Example + ------- + : code-block:: python + + >>> final_costs = pyissm.inversion.metrics.cost_function_values(md) """ # Get cost function history from model results @@ -48,6 +56,9 @@ def cost_function_ratios(md): """ Compute ratios between all inversion cost functions. + These ratios can provide insight into the relative contributions of different cost functions + to the total cost and help identify which cost functions are dominating the inversion results. + Parameters ---------- md : :class:`pyissm.Model` @@ -57,6 +68,12 @@ def cost_function_ratios(md): ------- :class:`dict` Dictionary of pairwise cost-function ratios. + + Example + ------- + : code-block:: python + + >>> cost_ratios = pyissm.inversion.metrics.cost_function_ratios(md) """ # Get individual cost values @@ -97,6 +114,9 @@ def extract_convergence_history(md): """ Extract convergence history of cost functions. + Extracts the history of cost-function values across iterations from the modelresults and + organizes it into a pandas DataFrame for easier analysis and visualization of convergence behavior. + Parameters ---------- md : :class:`pyissm.Model` @@ -105,6 +125,12 @@ def extract_convergence_history(md): ------- :class:`pandas.DataFrame` DataFrame containing cost function history. + + Example + ------- + : code-block:: python + + >>> convergence_history = pyissm.inversion.metrics.extract_convergence_history(md) """ # Get cost function history from model results @@ -134,6 +160,10 @@ def velocity_residual_area_metrics(md): """ Compute positive/negative residual area metrics. + Calculates the total area of elements with positive and negative velocity residuals, + as well as their fractions relative to the total area. This provides insight into the + spatial distribution of model-data misfit in terms of velocity residuals. + Parameters ---------- md : :class:`pyissm.Model` @@ -143,6 +173,12 @@ def velocity_residual_area_metrics(md): ------- :class:`dict` Dictionary containing the computed metrics. + + Example + ------- + : code-block:: python + + >>> area_metrics = pyissm.inversion.metrics.velocity_residual_area_metrics(md) """ # Compute velocity residuals at vertices @@ -205,11 +241,16 @@ def field_smoothness_metrics(md, field = None): """ Compute field smoothness metrics. + + Calculates the area-weighted mean and RMSE of the gradient magnitude (slope) of a specified field. + This provides insight into the spatial variability and smoothness of the control parameter field, + which can be important for understanding inversion results and regularization effects. Parameters ---------- md : :class:`pyissm.Model` ISSM model object containing mesh and (optionally) solution data. + field : :class:`numpy.ndarray` Field values, defined on vertices. If None, will attempt to extract from model results based on control parameter. @@ -217,6 +258,12 @@ def field_smoothness_metrics(md, ------- :class:`dict` Dictionary containing smoothness metrics. + + Example + ------- + : code-block:: python + + >>> smoothness_metrics = pyissm.inversion.metrics.field_smoothness_metrics(md) """ # Extract field from model if not provided diff --git a/src/pyissm/inversion/plot.py b/src/pyissm/inversion/plot.py index 6011e776..2e9163bc 100644 --- a/src/pyissm/inversion/plot.py +++ b/src/pyissm/inversion/plot.py @@ -1,3 +1,9 @@ +""" +Inversion plotting tools for pyISSM. + +This module contains various functions for plotting inversion diagnostics for ISSM models. +""" + import numpy as np import matplotlib.pyplot as plt import textwrap @@ -48,6 +54,14 @@ def plot_convergence_history(convergence_history, ----- This function does not validate metric names. A missing metric column will raise a pandas ``KeyError`` during plotting. + + Example + ------- + : code-block:: python + + >>> convergence_history = pyissm.inversion.metrics.extract_convergence_history(md) + >>> fig, ax = plot_convergence_history(convergence_history) + """ ## Is an ax passed? @@ -150,6 +164,14 @@ def plot_sensitivity_heatmap(diagnostics, ----- The heatmap is generated via :meth:`pandas.DataFrame.pivot_table` using ``y`` as the index, ``x`` as the columns, and ``value`` as cell values. + + Example + ------- + : code-block:: python + + >>> manifest = pd.read_csv('./sensitivity_runs/manifest.csv') + >>> diagnostics = pyissm.inversion.sensitivity.compute_sensitivity_diagnostics(manifest, output_dir = './sensitivity_runs') + >>> fig, ax = pyissm.inversion.plot.plot_sensitivity_heatmap(diagnostics, x = 'cf101', y = 'cf103', value = 'vel_rmse') """ ## Is an ax passed? @@ -210,16 +232,25 @@ def plot_run_summary(md, ---------- md : :class:`pyissm.Model` ISSM model object containing inversion results. + output_pdf : :class:`str` Path to output PDF file. + diagnostics : :class:`dict`, optional - Diagnostics configuration. Default is None. + Model diagnostics. Default is None. Computed internally if not provided. + plot_kwargs : :class:`dict`, optional Plot keyword arguments. Default is None. Returns ------- - None + None. Saves the generated report to the specified PDF file. + + Example + ------- + : code-block:: python + + >>> plot.plot_run_summary(md, output_pdf = f'./sensitivity_runs/run_001_1_1/run_001_1_summary.pdf') """ # Compute velocity residuals for diagnostics and plotting @@ -486,9 +517,9 @@ def plot_lcurve(diagnostics, Column containing regularization coefficient values. If None, automatically inferred from regularization term: - cost_501 -> cf501 - cost_502 -> cf502 - cost_503 -> cf503 + - cost_501 -> cf501 + - cost_502 -> cf502 + - cost_503 -> cf503 misfit_cols : list of :class:`str`, optional Cost-function columns contributing to model-data misfit. @@ -533,6 +564,14 @@ def plot_lcurve(diagnostics, ax : :class:`matplotlib.axes.Axes` The axes containing the plot. If an axes was passed in, this is the same object; if no axes was passed, this is the newly created axes. + + Example + ------- + : code-block:: python + + >>> manifest = pd.read_csv('./sensitivity_runs/manifest.csv') + >>> diagnostics = pyissm.inversion.sensitivity.compute_sensitivity_diagnostics(manifest, output_dir = './sensitivity_runs') + >>> fig, ax = plot_lcurve(diagnostics) """ ## Is an ax passed? diff --git a/src/pyissm/inversion/sensitivity.py b/src/pyissm/inversion/sensitivity.py index 9e29ca13..f95397bd 100644 --- a/src/pyissm/inversion/sensitivity.py +++ b/src/pyissm/inversion/sensitivity.py @@ -42,6 +42,15 @@ def build_parameter_grid(coefficients, initial_run_id = 1): - run_id - cfXXX columns for each cost function - run_name + + Example + ------- + : code-block:: python + + >>> coefficients = {101: [1, 10, 100, 1000], + ... 103: [1, 10, 100, 1000]} + >>> param_grid = pyissm.inversion.sensitivity.build_parameter_grid(coefficients) + >>> param_grid = pyissm.inversion.sensitivity.build_parameter_grid(coefficients, initial_run_id = 101) """ # Sort cost functions for consistent ordering @@ -107,9 +116,8 @@ def assign_cost_functions(md, global_mask : :class:`numpy.ndarray` of :class:`bool`, optional Global boolean mask applied to all cost functions. - - True -> coefficient active - False -> coefficient set to zero + - True -> coefficient active + - False -> coefficient set to zero coeff_masks : :class:`dict`, optional Dictionary mapping cost-function IDs to boolean masks. @@ -124,6 +132,20 @@ def assign_cost_functions(md, ------- md : :class:`pyissm.Model` Updated ISSM model. + + Example + ------- + : code-block:: python + + >>> coefficients = {101: 1000, + ... 103: 25, + ... 502: 1e-19} + >>> global_mask = md.inversion.vel_obs > 0 + >>> md = pyissm.inversion.sensitivity.assign_cost_functions(md, coefficients, global_mask = global_mask) + + >>> coeff_masks = {101: md.inversion.vel_obs > 0, + ... 103: md.mask.ice_levelset > 0} + >>> md = pyissm.inversion.sensitivity.assign_cost_functions(md, coefficients, coeff_masks = coeff_masks) """ # Sort cost functions for consistent ordering @@ -209,8 +231,8 @@ def parameter_sensitivity(md, **NOTE:** This is intentionally separate from: md.cluster.executionpath: - - md.cluster.executionpath controls where ISSM stages runtime files for execution. - - output_dir controls where pyISSM stores organized experiment outputs. + - `md.cluster.executionpath` controls where ISSM stages runtime files for execution. + - `output_dir` controls where pyISSM stores organized experiment outputs. run : :class:`bool`, default = True Submit inversion runs. @@ -220,9 +242,8 @@ def parameter_sensitivity(md, global_mask : :class:`numpy.ndarray` of :class:`bool`, optional Global boolean mask applied to all cost functions. Passed to :func:`assign_cost_functions`. - - True -> coefficient active - False -> coefficient set to zero + - True -> coefficient active + - False -> coefficient set to zero coeff_masks : :class:`dict`, optional Dictionary mapping cost-function IDs to boolean masks. Passed to :func:`assign_cost_functions`. @@ -236,6 +257,15 @@ def parameter_sensitivity(md, ------- :class:`pandas.DataFrame` Experiment manifest. + + Example + ------- + : code-block:: python + + >>> coefficients = {101: [1, 10, 100, 1000], + ... 103: [1, 10, 100, 1000]} + >>> parameter_grid = pyissm.inversion.sensitivity.build_parameter_grid(coefficients) + >>> manifest = pyissm.inversion.sensitivity.parameter_sensitivity(md, parameter_grid, output_dir = './sensitivity_runs', run = True, load_only = False) """ # Prevent ambiguous execution mode @@ -365,7 +395,7 @@ def parameter_sensitivity(md, def load_parameter_sensitivity_run(manifest_row): """ - Load a parameter sensitivity run. + Load a parameter sensitivity run from a manifest row. Parameters ---------- @@ -375,6 +405,13 @@ def load_parameter_sensitivity_run(manifest_row): ------- md : :class:`pyissm.Model` Loaded ISSM model for the specified run. + + Example + ------- + : code-block:: python + + >>> manifest = pd.read_csv('./sensitivity_runs/manifest.csv') + >>> md = pyissm.inversion.sensitivity.load_parameter_sensitivity_run(manifest.iloc[0]) """ # Extract run directory and name from manifest row @@ -397,17 +434,31 @@ def compute_sensitivity_diagnostics(manifest, output_dir = None, plot_run_summaries = True): """ - Compute diagnostics for parameter sensitivity runs. + Compute diagnostics for parameter sensitivity runs from a manifest. Parameters ---------- manifest : :class:`pandas.DataFrame` Output from :func:`pyissm.inversion.sensitivity.parameter_sensitivity`. + output_dir : :class:`str` or :class:`pathlib.Path`, optional + Directory used to store computed diagnostics. If None, diagnostics are not saved to disk. + + plot_run_summaries : :class:`bool`, default = True + Whether to generate summary PDF reports for each run, including diagnostic plots and convergence history. If True, summary PDFs are saved in each run's directory. + Returns ------- :class:`pandas.DataFrame` Diagnostics table. + + Example + ------- + : code-block:: python + + >>> manifest = pd.read_csv('./sensitivity_runs/manifest.csv') + >>> diagnostics = pyissm.inversion.sensitivity.compute_sensitivity_diagnostics(manifest, output_dir = './sensitivity_runs/diagnostics') + """ # Copy manifest so coefficient metadata is preserved @@ -496,9 +547,8 @@ def normalize_diagnostics(diagnostics, higher_is_better : :class:`dict`, optional Dictionary mapping column name -> bool. - - True = higher values are better - False = lower values are better + - True = higher values are better + - False = lower values are better Example ------- @@ -515,6 +565,14 @@ def normalize_diagnostics(diagnostics, ------- :class:`pandas.DataFrame` Copy of dataframe with normalized columns added. + + Example + ------- + : code-block:: python + + >>> manifest = pd.read_csv('./sensitivity_runs/manifest.csv') + >>> diagnostics = pyissm.inversion.sensitivity.compute_sensitivity_diagnostics(manifest, output_dir = './sensitivity_runs/diagnostics') + >>> diagnostics_norm = pyissm.inversion.sensitivity.normalize_diagnostics(diagnostics, columns = ['vel_rmse', 'thickness_rmse']) """ # Create a copy of the diagnostics DataFrame to avoid modifying the original