From 604511e647fd30c21ea8b58e35b2288e3587d263 Mon Sep 17 00:00:00 2001 From: Ian Kenney Date: Tue, 10 Mar 2026 16:05:50 -0400 Subject: [PATCH 1/9] Add quickstart example --- docs/getting_started.rst | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 2069331..dfe96fe 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -21,7 +21,25 @@ 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``. Other resources ~~~~~~~~~~~~~~~ From d1ec0e9652d6933b904bf7c025aefe9c07a2f6f9 Mon Sep 17 00:00:00 2001 From: Ian Kenney Date: Thu, 12 Mar 2026 12:18:37 -0400 Subject: [PATCH 2/9] Expand user docs --- docs/newstrat.py | 43 +++++++++++++++++++++++++++++++++++ docs/user_guide.rst | 55 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 docs/newstrat.py diff --git a/docs/newstrat.py b/docs/newstrat.py new file mode 100644 index 0000000..f3c5eda --- /dev/null +++ b/docs/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/user_guide.rst b/docs/user_guide.rst index 415d843..89acf4e 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -1,2 +1,57 @@ 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. +As results are accumulated, the strategy eventually reaches a terminating condition where no weights are presented. +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. + +.. code:: python + + 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) + + +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. + +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:: 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. From 9ee55cde6535f5f694a75359ef48ee848334cdfe Mon Sep 17 00:00:00 2001 From: Ian Kenney Date: Mon, 16 Mar 2026 10:49:13 -0400 Subject: [PATCH 3/9] Include notes on weight meanings --- docs/iterative.py | 21 +++++++++++++++++++++ docs/user_guide.rst | 31 +++++++------------------------ 2 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 docs/iterative.py diff --git a/docs/iterative.py b/docs/iterative.py new file mode 100644 index 0000000..4aac9d4 --- /dev/null +++ b/docs/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/user_guide.rst b/docs/user_guide.rst index 89acf4e..cbcf168 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -4,35 +4,18 @@ 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. -As results are accumulated, the strategy eventually reaches a terminating condition where no weights are presented. +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. +Additionally, 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. -.. code:: python - - 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) - +.. literalinclude:: 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`` is called, which returns a normalized set of weights, 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`` --------------------------- From 8619fa9c4af051b32791b91110b959ab14157911 Mon Sep 17 00:00:00 2001 From: "David L. Dotson" Date: Tue, 31 Mar 2026 07:35:44 -0600 Subject: [PATCH 4/9] Jazz up the landing page for the docs and add links to related resources (#21) --- docs/index.rst | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index c8464ee..13c5546 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,12 +3,19 @@ :alt: stratocaster logo ####################################################################### -AlchemicalNetwork Transformation Prioritization Library +alchemical effort allocation, automated ####################################################################### -The stratocaster library is complimentary to gufe and provides suggestions, via Strategies, for optimally executing Transformation Protocols defined in AlchemicalNetworks. +**stratocaster** is a ``Strategy`` library 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 From e786a5de0f685111ea1e38b0cb75be0cf1c65aec Mon Sep 17 00:00:00 2001 From: Ian Kenney Date: Mon, 13 Apr 2026 09:27:06 -0400 Subject: [PATCH 5/9] Make main headers title case --- docs/index.rst | 2 +- docs/user_guide.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 0634b9a..4fa0adb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,7 +3,7 @@ :alt: stratocaster logo ####################################################################### -alchemical effort allocation, automated +Automated Alchemical Effort Allocation ####################################################################### **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. diff --git a/docs/user_guide.rst b/docs/user_guide.rst index cbcf168..fad41b0 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -1,4 +1,4 @@ -User guide +User Guide ========== A ``Strategy`` is an algorithm that assists in traversing the execution path of transformations within ``gufe`` ``AlchemicalNetwork`` objects. From 86035541c0293c457e43999cee7b4b8ddf243f03 Mon Sep 17 00:00:00 2001 From: Ian Kenney Date: Mon, 13 Apr 2026 09:52:49 -0400 Subject: [PATCH 6/9] Link getting started to the user guide --- docs/getting_started.rst | 1 + docs/newstrat.py | 2 +- docs/user_guide.rst | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/getting_started.rst b/docs/getting_started.rst index dfe96fe..ce035cf 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -40,6 +40,7 @@ This returns a ``StrategyResult`` object, which is a mapping between the transfo ``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/newstrat.py b/docs/newstrat.py index f3c5eda..cce3784 100644 --- a/docs/newstrat.py +++ b/docs/newstrat.py @@ -5,7 +5,7 @@ from pydantic import Field, field_validator from stratocaster.base import Strategy, StrategyResult -from stratocaster.base models import StrategySettings +from stratocaster.base.models import StrategySettings class MyCustomStrategySettings(StrategySettings): diff --git a/docs/user_guide.rst b/docs/user_guide.rst index fad41b0..48925fc 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -1,3 +1,5 @@ +.. _user-guide-label: + User Guide ========== From 06e5e0d1db504c642ac9838dfefb917baeb37c1f Mon Sep 17 00:00:00 2001 From: Ian Kenney Date: Mon, 13 Apr 2026 09:56:31 -0400 Subject: [PATCH 7/9] Move doc code snippets to subdirectory --- docs/{ => code}/iterative.py | 0 docs/{ => code}/newstrat.py | 0 docs/user_guide.rst | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename docs/{ => code}/iterative.py (100%) rename docs/{ => code}/newstrat.py (100%) diff --git a/docs/iterative.py b/docs/code/iterative.py similarity index 100% rename from docs/iterative.py rename to docs/code/iterative.py diff --git a/docs/newstrat.py b/docs/code/newstrat.py similarity index 100% rename from docs/newstrat.py rename to docs/code/newstrat.py diff --git a/docs/user_guide.rst b/docs/user_guide.rst index 48925fc..506b2bd 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -11,7 +11,7 @@ As results are accumulated, the strategy must eventually reach a terminating con Additionally, 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:: iterative.py +.. 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. @@ -30,7 +30,7 @@ In the below example, we include only a ``max_runs`` setting, which is usually e 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:: newstrat.py +.. 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()``. From a5d30497d272177aeb32bbeca834795a30ae2cda Mon Sep 17 00:00:00 2001 From: Ian Kenney Date: Mon, 13 Apr 2026 11:20:17 -0400 Subject: [PATCH 8/9] Apply suggestions from code review Co-authored-by: Alyssa Travitz <31974495+atravitz@users.noreply.github.com> --- docs/user_guide.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/user_guide.rst b/docs/user_guide.rst index 506b2bd..5f171fb 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -7,15 +7,16 @@ A ``Strategy`` is an algorithm that assists in traversing the execution path of 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. -Additionally, valid strategies are deterministic, i.e. networks with a fixed set of previous results always return the same weights. +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`` is called, which returns a normalized set of weights, the magnitudes of the weights are arbitrary and may reflect the underlying logic behind the specific strategy implementation. +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``. From c0df64796b441dd4427139c3bfea0899e91b4c5e Mon Sep 17 00:00:00 2001 From: Ian Kenney Date: Mon, 13 Apr 2026 11:37:15 -0400 Subject: [PATCH 9/9] Correct line endings --- docs/user_guide.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/user_guide.rst b/docs/user_guide.rst index 5f171fb..37c908a 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -7,16 +7,15 @@ A ``Strategy`` is an algorithm that assists in traversing the execution path of 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. +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. +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``.