From 2f6ec27eebefe4fc1b290d680b720af5ce02b46e Mon Sep 17 00:00:00 2001 From: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com> Date: Wed, 27 May 2026 20:42:31 +0200 Subject: [PATCH 1/3] Update .gitignore --- .gitignore | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 87ef7909..ef381743 100644 --- a/.gitignore +++ b/.gitignore @@ -259,20 +259,25 @@ codebase-map.html CLAUDE.md .luarc.json .mcp.json +/bart26g/.quarto /bart26g/_manuscript /bart26g/_freeze -bart26g/index.quarto_ipynb_5 -bart26g/index.quarto_ipynb_4 -bart26g/index.quarto_ipynb_3 -bart26g/index.quarto_ipynb_2 +/bart26g/runs +/bart26g/spotoptim_arxiv +bart26g/index.pdf +bart26g/index.tex bart26g/index.quarto_ipynb_1 -bart26g/index.quarto_ipynb_10 -bart26g/index.quarto_ipynb_11 -bart26g/index.quarto_ipynb_12 +bart26g/index.quarto_ipynb_2 +bart26g/index.quarto_ipynb_3 +bart26g/index.quarto_ipynb_4 +bart26g/index.quarto_ipynb_5 bart26g/index.quarto_ipynb_6 bart26g/index.quarto_ipynb_7 bart26g/index.quarto_ipynb_8 bart26g/index.quarto_ipynb_9 +bart26g/index.quarto_ipynb_10 +bart26g/index.quarto_ipynb_11 +bart26g/index.quarto_ipynb_12 bart26g/index.quarto_ipynb_13 bart26g/index.quarto_ipynb_14 bart26g/index.quarto_ipynb_15 @@ -281,8 +286,6 @@ bart26g/index.quarto_ipynb_17 bart26g/index.quarto_ipynb_18 bart26g/index.quarto_ipynb_19 bart26g/index.quarto_ipynb_20 -bart26g/index.pdf -bart26g/index.tex bart26g/index.quarto_ipynb_21 bart26g/index.quarto_ipynb_22 bart26g/index.quarto_ipynb_23 @@ -307,6 +310,4 @@ bart26g/index.quarto_ipynb_41 bart26g/index.quarto_ipynb_42 bart26g/index.quarto_ipynb_43 bart26g/index.quarto_ipynb_44 -/bart26g/runs bart26g/index.quarto_ipynb_45 -/bart26g/spotoptim_arxiv From aaca3ade013bc3e68971d25489e1458532eb3238 Mon Sep 17 00:00:00 2001 From: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com> Date: Wed, 27 May 2026 20:45:23 +0200 Subject: [PATCH 2/3] Add plot_only_corrected flag to plot_mmphi_corrected_vs_n_lhs When True, the function plots only the corrected Morris-Mitchell criterion on a single axis instead of the dual-axis comparison with the intensive criterion. Default remains False for backward compatibility. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/spotoptim/sampling/mm.py | 54 ++++++++++++++++++++++++++++++------ tests/test_mm.py | 50 +++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 8 deletions(-) diff --git a/src/spotoptim/sampling/mm.py b/src/spotoptim/sampling/mm.py index a89284cb..99952e43 100644 --- a/src/spotoptim/sampling/mm.py +++ b/src/spotoptim/sampling/mm.py @@ -1797,6 +1797,7 @@ def plot_mmphi_corrected_vs_n_lhs( n_step: int = 5, q_phi: float = 2.0, p_phi: float = 2.0, + plot_only_corrected: bool = False, ) -> None: """Generate LHS designs for varying n and plot the Corrected Morris-Mitchell Criterion against the standard criterion. @@ -1808,6 +1809,11 @@ def plot_mmphi_corrected_vs_n_lhs( two series are displayed on a shared x-axis with independent y-axes so their trends can be compared directly. + When ``plot_only_corrected`` is ``True``, the intensive criterion is + omitted: only ``hat_Phi_q`` is plotted on a single y-axis. This is the + preferred form when illustrating the asymptotic size-invariance of the + corrected criterion on its own scale. + The *corrected* criterion is asymptotically size-invariant: for large ``n`` its expected value stabilizes at a finite constant that depends only on the spatial distribution of the design, not on ``n`` itself. This plot makes @@ -1821,42 +1827,74 @@ def plot_mmphi_corrected_vs_n_lhs( n_step (int): Step size for increasing n. Defaults to 5. q_phi (float): Exponent q for the Morris-Mitchell criteria. Defaults to 2.0. p_phi (float): Distance norm p for the Morris-Mitchell criteria. Defaults to 2.0. + plot_only_corrected (bool): If ``True``, plot only the corrected + criterion ``hat_Phi_q`` on a single y-axis and skip the intensive + criterion entirely. Defaults to ``False``, which preserves the + original dual-axis comparison plot. Returns: - None: Displays a dual-axis plot of ``mmphi_intensive`` and - ``mmphi_corrected`` vs. number of samples (n). + None: Displays the plot. When ``plot_only_corrected`` is ``False`` the + figure has dual y-axes showing ``mmphi_intensive`` and + ``mmphi_corrected``; when ``True`` only the corrected curve is shown. Examples: >>> from spotoptim.sampling.mm import plot_mmphi_corrected_vs_n_lhs >>> plot_mmphi_corrected_vs_n_lhs(k_dim=3, seed=42, n_min=10, n_max=50, n_step=5, q_phi=2.0, p_phi=2.0) + >>> plot_mmphi_corrected_vs_n_lhs(k_dim=3, seed=42, n_min=10, n_max=50, plot_only_corrected=True) """ n_values = list(range(n_min, n_max + 1, n_step)) if not n_values: print("Warning: n_values list is empty. Check n_min, n_max, and n_step.") return - mmphi_intensive_results = [] - mmphi_corrected_results = [] + mmphi_intensive_results: list[float] = [] + mmphi_corrected_results: list[float] = [] lhs_sampler = LatinHypercube(d=k_dim, rng=seed) for n_points in n_values: if n_points < 2: print(f"Skipping n={n_points} as it's less than 2.") - mmphi_intensive_results.append(np.nan) + if not plot_only_corrected: + mmphi_intensive_results.append(np.nan) mmphi_corrected_results.append(np.nan) continue try: X_design = lhs_sampler.random(n=n_points) - phi_intensive, _, _ = mmphi_intensive(X_design, q=q_phi, p=p_phi) phi_corrected, _, _ = mmphi_corrected(X_design, q=q_phi, p=p_phi) - mmphi_intensive_results.append(phi_intensive) mmphi_corrected_results.append(phi_corrected) + if not plot_only_corrected: + phi_intensive, _, _ = mmphi_intensive(X_design, q=q_phi, p=p_phi) + mmphi_intensive_results.append(phi_intensive) except Exception as e: print(f"Error calculating for n={n_points}: {e}") - mmphi_intensive_results.append(np.nan) + if not plot_only_corrected: + mmphi_intensive_results.append(np.nan) mmphi_corrected_results.append(np.nan) fig, ax1 = plt.subplots(figsize=(9, 6)) + if plot_only_corrected: + color = "tab:blue" + ax1.set_xlabel("Number of Samples (n)") + ax1.set_ylabel("mmphi_corrected (hat_Phiq)", color=color) + ax1.plot( + n_values, + mmphi_corrected_results, + color=color, + marker="x", + linestyle="--", + label="mmphi_corrected (hat_Phiq)", + ) + ax1.tick_params(axis="y", labelcolor=color) + ax1.grid(True, linestyle="--", alpha=0.7) + fig.tight_layout() + plt.title( + f"Corrected Morris-Mitchell Criterion vs. Number of Samples (n)\n" + f"LHS (k={k_dim}, q={q_phi}, p={p_phi})" + ) + ax1.legend(loc="best") + plt.show() + return + color = "tab:red" ax1.set_xlabel("Number of Samples (n)") ax1.set_ylabel("mmphi_intensive (PhiqI)", color=color) diff --git a/tests/test_mm.py b/tests/test_mm.py index a2b916ff..680964f8 100644 --- a/tests/test_mm.py +++ b/tests/test_mm.py @@ -617,6 +617,56 @@ def test_plot_mmphi_corrected_vs_n_lhs_higher_dim(monkeypatch): plot_mmphi_corrected_vs_n_lhs(k_dim=5, seed=1, n_min=10, n_max=20, n_step=10) +def test_plot_mmphi_corrected_vs_n_lhs_only_corrected(monkeypatch): + """plot_only_corrected=True draws a single-axis figure with just the corrected curve.""" + import matplotlib.pyplot as plt + + monkeypatch.setattr(plt, "show", lambda: None) + captured_figs: list = [] + original_subplots = plt.subplots + + def _capture_subplots(*args, **kwargs): + fig, ax = original_subplots(*args, **kwargs) + captured_figs.append((fig, ax)) + return fig, ax + + monkeypatch.setattr(plt, "subplots", _capture_subplots) + plot_mmphi_corrected_vs_n_lhs( + k_dim=2, seed=0, n_min=10, n_max=20, n_step=5, plot_only_corrected=True + ) + assert captured_figs, "plt.subplots was never called" + fig, ax = captured_figs[-1] + # Single-axis figure: exactly one Axes on the figure. + assert len(fig.axes) == 1 + # The single curve plotted is the corrected criterion. + assert len(ax.get_lines()) == 1 + (line,) = ax.get_lines() + assert "corrected" in line.get_label().lower() + plt.close(fig) + + +def test_plot_mmphi_corrected_vs_n_lhs_default_is_dual_axis(monkeypatch): + """Default plot_only_corrected=False still produces a dual-axis figure.""" + import matplotlib.pyplot as plt + + monkeypatch.setattr(plt, "show", lambda: None) + captured_figs: list = [] + original_subplots = plt.subplots + + def _capture_subplots(*args, **kwargs): + fig, ax = original_subplots(*args, **kwargs) + captured_figs.append((fig, ax)) + return fig, ax + + monkeypatch.setattr(plt, "subplots", _capture_subplots) + plot_mmphi_corrected_vs_n_lhs(k_dim=2, seed=0, n_min=10, n_max=20, n_step=5) + assert captured_figs, "plt.subplots was never called" + fig, _ = captured_figs[-1] + # twinx() adds a second Axes sharing the x-axis. + assert len(fig.axes) == 2 + plt.close(fig) + + def test_plot_mmphi_corrected_vs_n_lhs_ratio_identity(): """mmphi_corrected equals mmphi_intensive scaled by (M / n^{1+q/k})^{1/q}. From c7553153460e2902d5b3ba8e119a79e99b817a47 Mon Sep 17 00:00:00 2001 From: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com> Date: Wed, 27 May 2026 22:53:36 +0200 Subject: [PATCH 3/3] test: widen parallel_max_time ceiling to reduce CI flakiness The assertion intent is "the loop terminated", not a tight timing budget. Shared CI runners occasionally stall the parallel pool teardown by several seconds, producing spurious failures (latest observed: 10.44s on a 10.0s ceiling for a 3s max_time target). Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/test_termination_criteria.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_termination_criteria.py b/tests/test_termination_criteria.py index f3568cb4..8f3bebfb 100644 --- a/tests/test_termination_criteria.py +++ b/tests/test_termination_criteria.py @@ -249,8 +249,10 @@ def slow_sphere(x): # It should stop roughly around 3 seconds assert elapsed >= 2.5 - # It shouldn't run forever - assert elapsed < 10.0 + # It shouldn't run forever. The ceiling is generous because shared CI + # runners can stall the parallel pool teardown by several seconds; the + # assertion's intent is "the loop terminated", not a tight timing budget. + assert elapsed < 20.0 def test_verbose_output_sequential(self, capsys): """Test Case 8: Verbose output in sequential mode."""