Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,17 @@ my_timeseries = ...
# of the first sample after equilibration, g is the statistical
# inefficiency of the equilibrated sample, and ess is the effective sample
# size of the equilibrated sample.
idx, g, ess = red.detect_equilibration_window(my_timeseries,
method="min_sse",
plot=True)
idx, g, ess = red.detect_equilibration_window(my_timeseries, method="min_sse", plot=True)

# Alternatively, use Geyer's initial convex sequence method to account
# for autocorrelation.
idx, g, ess = red.detect_equilibration_init_seq(my_timeseries,
method="min_sse",
plot=True)
idx, g, ess = red.detect_equilibration_init_seq(my_timeseries, method="min_sse", plot=True)

# We can also determine equilibration in the same way as in
# pymbar.timeseries.detect_equilibration(my_timeseries, fast=False)
idx, g, ess = red.detect_equilibration_init_seq(my_timeseries,
method="max_ess",
sequence_estimator="positive")
idx, g, ess = red.detect_equilibration_init_seq(
my_timeseries, method="max_ess", sequence_estimator="positive"
)
```

#### Uncertainty Quantification
Expand All @@ -99,7 +95,6 @@ idx, g, ess = red.detect_equilibration_init_seq(my_timeseries,
# Estimate the 95 % confidence interval, accounting for autocorrelation using Geyer's initial
# convex sequence method.
ci_95 = red.get_conf_int_init_seq(my_timeseries, alpha_two_tailed=0.05)

```

