diff --git a/docs/code/iterative.py b/docs/code/iterative.py new file mode 100644 index 0000000..4aac9d4 --- /dev/null +++ b/docs/code/iterative.py @@ -0,0 +1,21 @@ +import random +from stratocaster.strategies import ConnectivityStrategy + +settings = ConnectivityStrategy.default_settings() +strategy = ConnectivityStrategy(settings) + +previous_results = {} +# a loop that will eventually end +while True: + strategy_result = strategy.propose() + normalized_weights = strategy_result.resolve() + # check if there are any weights + if not any(weights.values()): + break + + # Pick a transformation from the weights, run it, update previous_results. + # This functionality lies outside of the scope of stratocaster. + run_and_update_previous_results(alchem_network, + previous_results, + strategy_result) + diff --git a/docs/code/newstrat.py b/docs/code/newstrat.py new file mode 100644 index 0000000..cce3784 --- /dev/null +++ b/docs/code/newstrat.py @@ -0,0 +1,43 @@ +from gufe import AlchemicalNetwork, ProtocolResult +from gufe.tokenization import GufeKey + +# if including validators with settings, recommended +from pydantic import Field, field_validator + +from stratocaster.base import Strategy, StrategyResult +from stratocaster.base.models import StrategySettings + + +class MyCustomStrategySettings(StrategySettings): + + # an example settings field + max_runs: int = Field( + default=1, + description="the number of times each transformation will run", + ) + + # validate your field + @field_validator("max_runs", mode="before") + def validate_max_runs(cls, value): + if value <= 0: + raise ValueError("max_runs must be larger than 0") + return value + + +class MyCustomStrategy(Strategy): + + # required: prevents initialization of the strategy with incorrect + # settings at runtime + _settings_cls = MyCustomStrategySettings + + @classmethod + def _default_settings(cls) -> StrategySettings: + # the model provides the defaults + return MyCustomStrategySettings() + + def _propose( + self, + alchem_network: AlchemicalNetwork, + protocol_results: dict[GufeKey, ProtocolResult] + ) -> StrategyResult: + ... diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 2069331..ce035cf 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -21,7 +21,26 @@ Verify the installation was successful in a Python interpreter 3. Quick-start example ~~~~~~~~~~~~~~~~~~~~~~ -TODO +You can calculate transformation weights for an ``AlchemicalNetwork``, ``alchem_network`` by calling a strategy's ``propose`` method. + + +.. code:: python + + from stratocaster.strategies import ConnectivityStrategy + + settings = ConnectivityStrategy.default_settings() + strategy = ConnectivityStrategy(settings) + + previous_results: dict[GufeKey, ProtocolResult] = {} + + strategy_result: StrategyResult = strategy.propose(alchem_network, previous_results) + + +This returns a ``StrategyResult`` object, which is a mapping between the transformations in an ``AlchemicalNetwork`` and the weights determined by the strategy. +``None`` weights are a terminating case: a transformation with a ``None`` weight won't be proposed again and a ``StrategyResult`` with only ``None`` weights is "complete". + +Calls to ``propose`` are deterministic and guaranteed to reach a terminating condition if the resulting weights are used to update the ``ProtocolResult`` objects in ``previous_results``. +See the :ref:`user guide` for an example of this process. Other resources ~~~~~~~~~~~~~~~ diff --git a/docs/index.rst b/docs/index.rst index ef3d291..4fa0adb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,12 +3,19 @@ :alt: stratocaster logo ####################################################################### -AlchemicalNetwork Transformation Prioritization Library +Automated Alchemical Effort Allocation ####################################################################### -The stratocaster library is complementary to `gufe `_ and provides suggestions, via Strategies, for optimally executing Transformation Protocols defined in AlchemicalNetworks. +**stratocaster** is a ``Strategy`` library built on top of `gufe `_ for the `Open Free Energy`_ ecosystem: given an ``AlchemicalNetwork`` and any existing results for its ``Transformation``\s, a ``Strategy`` proposes where to apply additional computational effort to produce result data in an "optimal" way. +Different ``Strategy`` implementations define "optimal" differently, with the choice of ``Strategy`` determined by the application. -This library includes a set of Strategy implementations as well as base classes to facilitate the creation of custom Strategy implementations. +**stratocaster** includes many such ``Strategy`` implementations, as well as base classes and guidance for creating new ones. +A ``Strategy`` can be used directly on its own, or in combination with an automated execution system such as `alchemiscale`_ or `exorcist`_. +**stratocaster** is fully open source under the **MIT license**. + +.. _Open Free Energy: https://openfree.energy/ +.. _alchemiscale: https://alchemiscale.org/ +.. _exorcist: https://github.com/OpenFreeEnergy/exorcist .. toctree:: :maxdepth: 2 diff --git a/docs/user_guide.rst b/docs/user_guide.rst index 415d843..37c908a 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -1,2 +1,42 @@ -User guide +.. _user-guide-label: + +User Guide ========== + +A ``Strategy`` is an algorithm that assists in traversing the execution path of transformations within ``gufe`` ``AlchemicalNetwork`` objects. +It removes the burden for an individual or execution engine to determine which transformations in a network must be performed and how important one transformation is relative to another given results that have already been collected. +For instance, transformations with many previously calculated repeats might have a lower priority compared to transformations that haven't been performed at all. +This prioritization is encoded by transformation weights, which are presented for an ``AlchemicalNetwork`` given a set of previously computed results. +As results are accumulated, the strategy must eventually reach a terminating condition where no weights are presented. +Valid strategies are deterministic, i.e. networks with a fixed set of previous results always return the same weights. +While the details of selecting and running a transformation from the weights is out of scope for ``stratocaster``, the following code demonstrates where a strategy might fit in an iterative execution workflow. + +.. literalinclude:: ./code/iterative.py + +A ``None`` weight for a transformation means the transformation should not be performed again as more results are added. +This differs from a zero weight, which could mean the transformation will eventually be proposed again with more results. +Note that before ``resolve`` (which returns a normalized set of weights) is called, the magnitudes of the weights are arbitrary and may reflect the underlying logic behind the specific strategy implementation. +For example, the ``ConnectivityStrategy`` weights are, before correcting for repeated calculations, the average number of connections of the transformations' end states. +Therefore, the pre-normalization weights directly report properties of the many subgraphs in the ``AlchemicalNetwork``. + +Defining a new ``Strategy`` +--------------------------- + +A new ``Strategy`` implementation requires definitions of a new ``Strategy`` subclass along with a ``StrategySettings`` subclass specific to the new strategy. + +The new ``StrategySettings`` is the mechanism by which a user will alter behavior of the new ``Strategy``. +As such, it should define the relevant variables on which the ``Strategy`` will depend. +In the below example, we include only a ``max_runs`` setting, which is usually enough to guarantee that the strategy reaches a termination condition. + +The new ``Strategy`` implementation involves three main steps: 1) linking the strategy to its settings class, 2) defining the ``_default_settings`` class method, and 3) defining the ``_propose`` method. + +.. literalinclude:: ./code/newstrat.py + +A definition of ``_settings_cls`` provides a guardrail by preventing a user of your strategy from supplying an unexpected settings type. +Defining ``_default_settings`` allows a user to get the default settings through ``MyCustomStrategy.default_settings()``. +If your settings provide an exhaustive set of default options, simply return an instance of your settings without providing hard-coded keyword arguments. + +Lastly, the ``_propose`` method implementation determines the results of a strategy prediction based on the ``AlchemicalNetwork``, prior results from executing ``Transformation`` protocols, and your settings. +This method should be deterministic: repeated proposals given the same set of results will yield the same ``StrategyResult``. +It should also have a clear termination condition. +If results are accumulated as a result of the recommendations provided by the strategy, the ``StrategyResult`` will eventually return ``None`` weights for all transformations in the network.