Automating Execution with a Strategy

After submitting an AlchemicalNetwork and creating some initial Tasks as described in Getting Started, you can automate the ongoing management of your computation with a Strategy. A Strategy intelligently analyzes your network’s current results and automatically prioritizes which transformations should receive computational resources next.

What is a Strategy?

A Strategy is an algorithm that continuously monitors your AlchemicalNetwork and:

  • Analyzes the current state of your network and existing results

  • Assigns priority weights to transformations (0.0 to 1.0 scale)

  • Automatically creates and actions (or cancels) Tasks based on these priorities

  • Adapts its decisions as new results become available

This automation saves you from manually monitoring progress and creating new Tasks, while ensuring computational resources are allocated where they will be most beneficial.

Basic Strategy Usage

Setting up a Strategy for your Network

Once you have submitted an AlchemicalNetwork (see Submitting your AlchemicalNetwork to alchemiscale), you can attach a Strategy to it:

>>> from stratocaster.strategies import ConnectivityStrategy
>>> from alchemiscale import AlchemiscaleClient, Scope

>>> # Assuming you already have a submitted network
>>> asc = AlchemiscaleClient()
>>> scope = Scope('my_org', 'my_campaign', 'my_project')
>>>
>>> # Create a connectivity-based strategy
>>> strategy = ConnectivityStrategy(ConnectivityStrategy.default_settings())
>>>
>>> # Attach it to your network
>>> asc.set_network_strategy(
...     network=network_sk,
...     strategy=strategy,
...     max_tasks_per_transformation=5
... )

This connectivity strategy will prioritize transformations critical for overall connectivity between ChemicalSystems within your network, helping to improve overall convergence.

Monitoring Strategy Progress

You can check on your Strategy's current status:

>>> # Check strategy state
>>> state = asc.get_network_strategy_state(network_sk)
>>> print(f"Status: {state.status}")
>>> print(f"Iterations: {state.iterations}")
>>> print(f"Last run: {state.last_iteration}")

The Strategy will automatically run periodically (every 1 hour by default) to reassess your network and adjust Task allocation accordingly.

Understanding Strategy Modes

Strategies operate in different modes that control how aggressively they manage your Tasks:

Partial Mode (Default)

In partial mode, the Strategy takes a conservative approach:

>>> asc.set_network_strategy(
...     network=network_sk,
...     strategy=strategy,
...     mode="partial"
... )
  • Creates new Tasks when transformation priorities increase

  • Never cancels existing Tasks

  • Accumulates computational work over time

  • Safe choice when you want to avoid wasting any computation

Full Mode

In full mode, the Strategy actively reallocates resources:

>>> asc.set_network_strategy(
...     network=network_sk,
...     strategy=strategy,
...     mode="full"
... )
  • Creates new Tasks when priorities increase

  • Cancels existing Tasks when priorities decrease

  • Aggressively optimizes resource allocation

  • May cancel running Tasks if they become lower priority

Warning

Use full mode carefully, as it may cancel in-progress work if transformation priorities change significantly.

Task Scaling and Resource Control

You can control how many Tasks are created based on transformation priorities:

Linear Scaling

With linear scaling, the number of tasks increases proportionally with priority:

>>> asc.set_network_strategy(
...     network=network_sk,
...     strategy=strategy,
...     task_scaling="linear",
...     max_tasks_per_transformation=6
... )

The number of tasks proposed corresponds to:

tasks = math.floor(1 + (weight × max_tasks_per_transformation))

So a transformation with weight 0.5 would get 1 + (0.5 × 6) = 4 tasks.

Linear scaling gives the following qualitative relationship between weight and Task counts, assuming max_tasks_per_transformation = 6:

tasks        1           2           3          4           5          6
        |----------|-----------|----------|-----------|----------|-----------|
weight  0                                                                    1

Exponential Scaling (Default)

With exponential scaling, high-priority transformations receive disproportionately more resources:

>>> asc.set_network_strategy(
...     network=network_sk,
...     strategy=strategy,
...     task_scaling="exponential",
...     max_tasks_per_transformation=6
... )

The number of tasks proposed corresponds to:

tasks = math.floor((1 + max_tasks_per_transformation) ^ weight)

So a transformation with weight 0.5 would get (1 + 6)^0.5 2.6 (rounded to 2) tasks.

This gives high-priority transformations much more computational power while still allocating some resources to lower-priority ones. Exponential scaling gives the following qualitative relationship between weight and Task counts, assuming max_tasks_per_transformation = 6:

tasks                   1                         2            3      4   5 6
        |--------------------------------|----------------|--------|----|--|-|
weight  0                                                                    1

Strategy Lifecycle States

Your Strategy also features a status, similar to Task status:

Awake Status

When 'awake', the Strategy is actively working:

>>> status = asc.get_network_strategy_status(network_sk)
>>> print(status)
'awake'

