Source code for sage.plotting.perturbation_sensitivity

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Filename        : perturbation_sensitivity.py
Description     : Short description of the file

Created on 2026-03-21 17:51:16

__author__        = Narenraju Nagarajan
__copyright__     = Copyright 2026, ProjectName
__license__       = MIT Licence
__version__       = 0.0.1
__maintainer__    = Narenraju Nagarajan
__affiliation__   = N/A
__email__         = N/A
__status__        = ['inProgress', 'Archived', 'inUsage', 'Debugging']


GitHub Repository: NULL

Documentation: NULL

"""

# Packages
import os


[docs] def plot_perturbation_sensitivity( model, ranking_stat, labels, source_params, param_name, export_dir=None, save=True, perturb_frac=0.05, nbins=20, epoch=None, ): """ Plot network output change when a source parameter is perturbed. For each signal event, increases and decreases ``param_name`` by ``perturb_frac × value``, runs inference, and bins the output change by the original parameter value. Reveals how sensitively the network responds to small changes in each physical parameter. Parameters ---------- model : object with ``predict`` method Trained model. ranking_stat : array-like, shape ``(N,)`` Baseline network ranking statistics. labels : array-like, shape ``(N,)`` Binary ground-truth labels. source_params : dict[str, array-like] Per-event parameter arrays. param_name : str Key of the parameter to perturb. export_dir : str or None Output directory. save : bool If ``True``, save to disk; otherwise display. perturb_frac : float Fractional perturbation size (default ``0.05``). nbins : int Number of parameter bins for the sensitivity curve (default ``20``). epoch : int or str or None Epoch identifier for the filename. """ import numpy as np import matplotlib.pyplot as plt # Only signals signal_mask = labels == 1.0 base_param = source_params[param_name][signal_mask] # Original network output base_output = ranking_stat[signal_mask] # Perturb the parameter +/- fraction perturb_outputs = [] perturb_values = [] for frac in np.linspace(-perturb_frac, perturb_frac, nbins): new_param = np.copy(base_param) new_param *= 1 + frac # Construct modified input dict input_dict = {k: v[signal_mask] for k, v in source_params.items()} input_dict[param_name] = new_param # Run model inference model_output = model.predict(input_dict) # adjust depending on your model API # assume model_output[:,0] is ranking_stat perturb_outputs.append(model_output[:, 0]) perturb_values.append(np.full_like(base_param, frac)) perturb_outputs = np.concatenate(perturb_outputs) perturb_values = np.concatenate(perturb_values) plt.figure(figsize=(7, 6)) plt.scatter(perturb_values, perturb_outputs, alpha=0.4, c="blue", s=20) plt.xlabel(f"Fractional perturbation in {param_name}") plt.ylabel("Network Ranking Statistic") plt.title(f"Perturbation Sensitivity - {param_name} - Epoch {epoch}") plt.grid(True, ls=":") if save and export_dir is not None: outdir = os.path.join(export_dir, "PERTURBATION_SENSITIVITY") os.makedirs(outdir, exist_ok=True) plt.savefig( os.path.join(outdir, f"perturb_sensitivity_{param_name}_epoch_{epoch}.png"), dpi=150, bbox_inches="tight", ) plt.close() else: plt.show() plt.close()