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
8 changes: 7 additions & 1 deletion AUTHORS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Authors

All contributing authors to statocaster are listed below.
All contributing authors to stratocaster are listed below.

2026
- Alyssa Travitz

2025
- David L. Dotson

2024
- Ian Kenney
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,39 @@ Find the documentation for stratocaster on [Read the Docs](https://stratocaster.

## Installation

Install stratocaster via conda-forge:

```bash
conda install -c conda-forge stratocaster
```

Or install the latest development version via pip:

```bash
pip install git+https://github.com/OpenFreeEnergy/stratocaster.git@main
```

## Usage

Import a strategy and call `propose()` with your `AlchemicalNetwork` and existing results:

```python
from stratocaster.strategies import RadialGrowthStrategy

strategy = RadialGrowthStrategy(RadialGrowthStrategy.default_settings())

previous_results = {}
result = strategy.propose(alchemical_network, previous_results)
normalized_weights = result.resolve()
```

For more details, visit the [stratocaster documentation](https://stratocaster.readthedocs.io/en/latest/).

## Contributing

Please report bugs or request features via the [issue tracker](https://github.com/OpenFreeEnergy/stratocaster/issues).
Pull requests are encouraged for bug fixes, new strategies, and documentation improvements.

## License

This project is released under the [MIT license](./LICENSE).
Expand Down
5 changes: 2 additions & 3 deletions docs/code/iterative.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import random
from stratocaster.strategies import ConnectivityStrategy

settings = ConnectivityStrategy.default_settings()
Expand All @@ -7,10 +6,10 @@
previous_results = {}
# a loop that will eventually end
while True:
strategy_result = strategy.propose()
strategy_result = strategy.propose(alchem_network, previous_results)
normalized_weights = strategy_result.resolve()
# check if there are any weights
if not any(weights.values()):
if not any(normalized_weights.values()):
break

# Pick a transformation from the weights, run it, update previous_results.
Expand Down
2 changes: 1 addition & 1 deletion docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Verify the installation was successful in a Python interpreter

.. code:: python

import statocaster
import stratocaster
print(stratocaster.__version__)

3. Quick-start example
Expand Down
4 changes: 2 additions & 2 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Installation
============

The only requirement for installing statocaster is a working installation of gufe with a version 1.2.0 or higher.
The only requirement for installing stratocaster is a working installation of gufe with a version 1.2.0 or higher.
For general use, we recommend installing from the conda-forge channel, which will also install gufe in the process.

conda-forge channel
Expand All @@ -13,7 +13,7 @@ If you use conda, stratocaster can be installed through the conda-forge channel.

.. code::

conda create -n statocaster-env
conda create -n stratocaster-env
conda activate stratocaster-env
conda install -c conda-forge stratocaster

Expand Down
19 changes: 9 additions & 10 deletions src/stratocaster/strategies/connectivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
field_validator,
)

import pydantic


class ConnectivityStrategySettings(StrategySettings):
"""Specific settings required for the ConnectivityStrategy."""
Expand All @@ -25,7 +23,7 @@ class ConnectivityStrategySettings(StrategySettings):
)
max_runs: int | None = Field(
default=None,
description="the upper limit of protocol DAG results needed before a transformation is no longer weighed",
description="the upper limit of protocol DAG results needed before a transformation is no longer weighted",
)

@field_validator("cutoff", mode="before")
Expand Down Expand Up @@ -63,8 +61,9 @@ class ConnectivityStrategy(Strategy):
"""A Strategy that suggests Transformations which lead to nodes
with higher degrees of connectivity.

The Transformation for a given Transformation is calculated as the
average number of connections both of its end states has.
The weight for a given Transformation is calculated as the average
number of connections both of its end states has.

"""

_settings_cls = ConnectivityStrategySettings
Expand Down Expand Up @@ -128,13 +127,13 @@ def _propose(

match (protocol_results.get(transformation_key)):
case None:
transformation_n_protcol_dag_results = 0
transformation_n_protocol_dag_results = 0
case pr:
assert isinstance(pr, ProtocolResult)
transformation_n_protcol_dag_results = pr.n_protocol_dag_results
transformation_n_protocol_dag_results = pr.n_protocol_dag_results

scaling_factor = self._exponential_decay_scaling(
transformation_n_protcol_dag_results, settings.decay_rate
transformation_n_protocol_dag_results, settings.decay_rate
)
weight = scaling_factor * (num_neighbors_a + num_neighbors_b) / 2

Expand All @@ -143,12 +142,12 @@ def _propose(
if weight < cutoff:
weight = None
case (max_runs, None) if max_runs is not None:
if transformation_n_protcol_dag_results >= max_runs:
if transformation_n_protocol_dag_results >= max_runs:
weight = None
case (max_runs, cutoff) if max_runs is not None and cutoff is not None:
if (
weight < cutoff
or transformation_n_protcol_dag_results >= max_runs
or transformation_n_protocol_dag_results >= max_runs
):
weight = None

Expand Down
22 changes: 11 additions & 11 deletions src/stratocaster/strategies/radialgrowth.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class RadialGrowthStrategySettings(StrategySettings):

max_runs: int = Field(
default=3,
description="the upper limit of ProtocolDAG results needed before a Transformation is no longer weighed",
description="the upper limit of ProtocolDAG results needed before a Transformation is no longer weighted",
)

candidacy_max_distance: int = Field(
Expand All @@ -42,10 +42,10 @@ def validate_max_runs(cls, value):
return value

@field_validator("candidacy_max_distance", mode="before")
def validate_candidate_max_distance(cls, value):
def validate_candidacy_max_distance(cls, value):
if not value >= 1:
raise ValueError(
"`candidtate_max_distance` must be greater than or equal to 1"
"`candidacy_max_distance` must be greater than or equal to 1"
)
return value

Expand All @@ -68,7 +68,7 @@ class RadialGrowthStrategy(Strategy):

The weight assigned to each Transformation depends on its highest
ChemicalSystem distance, as measured and labeled by the vertex
eccentricty, from the lowest completed tier of distances. In the
eccentricity, from the lowest completed tier of distances. In the
graph below, if at least one ``ProtocolDAGResult`` exists for both
edges in 3-2-3, the lowest completed distance is 3. If neither
edge or only one edge has a result, the lowest completed distance
Expand Down Expand Up @@ -125,21 +125,21 @@ def _propose(
alchemical_network_mdg = alchemical_network.graph
weights: dict[GufeKey, float | None] = {}

# calculate all node eccentricies
# calculate all node eccentricities
e = nx.eccentricity(alchemical_network_mdg.to_undirected())

# start with the maximum value, this will be decremented as we
# see evidence the value should be lower
lowest_complete_eccentricity = max(e.values())
# hold on to the eccentricies of the transformations instead
# hold on to the eccentricities of the transformations instead
# of the distances since we don't know the lowest complete
# eccentricity until we process the full graph, distances can
# be calculated after
transformation_eccentricity = {}

for state_a, state_b in alchemical_network_mdg.edges():
edge = e[state_a], e[state_b]
# find the range of eccentricies
# find the range of eccentricities
lower, upper = min(edge), max(edge)

transformation_key = alchemical_network_mdg.get_edge_data(state_a, state_b)[
Expand All @@ -149,24 +149,24 @@ def _propose(
factor_repeats = 1
match (protocol_results.get(transformation_key)):
case None:
transformation_n_protcol_dag_results = 0
transformation_n_protocol_dag_results = 0
# since we have no results for this
# transformation, we know the lowest complete
# eccentricity must be lower than the upper
# eccentricity of the transformation
if upper < lowest_complete_eccentricity:
lowest_complete_eccentricity = lower
case pr:
transformation_n_protcol_dag_results = pr.n_protocol_dag_results
transformation_n_protocol_dag_results = pr.n_protocol_dag_results
# scale the repeat factor to discourage reruns as
# specified by the user's decay_repeat_rate
factor_repeats *= (
self.settings.decay_repeat_rate
** transformation_n_protcol_dag_results
** transformation_n_protocol_dag_results
)

# stop condition given max runs
if self.settings.max_runs <= transformation_n_protcol_dag_results:
if self.settings.max_runs <= transformation_n_protocol_dag_results:
weights[transformation_key] = None
continue

Expand Down
Loading