The Strategy analyzes your network, assigns weights to transformations, and creates and actions (or cancels) Tasks according to its mode.

Dormant Status

A Strategy goes 'dormant' when it determines no further work is needed:

>>> status = asc.get_network_strategy_status(network_sk)
>>> print(status)
'dormant'

This happens when all transformation weights are None, indicating the Strategy has reached its stop condition.

A Strategy will automatically go from 'dormant' to 'awake' if new results have appeared since it went 'dormant', giving it a chance to evaluate whether to allocate additional effort given the new information. You can also manually wake up a 'dormant' Strategy with:

>>> asc.set_network_strategy_awake(network_sk)

Error Status

If the Strategy encounters an error during execution, it will enter the 'error' status:

>>> status = asc.get_network_strategy_status(network_sk)
>>> print(status)
'error'

You can introspect the problem using:

>>> state = asc.get_network_strategy_state(network_sk)
>>> if state.exception:
...     print(f"Error: {state.exception}")
...     print(f"Traceback: {state.traceback}")

A Strategy in the 'error' status will no longer be performed. You should address the issue indicated by the traceback, and then set the Strategy back to the 'awake' status to continue:

>>> asc.set_network_strategy_awake(network_sk)

Managing Strategy Execution Frequency

You can control how often your Strategy runs:

>>> asc.set_network_strategy(
...     network=network_sk,
...     strategy=strategy,
...     sleep_interval=600  # 10 minutes between runs
... )

Shorter intervals mean more responsive automation but higher computational overhead for the strategist service. Longer intervals reduce overhead but may be slower to respond to changing conditions.

The alchemiscale Strategist service will be configured with a minimum sleep interval, so setting this too low will have no effect if it is lower than that interval.

Disabling a Strategy

If you need to pause strategy execution temporarily:

>>> asc.set_network_strategy(
...     network=network_sk,
...     strategy=strategy,
...     mode="disabled"
... )

This completely stops the Strategy from creating or canceling any Tasks. You can re-enable it later by changing the mode back to partial or full.

Best Practices

Start Conservative

When first using a Strategy:

  • Begin with partial mode to avoid unexpected cancellations

  • Use lower max_tasks_per_transformation values initially

  • Monitor strategy behavior before scaling up

Resource Planning

  • Use linear scaling for predictable resource usage

  • Use exponential scaling when you want to heavily prioritize important transformations

  • Adjust max_tasks_per_transformation based on your available compute resources

Monitoring

Regular monitoring helps ensure your Strategy is working as expected:

>>> # Check strategy state periodically
>>> state = asc.get_network_strategy_state(network_sk)
>>> print(f"Status: {state.status}, Iterations: {state.iterations}")
>>>
>>> # Monitor overall network progress
>>> status_counts = asc.get_network_status(network_sk)
>>> print(status_counts)

Troubleshooting

Strategy Not Running

If your Strategy isn’t executing:

  • Verify with the alchemiscale server administrator that the Strategist service is running and accessible

  • Check that your network is in a Scope visible to the Strategist service

  • Ensure sufficient time has passed since the last iteration (respecting sleep_interval)

Unexpected Task Behavior

If Tasks are being created or canceled unexpectedly:

  • Review your Strategy mode (partial vs full)

  • Check max_tasks_per_transformation and task_scaling settings

Poor Performance

If Strategy execution is slow:

  • Increase the strategist service cache size if running your own service

  • Consider reducing max_workers if the host is overloaded, or increasing if underutilized

  • Evaluate whether your chosen Strategy algorithm is efficient for large networks

Example Workflow

Here’s a complete example showing typical Strategy usage:

>>> from alchemiscale import AlchemiscaleClient, Scope, ScopedKey
>>> from stratocaster.strategies import ConnectivityStrategy
>>>
>>> # Set up client and submit network (assuming this is done)
>>> asc = AlchemiscaleClient()
>>> network_sk = ScopedKey.from_str("<your-network-scoped-key>")
>>>
>>> # Create and attach a conservative connectivity strategy
>>> strategy = ConnectivityStrategy(ConnectivityStrategy.default_settings())
>>> asc.set_network_strategy(
...     network=network_sk,
...     strategy=strategy,
...     mode="partial",
...     task_scaling="linear",
...     max_tasks_per_transformation=3,
...     sleep_interval=1800
... )
>>>
>>> # Monitor progress
>>> state = asc.get_network_strategy_state(network_sk)
>>> print(f"Strategy status: {state.status}")
>>>
>>> # Later, if you want more aggressive optimization
>>> asc.set_network_strategy(
...     network=network_sk,
...     strategy=strategy,
...     mode="full",
...     task_scaling="exponential",
...     max_tasks_per_transformation=10
... )

This workflow starts conservatively and becomes more aggressive as you gain confidence in the Strategy’s behavior.