diff --git a/PLAN.md b/PLAN.md index 9af0b55..2e51fed 100644 --- a/PLAN.md +++ b/PLAN.md @@ -106,6 +106,9 @@ replace project documentation. living-article revisions to “Why Add Q to MC?”. - [x] Verify last-revised dates, authors, local links, code, mathematics, and all 48 images in a complete local render and browser review. +- [x] Add the two May 2026 QMCPy documentation blogs on iteration logs and + resuming integrations, retaining links to their executable notebooks. +- [x] Expand the current website archive from the historic 18 posts to 20 posts. - [ ] Define and verify redirects or canonical handling for legacy URLs. - [ ] Publish the complete archive only after collaborator review. @@ -176,8 +179,11 @@ replace project documentation. with QMCSoftware collaborators. - [ ] Record requested changes and resolve launch-blocking issues. - [ ] Agree on ownership and cadence for blog, news, and community updates. -- [x] Authorize and locally validate the 18-post blog migration. -- [x] Approve and publish the completed 18-post archive. +- [x] Authorize and locally validate the historic 18-post blog migration. +- [x] Approve and publish the completed historic 18-post archive. +- [x] Authorize migration of the two newer resume and iteration-log blogs. +- [ ] Approve and publish the completed 20-post archive after pull-request + review. ### Phase 12 — Coordinate work across machines diff --git a/STATUS.md b/STATUS.md index 4555e5d..cb9e694 100644 --- a/STATUS.md +++ b/STATUS.md @@ -32,17 +32,19 @@ - [x] Added repository guidance, roadmap, ignore rules, and local instructions. - [x] Rendered the complete 13-page site and verified local link targets and rendered `CNAME` preservation. -- [x] Migrated the complete 18-post QMCPy blog archive into self-contained - Quarto posts while retaining the technical documentation and notebooks in - the QMCPy repository. +- [x] Migrated the complete historic 18-post QMCPy blog archive into + self-contained Quarto posts while retaining the technical documentation and + notebooks in the QMCPy repository. +- [x] Added the two newer QMCPy documentation blogs, “Stop Re-running” and + “How Much Accuracy Do You Need?”, bringing the current archive to 20 posts. - [x] Standardized blog chronology on one last-revised `date` per post, with no separate first-publication field, and retained the approved revised version of “Why Add Q to MC?”. -- [x] Preserved and published all 48 blog images in the local render, expanded - MkDocs code snippets, converted callouts and image groups, and normalized - legacy mathematical delimiters for Quarto. -- [x] Rendered all 30 site pages and completed local link, desktop, mobile, and - per-post browser checks for the 18-post archive. +- [x] Preserved all 48 historic blog images and added two result figures for + the newer iteration-log article; expanded MkDocs code snippets, converted + callouts and image groups, and normalized mathematical delimiters for Quarto. +- [x] Rendered all 33 site pages and completed local link, desktop, and mobile + checks for the 20-post archive. - [x] Widened the shared blog reading column modestly and made title metadata responsive so realistic author names and dates wrap without clipping. - [x] Added a responsive, YAML-driven QMC Software Directory under Community, @@ -73,7 +75,9 @@ See [notes/NEXT.md](notes/NEXT.md) for the immediate operational handoff. link to their respective repositories and documentation. - The site uses `qmcsoftware.org` as its canonical URL and custom domain; redirect behavior from the former domain still requires verification. -- The complete 18-post QMCPy blog archive is published on the Website. +- The historic 18-post QMCPy blog archive is published on the Website; the two + May 2026 posts are migrated in a review branch, with publication still + approval-gated. - Blog cards and title blocks use each post's last-revised date; the archive is sorted newest revision first and does not carry a separate first-published date. diff --git a/blogs/iteration-log/figures/classic-loop.png b/blogs/iteration-log/figures/classic-loop.png new file mode 100644 index 0000000..9232268 Binary files /dev/null and b/blogs/iteration-log/figures/classic-loop.png differ diff --git a/blogs/iteration-log/figures/iteration-log-resume.png b/blogs/iteration-log/figures/iteration-log-resume.png new file mode 100644 index 0000000..8430b09 Binary files /dev/null and b/blogs/iteration-log/figures/iteration-log-resume.png differ diff --git a/blogs/iteration-log/index.qmd b/blogs/iteration-log/index.qmd new file mode 100644 index 0000000..adf5070 --- /dev/null +++ b/blogs/iteration-log/index.qmd @@ -0,0 +1,304 @@ +--- +title: "Stop Re-running: Efficient Numerical Integration via Solver Log and Resumption" +author: "Sou-Cheng Choi" +date: 2026-05-04 +date-format: "MMMM D, YYYY" +description: "Comparing repeated tolerance sweeps with QMCPy's iteration-log and resume workflow." +categories: + - "QMCPy" + - "Performance" + - "Stopping Criteria" +image: figures/iteration-log-resume.png +--- + +This post compares a classic tolerance sweep with QMCPy's iteration-log and +resume workflow for high-dimensional numerical integration. + +The executable source is the +[`Iteration_Log_Tolerance_Demo.ipynb`](https://github.com/QMCSoftware/QMCSoftware/blob/develop/demos/demo_resume_data/Iteration_Log_Tolerance_Demo.ipynb) +notebook in the QMCPy repository. + +In high-dimensional integration, achieving high precision in the solution +estimate often requires solving the same problem across a wide range of +tolerances ($\varepsilon$). Traditionally, this meant running the entire +simulation multiple times, leading to prohibitive computational costs. This +demo shows how QMCPy's resume feature and internal solver logs can reduce +redundant computation while maintaining the requested accuracy. + +::: {.callout-note} +The runtimes below are empirical outputs saved in the source notebook and will +vary by machine and software environment. They are not theoretical performance +guarantees. The sample counts correspond to the stated methods, tolerances, and +random seed. +::: + +## Approach 1: Classic loop + +Following the setup in the +[`MCQMC2022_Article_Figures.ipynb`](https://github.com/QMCSoftware/QMCSoftware/blob/develop/demos/talk_paper_demos/MCQMC2022_Article_Figures/MCQMC2022_Article_Figures.ipynb) +notebook, we create two tolerance plots: + +1. Time versus tolerance. +2. Number of samples, $n$, versus tolerance. + +Both use log-log axes and compare the lattice results with an +$\mathcal{O}(\varepsilon^{-1})$ reference trend. + +This naive approach re-runs the solver for every target tolerance, so it +discards work completed at earlier tolerances. + +```python +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt +from matplotlib.ticker import FuncFormatter +from time import perf_counter +import qmcpy as qp + +tol0, n_tol = 1e-3, 9 +tol = np.array([tol0 / (2**i) for i in range(n_tol)]) + +def run_lattice_tolerance_curve(seed): + integ = qp.Keister(qp.Gaussian(qp.Lattice(3, seed=seed))) + times, ns = [], [] + for eps in tol: + _, data = qp.CubQMCLatticeG( + integ, abs_tol=float(eps) + ).integrate() + times.append(float(data.time_integrate)) + ns.append(float(data.n_total)) + return np.asarray(times), np.asarray(ns) + +def _time_fmt(y, _): + """Format seconds at a scale appropriate for the plotted value.""" + if y >= 1: + return f"{y:g} s" + if y >= 1e-3: + return f"{y * 1e3:g} ms" + return f"{y * 1e6:g} us" + +approach1_tic = perf_counter() +ld_time, ld_n = run_lattice_tolerance_curve(seed=7) +ref_time = (ld_time[0] * tol[0]) / tol +ref_n = (ld_n[0] * tol[0]) / tol +approach1_elapsed = perf_counter() - approach1_tic +print(f"Approach 1: elapsed={approach1_elapsed:.6f} s") + +fig1, ax1 = plt.subplots( + 1, 2, figsize=(11, 4.8), constrained_layout=True +) +fig1.suptitle(f"Classic Loop (elapsed: {approach1_elapsed:.3f} s)") +for axis, values, reference, ylabel in zip( + ax1, + [ld_time, ld_n], + [ref_time, ref_n], + ["Time", "n"], +): + axis.scatter(tol, values, color="tab:blue") + axis.plot(tol, reference, color="tab:blue") + axis.set_ylabel(ylabel) + axis.set_xlim([tol.min() * 0.8, tol.max() * 1.2]) + axis.set_ylim([ + np.r_[values, reference].min() * 0.8, + np.r_[values, reference].max() * 1.2, + ]) + axis.set_xlabel("Tolerance, " + r"$\varepsilon$") + axis.set_xscale("log") + axis.set_yscale("log") + axis.legend( + ["Lattice", r"$\mathcal{O}(\varepsilon^{-1})$"], + frameon=False, + ) + axis.set_box_aspect(1) + +ax1[0].yaxis.set_major_formatter(FuncFormatter(_time_fmt)) +plt.show() +``` + +```text +Approach 1: elapsed=0.490905 s +``` + +![Classic-loop time and sample count versus tolerance.](figures/classic-loop.png){fig-alt="Two log-log plots showing elapsed time and total sample count against tolerance for independent fresh lattice runs."} + +## Approach 2: Iteration log with resume + +The solver first runs at the loosest tolerance and then resumes at +progressively tighter tolerances. Because each resumed run starts from the +previous state, the solver performs only the additional work needed for the +new accuracy target. + +After an initial or resumed run, `get_iteration_log()` returns a pandas +`DataFrame` containing the stored iteration history. For QMC stopping +criteria, the error surrogate is typically `comb_bound_diff`; for +root-mean-square-error criteria, it may be `rmse_estimate` or `rmse_tol`. +Resumed runs must use the same solver instance. + +The panels below plot cumulative elapsed time and sample count against +tolerance. Each point represents the work completed through that stopping +point. + +```python +def collect_log_rows_resume(method_name, seed): + """Resume through the tolerance sequence and return each stop row.""" + integ = qp.Keister(qp.Gaussian(qp.Lattice(3, seed=seed))) + stopping_criterion = None + data = None + + for eps in tol: # loosest to tightest + if stopping_criterion is None: + stopping_criterion = qp.CubQMCLatticeG( + integ, abs_tol=float(eps) + ) + _, data = stopping_criterion.integrate() + else: + stopping_criterion.set_tolerance(abs_tol=float(eps)) + _, data = stopping_criterion.integrate(resume=data) + + result = stopping_criterion.get_iteration_log( + formatted=False, view="stage_last" + ).copy() + result.insert(0, "method", method_name) + result.insert(1, "abs_tol", np.asarray(tol)[: len(result)]) + columns = ["method", "abs_tol", "elapsed_time", "n_total"] + return result[columns].sort_values( + "abs_tol", ascending=False + ).reset_index(drop=True) + +approach2_tic = perf_counter() +iter_log = collect_log_rows_resume("Lattice", seed=7) +approach2_elapsed = perf_counter() - approach2_tic +print(f"Approach 2: elapsed={approach2_elapsed:.6f} s") + +iter_log.head(9) +``` + +```text +Approach 2: elapsed=0.210797 s +``` + +| | method | absolute tolerance | cumulative time (s) | total samples | +|---:|:---|---:|---:|---:| +| 0 | Lattice | 0.001000 | 0.003221 | 8,192 | +| 1 | Lattice | 0.000500 | 0.005953 | 16,384 | +| 2 | Lattice | 0.000250 | 0.005953 | 16,384 | +| 3 | Lattice | 0.000125 | 0.016573 | 65,536 | +| 4 | Lattice | 0.000063 | 0.029973 | 131,072 | +| 5 | Lattice | 0.000031 | 0.056201 | 262,144 | +| 6 | Lattice | 0.000016 | 0.056201 | 262,144 | +| 7 | Lattice | 0.000008 | 0.109378 | 524,288 | +| 8 | Lattice | 0.000004 | 0.206681 | 1,048,576 | + +```python +plot_df = iter_log.copy() +plot_df[["abs_tol", "elapsed_time", "n_total"]] = plot_df[ + ["abs_tol", "elapsed_time", "n_total"] +].apply(pd.to_numeric, errors="coerce") +plot_df = plot_df.sort_values(["method", "abs_tol"]) + +def _positive_finite(values): + values = np.asarray(values, dtype=float) + return values[np.isfinite(values) & (values > 0)] + +def draw_panel(axis, y_column, y_label): + data = plot_df[plot_df["method"] == "Lattice"].sort_values( + "abs_tol" + ) + x = data["abs_tol"].to_numpy(dtype=float) + y = data[y_column].to_numpy(dtype=float) + reference = y[-1] * x[-1] / x + + axis.scatter( + x, y, color="tab:blue", marker="o", s=50, + label="Lattice", edgecolors="black", + ) + axis.plot( + x, reference, color="tab:blue", linewidth=2, + label=r"$\mathcal{O}(\varepsilon^{-1})$", + ) + + values = _positive_finite(np.r_[y, reference]) + axis.set_xscale("log") + axis.set_yscale("log") + axis.set_xlim([x.min() * 0.8, x.max() * 1.2]) + axis.set_ylim([values.min() * 0.8, values.max() * 1.25]) + axis.set_xlabel("Tolerance, " + r"$\varepsilon$") + axis.set_ylabel(y_label) + axis.grid(True, which="major", alpha=0.25) + axis.legend(frameon=False) + axis.set_box_aspect(1) + +fig2, ax2 = plt.subplots( + 1, 2, figsize=(12, 5.4), constrained_layout=True +) +fig2.suptitle( + f"Iteration Log with Resume Workflow " + f"(elapsed: {approach2_elapsed:.3f} s)" +) +draw_panel(ax2[0], "elapsed_time", "Time") +draw_panel(ax2[1], "n_total", "n") +ax2[0].yaxis.set_major_formatter(FuncFormatter(_time_fmt)) +plt.show() +``` + +![Iteration-log and resume time and sample count versus tolerance.](figures/iteration-log-resume.png){fig-alt="Two log-log plots showing cumulative elapsed time and total sample count against tolerance for the resumed lattice workflow."} + +Although the two sets of plots look similar, the second workflow reuses prior +work instead of starting fresh at every tolerance. + +## Repeated timing comparison + +A single `perf_counter()` measurement is noisy, so the notebook also uses +`timeit.repeat()` to run each workflow ten times. + +```python +import timeit + +REPEAT = 10 + +def run_approach1_once(): + times, sample_counts = run_lattice_tolerance_curve(seed=7) + return float(times.sum()), float(sample_counts[-1]) + +def run_approach2_once(): + log = collect_log_rows_resume("Lattice", seed=7) + return ( + float(log["elapsed_time"].iloc[-1]), + float(log["n_total"].iloc[-1]), + ) + +def benchmark_callable(function, repeat=REPEAT): + samples = np.asarray( + timeit.repeat(function, number=1, repeat=repeat), + dtype=float, + ) + return pd.Series({ + "average": float(samples.mean()), + "stdev": float(samples.std(ddof=1)), + "min": float(samples.min()), + "max": float(samples.max()), + "repeat": int(repeat), + }) + +benchmark_results = pd.DataFrame({ + "Classic Loop": benchmark_callable(run_approach1_once), + "Iteration Log + Resume": benchmark_callable(run_approach2_once), +}).T +benchmark_results +``` + +| workflow | average (s) | standard deviation (s) | minimum (s) | maximum (s) | repeats | +|:---|---:|---:|---:|---:|---:| +| Classic Loop | 0.459063 | 0.006500 | 0.449794 | 0.469225 | 10 | +| Iteration Log + Resume | 0.212442 | 0.009557 | 0.202143 | 0.229744 | 10 | + +## Conclusion + +For this multi-tolerance experiment, the iteration-log and resume workflow +avoids repeatedly solving the same problem from scratch. It gives researchers +access to the solver history and reuses completed work when the target +tolerance is tightened. + +The gain here applies to sequential tolerance exploration. If the final tight +tolerance is already known, running directly at that tolerance remains the +appropriate baseline. diff --git a/blogs/resume-feature/index.qmd b/blogs/resume-feature/index.qmd new file mode 100644 index 0000000..d0ceef8 --- /dev/null +++ b/blogs/resume-feature/index.qmd @@ -0,0 +1,271 @@ +--- +title: "How Much Accuracy Do You Need?" +author: "Sou-Cheng Choi (with edits by Fred Hickernell)" +date: 2026-05-04 +date-format: "MMMM D, YYYY" +description: "Checkpointing and resuming QMCPy integration when accuracy requirements change." +categories: + - "QMCPy" + - "Checkpointing" + - "Stopping Criteria" +--- + +This post explains how QMCPy's resume feature lets users begin with a loose +tolerance, inspect the result, and later continue to a tighter tolerance +without discarding prior samples. + +The executable source is the +[`accuracy_and_resume.ipynb`](https://github.com/QMCSoftware/QMCSoftware/blob/develop/demos/demo_resume_data/accuracy_and_resume.ipynb) +notebook in the QMCPy repository. A shorter recipe is available in +[`resume_examples.ipynb`](https://github.com/QMCSoftware/QMCSoftware/blob/develop/demos/demo_resume_data/resume_examples.ipynb). + +## Art Owen's reflections on Lyness and the accuracy question + +This notebook explores a discussion about the accuracy requirements for +numerical integration, particularly in automatic quadrature routines. The +central question is how a scientist determines the accuracy needed for a +specific application. + +Three common responses illustrate the challenge: + +**Case A: The "plenty of time" response** + +> I would like 8-figure accuracy. I have quite enough computer time available +> for this. + +This relatively rare response focuses on the result rather than computational +cost. It fits small problems for which high accuracy is the main concern. + +**Case B: The "time-constrained" response** + +> I need at least 4-figure accuracy. But I don't want to use more than 2 +> seconds CPU time. If this can't be done, I shall abandon this problem. If it +> can be done, I should prefer 6- or 7-figure accuracy. But if the marginal +> cost for more figures is really small let's go to 12 figures. + +This more typical response reflects a limited computational budget and a +preference for better accuracy when its marginal cost is small. Automatic +quadrature with a restart or resume facility can refine an initial answer when +the user later asks for more accuracy. + +**Case C: The "I don't know" response** + +> I really don't know. Let me explain... + +This response highlights the numerical analyst's role in helping a scientist +connect application requirements to a quantitative accuracy target. + +## The problem + +Automatic quadrature routines such as those in QMCPy require a target +accuracy. In practice, users may not know that target initially, or their +needs may change after they inspect preliminary results or reconsider the +available computational budget. + +## The solution: Resumable integration in QMCPy + +With QMCPy's `resume` feature, a user can: + +1. Start with a loose tolerance and get a quick estimate. +2. Save the computation state. +3. Resume with a tighter tolerance instead of starting over. +4. Repeat the process as needed. + +The workflow supports checkpointing across Python sessions. Resuming requires +a compatible QMCPy version and compatible problem settings, including the +integrand definition, dimension, randomization, and stopping-criterion family. + +## Implementation + +Supported subclasses of `StoppingCriterion` implement resumption through three +pieces: + +1. `integrate()` accepts a `resume` parameter. +2. When `resume=` is supplied, the solver restores the previous + state, including sample points, transformed values, and relevant statistics. + The new run begins at the previous `n_total` instead of zero. +3. `Data.save()` and `Data.load()` checkpoint the integration state for a + later Python session. + +The example below uses a three-dimensional Genz oscillatory integrand and +QMCPy's `CubQMCLatticeG` stopping criterion. + +::: {.callout-note} +The timing values below are empirical notebook outputs and will vary by +machine. The fixed random seed makes the example reproducible, but the timings +are not theoretical guarantees. +::: + +```python +from pathlib import Path +from qmcpy import CubQMCLatticeG, Genz, Lattice +from qmcpy.util.data import Data +import resume_util as ru +``` + +### Step 1: Quick estimate + +The first run uses a loose absolute tolerance of $10^{-6}$. This example keeps +the loose tolerance near the later tight tolerance so that the initial run has +already completed useful work. + +```python +def make_cub_qmc_lattice_solver( + abs_tol=1e-4, rel_tol=0, seed=7, dimension=3 +): + """Build a CubQMCLatticeG solver for the demo case.""" + integrand = Genz( + Lattice(dimension=dimension, seed=seed), + kind_func="oscillatory", + kind_coeff=1, + ) + return CubQMCLatticeG( + integrand, abs_tol=abs_tol, rel_tol=rel_tol + ) + +abs_tol_loose = 1e-6 +rel_tol = 0 +dimension = 3 +seed = 7 + +solver = make_cub_qmc_lattice_solver( + abs_tol_loose, + rel_tol=rel_tol, + seed=seed, + dimension=dimension, +) +solver.trace_iterations = True +solver.verbose = True +solution1, data1 = solver.integrate() +``` + +```text +stage iter solution comb_bound_diff n_min n_total m xfull.shape +------------------------------------------------------------------------------------------- +ITER 1 -0.4289211 1.943e-03 0 1024 10 (1024, 3) +ITER 2 -0.4289245 8.371e-04 1024 2048 11 (2048, 3) +ITER 3 -0.4289312 1.955e-04 2048 4096 12 (4096, 3) +ITER 4 -0.4289320 1.101e-04 4096 8192 13 (8192, 3) +ITER 5 -0.4289320 1.444e-04 8192 16384 14 (16384, 3) +ITER 6 -0.4289321 1.865e-05 16384 32768 15 (32768, 3) +ITER 7 -0.4289321 9.302e-06 32768 65536 16 (65536, 3) +ITER 8 -0.4289321 1.870e-06 65536 131072 17 (131072, 3) +``` + +The resulting `data1` object records an estimate of approximately +$-0.4289321$, a combined-bound difference of $1.87\times 10^{-6}$, and +$2^{17}=131{,}072$ total samples. It also retains the solver, integrand, +measure, and lattice metadata required for diagnostics and resumption. + +### Step 2: Save the state + +Saving is optional when the same Python process will continue to use `data1`, +but it enables resumption in a later session. + +```python +output_dir = Path("output") +output_dir.mkdir(parents=True, exist_ok=True) +save_path = output_dir / "demo_resume_data.pkl" +data1.save(save_path, overwrite=True) +``` + +`Data.save()` also supports gzip compression. When `compress=True`, QMCPy +appends `.gz` when necessary. + +```python +# Save compressed and load it again. +data1.save("data.pkl", compress=True, overwrite=True) +loaded_data = Data.load("data.pkl.gz") +``` + +In the saved notebook output, the uncompressed checkpoint occupied 7,346,730 +bytes and the compressed checkpoint occupied 4,548,870 bytes, a 38.1% reduction +for that particular state. + +### Step 3: Resume with a tighter tolerance + +The next run tightens the absolute tolerance to $10^{-7}$ while reusing the +saved state and the same solver instance. + +```python +loaded_data = Data.load(save_path) +old_n_total = int(loaded_data.n_total) +old_time = float(loaded_data.time_integrate) + +abs_tol_tight = 1e-7 +solver.set_tolerance(abs_tol=abs_tol_tight) +solution2, data2 = solver.integrate(resume=loaded_data) + +resume_wall_time = float(data2.time_integrate) +new_samples_resume = int(data2.n_total) - old_n_total +two_step_time = old_time + resume_wall_time +``` + +```text +stage iter solution comb_bound_diff n_min n_total m xfull.shape +------------------------------------------------------------------------------------------- +RESUME 8 -0.4289321 1.870e-06 131072 131072 17 (131072, 3) +ITER 9 -0.4289321 7.475e-07 131072 262144 18 (262144, 3) +ITER 10 -0.4289321 2.536e-07 262144 524288 19 (524288, 3) +ITER 11 -0.4289321 9.139e-08 524288 1048576 20 (1048576, 3) +``` + +The resumed result keeps the estimate near $-0.4289321$, reduces the +combined-bound difference to $9.14\times 10^{-8}$, and increases the total +sample count to $2^{20}=1{,}048{,}576$. + +### Step 4: Compare with starting from scratch + +There are three useful comparisons: + +1. **Incremental cost after the loose run already exists:** the resumed run + adds samples from $N_1$ to $N_2$, while a fresh tight run starts at zero. +2. **New sample count:** this is less noisy than wall-clock time. +3. **End-to-end time:** loose plus resume versus a fresh tight run. + +If the tight tolerance is known in advance, a direct tight run is usually just +as efficient or slightly more efficient because checkpointing has overhead. +The practical benefit appears when the loose run is already completed and the +user later requests more accuracy. + +```python +solver2 = make_cub_qmc_lattice_solver( + abs_tol=abs_tol_tight, + rel_tol=rel_tol, + seed=seed, + dimension=dimension, +) +solver2.trace_iterations = True +solver2.verbose = True +solution3, data3 = solver2.integrate() + +fresh_wall_time = float(data3.time_integrate) +new_samples_fresh = int(data3.n_total) +samples_saved = new_samples_fresh - new_samples_resume + +ru.print_stage_summary( + resume_solver=solver, + loose_data=data1, + resume_data=data2, + fresh_solver=solver2, + fresh_data=data3, +) +``` + +| stage | absolute tolerance | total samples | new samples | iterations | solution | half-width | time (s) | +|:---|---:|---:|---:|---:|---:|---:|---:| +| Loose | $10^{-6}$ | 131,072 | 131,072 | 8 | -0.42893206 | $9.35\times10^{-7}$ | 0.0158 | +| Resumed | $10^{-7}$ | 1,048,576 | 917,504 | 11 | -0.42893206 | $4.57\times10^{-8}$ | 0.1128 | +| Fresh | $10^{-7}$ | 1,048,576 | 1,048,576 | 11 | -0.42893206 | $4.57\times10^{-8}$ | 0.1240 | + +For this saved run, resumption evaluated 917,504 new samples, while a fresh +tight run evaluated 1,048,576. The 131,072 samples from the loose run were +reused rather than discarded. The resumed and fresh calculations reached the +same displayed estimate and error bound. + +## Conclusion + +QMCPy's resume feature supports an adaptive workflow: begin with a preliminary +accuracy target, inspect the result, checkpoint the state, and pay for more +samples only if a tighter answer is later needed. This provides a practical +response to time-constrained or initially uncertain accuracy requirements. diff --git a/notes/NEXT.md b/notes/NEXT.md index 13f839f..5fbf3cb 100644 --- a/notes/NEXT.md +++ b/notes/NEXT.md @@ -7,27 +7,27 @@ - Small, validated Website changes may be committed and pushed directly to `main`; longer, overlapping, or higher-risk work should use a short-lived branch. -- The complete blog archive and the community QMC Software Directory are live - on the Website. +- The historic 18-post blog archive and the community QMC Software Directory + are live on the Website. +- Draft pull request #4 adds the two May 2026 QMCPy posts and is integrated + with the current `main`; it remains pending review and merge. - The QMC software directory still exists in `QMCSoftware`. Removing it is a separate future task that must use that repository's issue and pull-request workflow. ## Current focus -Adopt the coordination workflow in `AGENTS.md` and `AUTHOR_WORKFLOW.md` during +Use the coordination workflow in `AGENTS.md` and `AUTHOR_WORKFLOW.md` to finish +reviewing and publishing the two-post expansion, then continue applying it to the next several real Website tasks. Refine it only in response to observed ambiguity, overlap, or unnecessary friction. ## Immediate next task -For the next substantive Website change: - -1. follow the start, validation, synchronization, and publication steps in - `AUTHOR_WORKFLOW.md`; -2. use a short-lived branch if the work will span sessions or may overlap - another collaborator; and -3. update this file only if the operational handoff materially changes. +1. Review the two new posts and the completed checks in draft pull request #4. +2. Merge the pull request after approval and verify the production deployment. +3. For the next substantive Website change, follow the start, validation, + synchronization, and publication steps in `AUTHOR_WORKFLOW.md`. ## Questions to resolve