Source code for pyretis.analysis.interface_placement
# -*- coding: utf-8 -*-
# Copyright (c) 2023, PyRETIS Development Team.
# Distributed under the LGPLv2.1+ License. See LICENSE for more info.
"""Automatic interface placement from a crossing-probability curve.
Ported from the upstream ``inftools`` ``infinit`` tool
(``misc/infinit_helper.py``): given a (binless) crossing-probability curve
-- order parameter ``x`` versus total crossing probability ``p`` -- place
TIS/RETIS interfaces so that every ensemble carries approximately the same
target local crossing probability ``pl_target`` (geometric spacing in ``p``).
This is the self-contained scientific core of ``infinit``; the iterative
"run a short simulation, re-estimate the crossing probability with WHAM,
re-place the interfaces, repeat" driver is a separate orchestration layer
(it runs ``pyretisrun`` and rewrites the input) and is not included here.
The crossing-probability curve consumed here is exactly the one
:func:`pyretis.analysis.path_weights.get_path_weights` can emit (its
binless ``Pcross``), or any monotonically-decreasing ``p(x)``.
"""
import numpy as np
__all__ = ['estimate_interface_positions']
[docs]
def estimate_interface_positions(x, p, pl_target=0.3, num_ens=None):
"""Estimate interfaces equally spaced with respect to ``pl_target``.
The interfaces are placed so the cumulative crossing probability drops
by a constant factor (``pl_target``) from one interface to the next --
i.e. each ensemble has a local crossing probability of about
``pl_target``. The final (state-B / cap) interface is not added, so the
probability to reach it stays ``pl_target``.
Parameters
----------
x : array_like
The order-parameter values of the crossing-probability curve
(assumed to contain no duplicates, as produced by the binless
``Pcross``).
p : array_like
The total crossing probability at each ``x`` (monotonically
decreasing, ``p[0] == 1``).
pl_target : float, optional
The target local crossing probability per ensemble (default 0.3).
num_ens : int, optional
The number of ensembles to place. If ``None`` (default) it is
derived from ``pl_target`` and the final crossing probability
``p[-1]``.
Returns
-------
interfaces : list of float
The estimated interface positions (the first is ``x[0]``; the
state-B interface is not included).
pl_used : float
The actual per-interface local crossing probability used (so the
``num_ens`` interfaces tile ``p[-1]`` exactly).
"""
x = np.asarray(x, dtype=float)
p = np.asarray(p, dtype=float)
# Estimate how many ensembles we need (unless given). Floor to at
# least 1: when ``p[-1]`` is not small enough relative to
# ``pl_target`` the truncated estimate is 0, which would make the
# ``p[-1] ** (1 / num_ens)`` below divide by zero.
if not num_ens:
num_ens = max(1, int(np.log(p[-1]) / np.log(pl_target)))
# The actual per-interface local crossing probability so that the
# num_ens steps tile p[-1] exactly.
pl_used = p[-1] ** (1.0 / num_ens)
# p decreases, so -log(p) increases: interpolate -log(p) vs x to find
# the order parameter where the cumulative probability hits pl_used^(i+1).
targets = [-np.log(pl_used ** (i + 1)) for i in range(num_ens - 1)]
interior = np.interp(targets, -np.log(p), x)
interfaces = [float(x[0])] + [float(v) for v in interior]
return interfaces, float(pl_used)