diff --git a/autofit/non_linear/plot/samples_plotters.py b/autofit/non_linear/plot/samples_plotters.py index 773879f8d..df8b5356b 100644 --- a/autofit/non_linear/plot/samples_plotters.py +++ b/autofit/non_linear/plot/samples_plotters.py @@ -9,6 +9,27 @@ logger = logging.getLogger(__name__) +def _corner_range_from(data): + """Per-column ``(min, max)`` plot ranges for a corner figure. + + ``corner`` raises "no dynamic range" if any column has zero spread (every + sample equal). That is a legitimate input — a reduced-iteration run (e.g. + ``PYAUTO_TEST_MODE=1``) or a parameter that has converged flat — so we hand + it an explicit per-column range rather than let it crash. Columns with real + spread keep their ``(min, max)``; degenerate columns are widened to a small + non-zero window centred on the value so the plot still renders. + """ + mins = np.asarray(data).min(axis=0) + maxs = np.asarray(data).max(axis=0) + plot_range = [] + for lower, upper in zip(mins, maxs): + if lower == upper: + pad = max(abs(lower) * 1e-4, 1e-8) + lower, upper = lower - pad, upper + pad + plot_range.append((lower, upper)) + return plot_range + + @skip_in_test_mode def corner_cornerpy(samples, path=None, filename="corner", format="show", **kwargs): data = np.asarray(samples.parameter_lists) @@ -34,6 +55,7 @@ def corner_cornerpy(samples, path=None, filename="corner", format="show", **kwar data=data, weight_list=samples.weight_list, labels=samples.model.parameter_labels_with_superscripts_latex, + range=_corner_range_from(data), ) output_figure(path=path, filename=filename, format=format) diff --git a/test_autofit/non_linear/plot/__init__.py b/test_autofit/non_linear/plot/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test_autofit/non_linear/plot/test_samples_plotters.py b/test_autofit/non_linear/plot/test_samples_plotters.py new file mode 100644 index 000000000..7ca9c2773 --- /dev/null +++ b/test_autofit/non_linear/plot/test_samples_plotters.py @@ -0,0 +1,43 @@ +import numpy as np + +from autofit.non_linear.plot.samples_plotters import _corner_range_from + + +def test__corner_range__real_spread_columns_preserved(): + data = np.array( + [ + [1.0, 10.0], + [2.0, 20.0], + [3.0, 30.0], + ] + ) + + plot_range = _corner_range_from(data) + + assert plot_range == [(1.0, 3.0), (10.0, 30.0)] + + +def test__corner_range__degenerate_columns_widened_to_nonzero_span(): + # Columns 0, 1, 2 have no dynamic range (all samples equal) — the input + # that made `corner` raise "no dynamic range". Column 3 has real spread. + data = np.array( + [ + [0.0, 5.0, -2.0, 1.0], + [0.0, 5.0, -2.0, 2.0], + [0.0, 5.0, -2.0, 3.0], + ] + ) + + plot_range = _corner_range_from(data) + + # Every column ends up with a strictly positive width, so `corner` gets a + # valid range for all of them and does not raise. + for lower, upper in plot_range: + assert upper > lower + + # Degenerate windows stay centred on the constant value. + for (lower, upper), value in zip(plot_range[:3], [0.0, 5.0, -2.0]): + assert lower < value < upper + + # The real-spread column is left untouched. + assert plot_range[3] == (1.0, 3.0)