For more examples, see the [documentation](https://fjclark.github.io/red/latest/examples/).
Expand Down
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 0.2.0 - 2026-08-06

- Make ruff stricter about docstrings and fix them up.
- Switch to from conda, make, and mypy to uv, just, and ty. Modernise type hinting and switch to Python > 3.11.
- Drop polyfill.io due to security issues.

Expand Down
28 changes: 21 additions & 7 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ For all examples, `my_timeseries` should be a numpy array with shape `(n_samples
To use any of Geyer's initial sequence methods ([Geyer, 1992](https://www.jstor.org/stable/2246094)), you can specify the "sequence_estimator" to be "initial_positive" (the least strict), "initial_monotone", or "initial_convex" (the strictest):

```python
idx, g, ess = red.detect_equilibration_init_seq(my_timeseries, sequence_estimator="initial_convex", plot=True)
idx, g, ess = red.detect_equilibration_init_seq(
my_timeseries, sequence_estimator="initial_convex", plot=True
)
my_truncated_timeseries = my_timeseries[idx:]
```
To use Chodera's method of simply truncating the autocovariance series at the first negative value ([Chodera, 2016](https://pubs.acs.org/doi/full/10.1021/acs.jctc.5b00784)), you can specify the "sequence estimator" to be "positive".
Expand All @@ -36,15 +38,19 @@ To use Chodera's method of simply truncating the autocovariance series at the fi
When using window methods, you can either specify a fixed window size, or a window size function which computes the window size as a function of the number of data points (which decreases as the truncation point increases). These are specified via `window_size` and `window_size_fn`, respectively (one must be specified and the other must be `None`). The default window size function is `lambda x: round(x**0.5)` - explicitly:

```python
idx, g, ess = red.detect_equilibration_window(my_timeseries, window_size=None, window_size_fn=lambda x: round(x**0.5), plot=True)
idx, g, ess = red.detect_equilibration_window(
my_timeseries, window_size=None, window_size_fn=lambda x: round(x**0.5), plot=True
)
# This is equivalent to:
idx, g, ess = red.detect_equilibration_window(my_timeseries, plot=True)
```

To use a window size of 10:

```python
idx, g, ess = red.detect_equilibration_window(my_timeseries, window_size=10, window_size_fn = None, plot=True)
idx, g, ess = red.detect_equilibration_window(
my_timeseries, window_size=10, window_size_fn=None, plot=True
)
```

You can also play with the kernel function used in the window method by specifying the `kernel` argument. You should supply the function directly - the default is `np.bartlett`.
Expand All @@ -54,15 +60,19 @@ You can also play with the kernel function used in the window method by specifyi
To use White's original Marginal Standard Error Rule ([White, 1997](https://journals.sagepub.com/doi/abs/10.1177/003754979706900601)), you can use the window method with a window size of 1:

```python
idx, g, ess = red.detect_equilibration_window(my_timeseries, window_size=1, window_size_fn=None, plot=True)
idx, g, ess = red.detect_equilibration_window(
my_timeseries, window_size=1, window_size_fn=None, plot=True
)
```

### Maximum Effective Sample Size and Chodera's Method

To select the truncation point according to the maximum effective sample size (instead of the minimum squared standard error), you can specify the `method` argument to be "max_ess". To use Chodera's method ([Chodera, 2016](https://pubs.acs.org/doi/full/10.1021/acs.jctc.5b00784)) as implemented in `pymbar.timeseries`, you can specify the `sequence_estimator` to be "positive":

```python
idx, g, ess = red.detect_equilibration_init_seq(my_timeseries, method="max_ess", sequence_estimator="positive", plot=True)
idx, g, ess = red.detect_equilibration_init_seq(
my_timeseries, method="max_ess", sequence_estimator="positive", plot=True
)
# Equivalent to pymbar.timeseries.detect_equilibration(my_timeseries, fast=False)
```

Expand All @@ -71,15 +81,19 @@ idx, g, ess = red.detect_equilibration_init_seq(my_timeseries, method="max_ess",
To save a plot showing the (block-averaged) time series and variance of the mean/ effective sample size against truncation time, simply specify `plot=True` and, optionally, specify a name for the plot with `plot_name`. This works for either of the equilbration detection functions.

```python
idx, g, ess = red.detect_equilibration_window(my_timeseries, plot=True, plot_name="my_equilibration_plot.png")
idx, g, ess = red.detect_equilibration_window(
my_timeseries, plot=True, plot_name="my_equilibration_plot.png"
)
```

## Estimating Uncertainty

To calculate uncertainty, we recommend using Geyer's initial convex sequence method ([Geyer, 1992](https://www.jstor.org/stable/2246094)), which is the default for [`get_conf_int_init_seq`][red.confidence_intervals.get_conf_int_init_seq]. For example, to estimate a 95 % confidence interval:

```python
ci_95 = red.get_conf_int_init_seq(my_timeseries, sequence_estimator="initial_convex", alpha_two_tailed=0.05)
ci_95 = red.get_conf_int_init_seq(
my_timeseries, sequence_estimator="initial_convex", alpha_two_tailed=0.05
)
```

This function has a similar interface to [`detect_equilibration_init_seq`][red.equilibration.detect_equilibration_init_seq], and you can specify the "sequence_estimator" in the same way. Note that this assumes we have a reasonable effective sample size, and hence that the means are approximately normally distributed by the central limit theorem.
Expand Down
10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,18 @@ line-length = 100
[tool.ruff.lint]
ignore = ["PLR", "PLW", "C901"]
select = ["B","C","E","F","W","B9"]
# Enforce PEP257 docstring conventions (pydocstyle). The numpy convention matches the
# numpy-style docstrings used throughout the package; it disables D212/D413/D415/D417 by
# default, so we re-enable those explicitly. D213 is intentionally left disabled by the
# numpy convention (D212 and D213 are mutually exclusive).
extend-select = ["D", "D212", "D413", "D415", "D417"]

[tool.ruff.lint.pydocstyle]
convention = "numpy"

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
"red/tests/*.py" = ["F401", "F811"]
"red/tests/*.py" = ["F401", "F811", "D"]

[tool.setuptools]
# This subkey is a beta stage development and keys may change in the future, see https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html for more details
Expand Down
2 changes: 1 addition & 1 deletion red/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Robust Equilibration Detection"""
"""Robust Equilibration Detection."""

from ._version import __version__
from .confidence_intervals import get_conf_int_init_seq
Expand Down
12 changes: 6 additions & 6 deletions red/_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
def check_data(
data: _npt.NDArray[_np.float64], one_dim_allowed: bool = False
) -> _npt.NDArray[_np.float64]:
"""
Assert that data passed is a numpy array where
the first dimension is the number of chains and
the second dimension is the number of samples.
If the array is one dimensional, add a second
dimension with length 1.
"""Validate and reshape input data to 2D ``(n_chains, n_samples)``.

Asserts that the data passed is a numpy array where the first dimension is the number
of chains and the second dimension is the number of samples. If the array is one
dimensional, a second dimension with length 1 is added.

Parameters
----------
Expand All @@ -30,6 +29,7 @@ def check_data(
-------
np.ndarray
Data with shape (n_chains, n_samples).

"""
# Check that data is a numpy array.
if not isinstance(data, _np.ndarray):
Expand Down
2 changes: 1 addition & 1 deletion red/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.1.4+6.gf1edc4b.dirty"
__version__ = "0.1.4+5.g7888c80.dirty"
7 changes: 4 additions & 3 deletions red/confidence_intervals.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ def get_conf_int_init_seq(
min_max_lag_time: int = 3,
max_max_lag_time: int | None = None,
) -> float:
"""
Calculate the confidence interval for the mean of a time
series using initial sequence methods. See Geyer, 1992:
"""Calculate the confidence interval for the mean of a time series.

Uses initial sequence methods. See Geyer, 1992:
https://www.jstor.org/stable/2246094.

Parameters
Expand Down Expand Up @@ -47,6 +47,7 @@ def get_conf_int_init_seq(
-------
float
The standard error of the mean.

"""
# Get the correlated estimate of the variance.
var_cor, max_lag, acovf = _get_variance_initial_sequence(
Expand Down
56 changes: 29 additions & 27 deletions red/equilibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,12 @@ def detect_equilibration_init_seq(
data_y_label: str = r"$\Delta G$ / kcal mol$^{-1}$",
plot_max_lags: bool = True,
) -> tuple[float | int, float, float]:
r"""
Detect the equilibration time of a time series by finding the minimum
squared standard error (SSE), or maximum effective sample size (ESS)
of the time series, using initial sequence estimators of the variance.
This is done by computing the SSE at each time point, discarding all
samples before the time point. The index of the time point with
the minimum SSE or maximum ESS is taken to be the point of equilibration.
r"""Detect the equilibration time of a time series by finding the minimum SSE or maximum ESS.

The variance is estimated using initial sequence estimators. This is done by computing
the squared standard error (SSE) at each time point, discarding all samples before the
time point. The index of the time point with the minimum SSE, or maximum effective
sample size (ESS), is taken to be the point of equilibration.

Parameters
----------
Expand Down Expand Up @@ -105,6 +104,7 @@ def detect_equilibration_init_seq(

equil_ess: float
The effective sample size at the equilibration point.

"""
# Check that data is valid.
data = check_data(data, one_dim_allowed=True)
Expand Down Expand Up @@ -196,13 +196,12 @@ def detect_equilibration_window(
data_y_label: str = r"$\Delta G$ / kcal mol$^{-1}$",
plot_window_size: bool = True,
) -> tuple[float | int, float, float]:
r"""
Detect the equilibration time of a time series by finding the minimum
squared standard error (SSE) or maximum effective sample size (ESS)
of the time series, using window estimators of the variance. This is
done by computing the SSE at each time point, discarding all samples
before the time point. The index of the time point with the minimum
SSE is taken to be the point of equilibration.
r"""Detect the equilibration time of a time series by finding the minimum SSE or maximum ESS.

The variance is estimated using window estimators. This is done by computing the
squared standard error (SSE) at each time point, discarding all samples before the time
point. The index of the time point with the minimum SSE, or maximum effective sample
size (ESS), is taken to be the point of equilibration.

Parameters
----------
Expand Down Expand Up @@ -264,6 +263,7 @@ def detect_equilibration_window(

equil_ess: float
The effective sample size at the equilibration point.

"""
# Check that data is valid.
data = check_data(data, one_dim_allowed=True)
Expand Down Expand Up @@ -343,11 +343,11 @@ def get_paired_t_p_timeseries(
final_block_size: float = 0.5,
t_test_sidedness: str = "two-sided",
) -> tuple[_npt.NDArray[_np.float64], _npt.NDArray[_np.int64 | _np.float64]]:
"""
Get a timeseries of the p-values from a paired t-test on the differences
between sample means between intial and final portions of the data. The timeseries
is obtained by repeatedly discarding more data from the time series between
calculations of the p-value.
"""Get a timeseries of the p-values from a paired t-test.

The p-values come from a paired t-test on the differences between sample means between
initial and final portions of the data. The timeseries is obtained by repeatedly
discarding more data from the time series between calculations of the p-value.

Parameters
----------
Expand Down Expand Up @@ -385,6 +385,7 @@ def get_paired_t_p_timeseries(

np.ndarray
The times at which the p-values were calculated.

"""
# Check that the data is valid.
data = check_data(data, one_dim_allowed=False)
Expand Down Expand Up @@ -474,14 +475,14 @@ def detect_equilibration_paired_t_test(
time_units: str = "ns",
data_y_label: str = r"$\Delta G$ / kcal mol$^{-1}$",
) -> _np.int64 | _np.float64:
r"""
Detect the equilibration time of a time series by performing a paired
t-test between initial and final portions of the time series. This is repeated
, discarding more data from the time series between repeats. If the p-value
is greater than the threshold, there is no significant evidence that the data is
no equilibrated and the timeseries is taken to be equilibrated at this time
point. This test may be useful when we care only about systematic bias in the
data, and do not care about detecting inter-run differences.
r"""Detect the equilibration time of a time series by performing a paired t-test.

A paired t-test is performed between initial and final portions of the time series.
This is repeated, discarding more data from the time series between repeats. If the
p-value is greater than the threshold, there is no significant evidence that the data
is not equilibrated and the timeseries is taken to be equilibrated at this time point.
This test may be useful when we care only about systematic bias in the data, and do
not care about detecting inter-run differences.

Parameters
----------
Expand Down Expand Up @@ -533,6 +534,7 @@ def detect_equilibration_paired_t_test(
np.float64 | np.int64
The time (or index, if no times are supplied) at which
the time series is equilibrated.

"""
# Validate data.
data = check_data(data, one_dim_allowed=False)
Expand Down
Loading