Part V: Discovery Through Simulation & Optimization
Chapter 45: Optimization For Discovery

45.3 Reinforcement Learning as Optimization

"I maximized my cumulative reward over a 50-step horizon. Turns out the real reward was the intermediates I synthesized along the way."

A Policy Gradient With Sentimental Variance
The Big Picture

A robotic chemist has fifteen reagent slots, a twenty-step budget, and one night to find a high-yield catalyst; every experiment it runs reshapes what it should try next. Bayesian optimization and evolutionary methods treat the objective as a static function: evaluate candidates, observe fitness, repeat. But problems like that robotic chemist's are inherently sequential. A retrosynthesis planner must choose a sequence of reactions. A materials screening campaign allocates budget across multiple stages, each informed by the last. These are sequential decision problems, and reinforcement learning (RL) provides the mathematical framework to solve them. This section develops policy gradient methods and actor-critic architectures, then shows when RL outperforms static optimization and when it does not.

1. Discovery as a Markov Decision Process

A Markov Decision Process (MDP) is defined by the tuple \((\mathcal{S}, \mathcal{A}, P, R, \gamma)\):

An MDP models any situation where an agent makes sequential decisions under uncertainty, receiving feedback after each step. It matters because it transforms vague notions of "planning" into a precise optimization problem with provable solution methods. Given the state, transition, and reward structure, algorithms compute the decision rule (policy) that maximizes long-term cumulative reward. The core mechanism is the Bellman equation, which splits a multi-step plan's value into the immediate reward plus the discounted value of the next state. This decomposition enables dynamic programming or gradient-based solutions. Use an MDP formulation (and therefore RL) instead of single-shot optimization when earlier decisions affect later options. If each evaluation is independent, simpler methods like Bayesian optimization or evolutionary search are more sample-efficient.

The goal is to find a policy \(\pi(a \mid s)\) (a distribution over actions given the current state) that maximizes the expected cumulative reward:

$$J(\pi) = \mathbb{E}_{\tau \sim \pi}\left[\sum_{t=0}^{T} \gamma^t R(s_t, a_t, s_{t+1})\right]$$

where \(\tau = (s_0, a_0, s_1, a_1, \ldots)\) is a trajectory sampled by following policy \(\pi\) in the MDP.

Key Insight

The critical difference between RL and Bayesian optimization (BO) is that RL optimizes a policy (a decision rule), not a single point. The policy maps states to actions, meaning it adapts to what has been observed so far. BO's acquisition function does something similar (it conditions on all past data), but RL explicitly models the sequential structure and can learn long-horizon strategies that BO cannot represent.

Mapping a discovery campaign to an MDP requires careful state design. Table 45.1 shows three example mappings.

Table 45.1: Discovery campaigns modeled as MDPs.
Campaign State \(s\) Action \(a\) Reward \(R\) Horizon \(T\)
Drug screening Tested compounds, assay results, budget remaining Choose next compound to test Activity of tested compound Budget / cost-per-test
Retrosynthesis Current molecule, available reagents Choose reaction template +1 if target reached, 0 otherwise Max synthesis steps
Robotic lab Instrument states, sample queue, results so far Schedule next instrument action Information gain from measurement Shift duration

2. The Policy Gradient Theorem

The MDP formulation raises a core question: how to find the policy that maximizes cumulative reward.

If the agent cannot estimate how a small change to its decision rule will affect cumulative reward, it is reduced to random trial and error, wasting every experiment in a budget-limited campaign. The policy gradient theorem solves exactly this problem, providing an unbiased gradient signal that turns sequential discovery into tractable optimization.

We parameterize the policy as \(\pi_\theta(a \mid s)\) using a neural network with parameters \(\theta\). The policy gradient theorem (Sutton et al., 1999) gives us the gradient of the expected return with respect to \(\theta\):

$$\nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta}\left[\sum_{t=0}^{T} \nabla_\theta \log \pi_\theta(a_t \mid s_t)\, G_t\right]$$

where \(G_t = \sum_{k=t}^{T} \gamma^{k-t} R_k\) is the return from time step \(t\). This expression uses the log-derivative trick: because \(\nabla_\theta \pi_\theta = \pi_\theta \nabla_\theta \log \pi_\theta\), we can estimate the gradient by sampling trajectories under \(\pi_\theta\) and weighting each action's log-probability by the return that followed it. Actions that led to high returns get reinforced (their probability increases); actions that led to low returns get suppressed. In short: weight each decision by the outcome it produced, then nudge the policy toward decisions that paid off.

2.1 REINFORCE

The simplest policy gradient algorithm, REINFORCE (Williams, 1992), directly applies the policy gradient theorem using Monte Carlo estimates (where the return \(G_t\) is computed from a complete sampled trajectory rather than bootstrapped from a value function) of \(G_t\):

import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions import Categorical
import numpy as np

class PolicyNetwork(nn.Module):
    """Simple policy network for discrete action spaces."""

    def __init__(self, state_dim: int, n_actions: int, hidden: int = 64):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(state_dim, hidden),
            nn.ReLU(),
            nn.Linear(hidden, hidden),
            nn.ReLU(),
            nn.Linear(hidden, n_actions),
        )

    def forward(self, state: torch.Tensor) -> Categorical:
        logits = self.net(state)
        return Categorical(logits=logits)


def reinforce(
    env,
    policy: PolicyNetwork,
    n_episodes: int = 1000,
    lr: float = 1e-3,
    gamma: float = 0.99,
) -> list:
    """Train a policy using REINFORCE with baseline subtraction.

    Parameters
    ----------
    env : environment with reset() -> state, step(action) -> (state, reward, done)
    policy : PolicyNetwork
    n_episodes : int
    lr : float
    gamma : float

    Returns
    -------
    episode_returns : list of float
        Total return per episode.
    """
    optimizer = optim.Adam(policy.parameters(), lr=lr)
    baseline = 0.0  # Running average baseline
    episode_returns = []

    for episode in range(n_episodes):
        state = env.reset()
        log_probs = []
        rewards = []

        # Collect a trajectory
        done = False
        while not done:
            state_t = torch.FloatTensor(state)
            dist = policy(state_t)
            action = dist.sample()
            log_probs.append(dist.log_prob(action))

            state, reward, done, _ = env.step(action.item())
            rewards.append(reward)

        # Compute discounted returns
        returns = []
        G = 0.0
        for r in reversed(rewards):
            G = r + gamma * G
            returns.insert(0, G)
        returns = torch.FloatTensor(returns)

        # Baseline subtraction reduces variance
        episode_return = returns[0].item()
        baseline = 0.99 * baseline + 0.01 * episode_return
        returns = returns - baseline

        # Policy gradient update
        loss = -torch.stack([lp * G for lp, G in zip(log_probs, returns)]).sum()
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        episode_returns.append(episode_return)

    return episode_returns
Listing 45.10: REINFORCE with a running-average baseline for discrete action spaces. Note: Gymnasium 0.26+ (2022) changed env.step() to return five values (obs, reward, terminated, truncated, info) instead of four. For current Gymnasium versions, replace done with terminated or truncated. The baseline subtraction does not change the expected gradient (it is a constant shift) but dramatically reduces variance, making learning more stable. The policy network outputs a categorical distribution over discrete actions.

REINFORCE is simple but suffers from high variance: each gradient estimate depends on the return of an entire trajectory, and trajectories vary widely. Two techniques reduce this variance: baseline subtraction (implemented above) and using a learned value function as the baseline, which leads us to actor-critic methods.

3. Actor-Critic Methods

An actor-critic architecture maintains two networks: the actor \(\pi_\theta(a \mid s)\) (the policy) and the critic \(V_\phi(s)\) (a value function that estimates the expected return from state \(s\)). The critic replaces the Monte Carlo return \(G_t\) with a lower-variance estimate, the advantage. Figure 45.3.1 illustrates Actor-Critic Architecture for Sequential Discovery.

Actor-Critic Architecture for Sequential Discovery
Figure 45.3.1: Actor-critic architecture for sequential discovery, showing the shared feature extractor feeding separate actor (policy) and critic (value) heads, with the advantage signal driving policy updates.
$$A(s_t, a_t) = R_t + \gamma V_\phi(s_{t+1}) - V_\phi(s_t)$$

The advantage measures how much better action \(a_t\) was compared to the average action from state \(s_t\). The policy gradient becomes:

$$\nabla_\theta J \approx \mathbb{E}\left[\sum_t \nabla_\theta \log \pi_\theta(a_t \mid s_t)\, A(s_t, a_t)\right]$$

Figure 45.3 illustrates how the actor and critic interact during training. The shared feature extractor feeds both heads, and the environment closes the loop by returning rewards and next states.

State s Shared Features f(s) Actor π(a|s) Critic V(s) Environ- ment action a reward R, next state s' Advantage A = R + γV(s') − V(s)
Figure 45.3: Actor-critic architecture. The shared feature extractor processes the state and feeds two heads: the actor (which outputs the policy distribution over actions) and the critic (which estimates the state value). The advantage, computed from the critic's estimates and the environment reward, guides the actor's gradient updates.

Mental Model

Think of the advantage function like a restaurant review that accounts for expectations. If you visit a restaurant in a neighborhood where most places are mediocre (low \(V_\phi(s)\)), even a decent meal produces a positive "advantage" (it exceeded the baseline). But the same meal at a restaurant in a world-class food district (high \(V_\phi(s)\)) produces a negative advantage, because it fell below what you expected from that location. The critic learns these neighborhood expectations, and the actor uses the relative surprise (positive or negative) to adjust its choices. Without this calibration, the agent would treat every good meal the same regardless of context, learning much more slowly.

The critic is trained to minimize the squared temporal-difference (TD) error (the gap between the critic's current value estimate and a one-step-ahead bootstrap target, used as a training signal to improve the critic): \(\mathcal{L}_\text{critic} = \mathbb{E}[(R_t + \gamma V_\phi(s_{t+1}) - V_\phi(s_t))^2]\).

class ActorCritic(nn.Module):
    """Combined actor-critic network with shared feature extractor."""

    def __init__(self, state_dim: int, n_actions: int, hidden: int = 128):
        super().__init__()
        # Shared feature layers
        self.shared = nn.Sequential(
            nn.Linear(state_dim, hidden),
            nn.ReLU(),
            nn.Linear(hidden, hidden),
            nn.ReLU(),
        )
        # Actor head: policy logits
        self.actor = nn.Linear(hidden, n_actions)
        # Critic head: state value
        self.critic = nn.Linear(hidden, 1)

    def forward(self, state: torch.Tensor):
        features = self.shared(state)
        policy = Categorical(logits=self.actor(features))
        value = self.critic(features).squeeze(-1)
        return policy, value


def train_actor_critic(
    env,
    model: ActorCritic,
    n_episodes: int = 2000,
    lr: float = 3e-4,
    gamma: float = 0.99,
    entropy_coef: float = 0.01,
) -> list:
    """Train an actor-critic agent on a discovery environment.

    Parameters
    ----------
    env : environment with reset/step interface
    model : ActorCritic network
    n_episodes : int
    lr : float
    gamma : float
    entropy_coef : float
        Entropy bonus to encourage exploration.

    Returns
    -------
    episode_returns : list of float
    """
    optimizer = optim.Adam(model.parameters(), lr=lr)
    episode_returns = []

    for episode in range(n_episodes):
        state = env.reset()
        total_reward = 0.0
        done = False

        log_probs, values, rewards, entropies = [], [], [], []

        while not done:
            state_t = torch.FloatTensor(state)
            policy, value = model(state_t)
            action = policy.sample()

            log_probs.append(policy.log_prob(action))
            values.append(value)
            entropies.append(policy.entropy())

            state, reward, done, _ = env.step(action.item())
            rewards.append(reward)
            total_reward += reward

        # Compute advantages using TD residuals
        returns = []
        G = 0.0
        for r in reversed(rewards):
            G = r + gamma * G
            returns.insert(0, G)
        returns = torch.FloatTensor(returns)
        values_t = torch.stack(values)

        advantages = returns - values_t.detach()

        # Combined loss: actor + critic + entropy
        actor_loss = -(torch.stack(log_probs) * advantages).sum()
        critic_loss = 0.5 * ((returns - values_t) ** 2).sum()
        entropy_loss = -torch.stack(entropies).sum()

        loss = actor_loss + critic_loss + entropy_coef * entropy_loss

        optimizer.zero_grad()
        loss.backward()
        nn.utils.clip_grad_norm_(model.parameters(), max_norm=0.5)
        optimizer.step()

        episode_returns.append(total_reward)

    return episode_returns
Listing 45.11: Actor-critic training loop with shared feature extractor and entropy regularization. Gradient clipping prevents destructive updates from high-variance episodes. The entropy bonus (a term added to the loss that rewards higher-entropy policy distributions) discourages premature convergence to a deterministic policy.

Common Misconception

A frequent misconception is that RL requires fewer evaluations than Bayesian optimization because it learns a "smarter" policy. In practice, the opposite is true for small budgets: RL needs hundreds or thousands of training episodes to learn a good policy, and each episode itself consumes a full sequence of evaluations. RL's advantage emerges only when the learned policy can be reused across many campaigns, when a simulator provides cheap training episodes, or when the sequential structure of the problem is so strong that no single-step method can capture it. For a one-off optimization with fewer than 50 evaluations and no simulator, Bayesian optimization remains the better choice.

Practical Example: Adaptive Experiment Sequencing

A materials science lab has a budget of 20 experiments to find a high-temperature superconductor. The state includes all compositions tested so far, their measured critical temperatures, and the remaining budget. The action is which composition to test next. A BO agent would treat this as 20 independent acquisition decisions. An RL agent can learn a strategy: "spend the first 5 experiments broadly surveying the phase diagram, then concentrate on the most promising region, but reserve 2 experiments for a final confirmation sweep." This staged strategy is a policy, not a point selection, and it adapts to intermediate results. In simulation studies on synthetic superconductor landscapes, RL agents trained via actor-critic typically achieve 15% to 25% higher best-found \(T_c\) compared to BO with expected improvement (EI), because they learn to allocate the fixed budget more strategically.

4. Continuous Action Spaces

The actor-critic framework above assumes a finite set of discrete actions, but many discovery problems require choosing from a continuum of possibilities.

When actions are continuous (e.g., choosing a composition vector or setting continuous process parameters), the policy outputs the parameters of a continuous distribution, typically a diagonal Gaussian (a multivariate normal distribution where each dimension is independent, so the covariance matrix contains only diagonal entries):

$$\pi_\theta(a \mid s) = \mathcal{N}\bigl(\mu_\theta(s),\; \text{diag}(\sigma_\theta(s))^2\bigr)$$

The log-probability under this distribution is:

$$\log \pi_\theta(a \mid s) = -\frac{1}{2}\sum_{i=1}^d \left[\frac{(a_i - \mu_i)^2}{\sigma_i^2} + \log \sigma_i^2 + \log 2\pi\right]$$

Checkpoint

So far: continuous action spaces replace the discrete categorical distribution with a diagonal Gaussian parameterized by a learned mean and variance, and the policy gradient still applies because we can compute the log-probability of any sampled action under that Gaussian.

Gradient interpretation

The policy gradient then pushes the mean \(\mu_\theta\) toward actions that yielded high advantage and adjusts the variance \(\sigma_\theta\) to explore more (if exploration helps) or less (if exploitation is sufficient).

class ContinuousActorCritic(nn.Module):
    """Actor-critic for continuous action spaces (Gaussian policy)."""

    def __init__(self, state_dim: int, action_dim: int, hidden: int = 128):
        super().__init__()
        self.shared = nn.Sequential(
            nn.Linear(state_dim, hidden),
            nn.Tanh(),
            nn.Linear(hidden, hidden),
            nn.Tanh(),
        )
        self.mu_head = nn.Linear(hidden, action_dim)
        self.log_std = nn.Parameter(torch.zeros(action_dim))  # Learnable
        self.value_head = nn.Linear(hidden, 1)

    def forward(self, state: torch.Tensor):
        features = self.shared(state)
        mu = self.mu_head(features)
        std = self.log_std.exp().expand_as(mu)
        dist = torch.distributions.Normal(mu, std)
        value = self.value_head(features).squeeze(-1)
        return dist, value

    def act(self, state: torch.Tensor):
        """Sample an action and return it with log-probability and value."""
        dist, value = self.forward(state)
        action = dist.sample()
        log_prob = dist.log_prob(action).sum(-1)  # Sum over action dimensions
        return action, log_prob, value
Listing 45.12: Continuous actor-critic with a diagonal Gaussian policy and learnable log standard deviation. Tanh activations keep the hidden representations bounded, which improves stability for continuous control. The act method sums log-probabilities across action dimensions because the diagonal Gaussian treats each dimension independently.

5. When RL Outperforms Static Optimization

RL is not always the right tool. Its advantages over BO and evolutionary methods emerge in specific settings:

Real-World Application: Retrosynthesis Planning
Real-World Application: Retrosynthesis Planning

RL struggles when:

Connection: RL and Experiment Design

The RL formulation here is the foundation for automated experiment design in Chapter 46. There, the MDP state includes the current dataset, the GP posterior, and the remaining experimental budget. The action is which experiment to run. The reward is information gain or improvement in the best-found value. The policy learns to sequence experiments adaptively, which is precisely what a skilled experimentalist does intuitively. The self-driving laboratories in Chapter 55 deploy these RL policies on robotic platforms that execute experiments autonomously.

6. Scaling with Ray Tune

Knowing when RL fits is only half the battle; training a policy to convergence can itself become a computational bottleneck.

Training RL policies requires many episodes, and each episode may involve expensive evaluations. Ray Tune distributes this workload across a cluster:

from ray import tune
from ray.tune.schedulers import ASHAScheduler

def train_rl_agent(config):
    """Training function compatible with Ray Tune."""
    env = DiscoveryEnv(
        n_compounds=config["n_compounds"],
        noise_level=config["noise_level"],
    )
    model = ActorCritic(
        state_dim=env.observation_space.shape[0],
        n_actions=env.action_space.n,
        hidden=config["hidden_size"],
    )
    returns = train_actor_critic(
        env, model,
        n_episodes=config["n_episodes"],
        lr=config["lr"],
        gamma=config["gamma"],
        entropy_coef=config["entropy_coef"],
    )
    # Report final performance to Ray Tune
    tune.report(mean_return=np.mean(returns[-100:]))


# Hyperparameter search over RL training configs
scheduler = ASHAScheduler(
    metric="mean_return",
    mode="max",
    max_t=5000,
    grace_period=500,
    reduction_factor=3,
)

analysis = tune.run(
    train_rl_agent,
    config={
        "n_compounds": 100,
        "noise_level": 0.1,
        "hidden_size": tune.choice([64, 128, 256]),
        "lr": tune.loguniform(1e-4, 1e-2),
        "gamma": tune.uniform(0.95, 0.999),
        "entropy_coef": tune.loguniform(1e-3, 1e-1),
        "n_episodes": 5000,
    },
    num_samples=20,
    scheduler=scheduler,
)
Listing 45.13: Distributed RL hyperparameter tuning with Ray Tune and the ASHA scheduler (Asynchronous Successive Halving Algorithm, which early-stops underperforming trials to concentrate compute on the most promising configurations). Each trial trains a full RL agent; Ray handles parallelization across available resources. As of 2024, Ray 2.x deprecates tune.run() and tune.report() in favor of the Tuner API and ray.train.report(); the pattern shown here still works but new projects should adopt the updated interface.
Library Shortcut: Stable-Baselines3

For standard RL algorithms (Proximal Policy Optimization (PPO), Soft Actor-Critic (SAC), Advantage Actor-Critic (A2C)), Stable-Baselines3 provides production-quality implementations:

from stable_baselines3 import PPO

model = PPO("MlpPolicy", env, verbose=1, learning_rate=3e-4)
model.learn(total_timesteps=100_000)

# Deploy the trained policy
obs = env.reset()
for _ in range(50):
    action, _ = model.predict(obs, deterministic=True)
    obs, reward, done, info = env.step(action)
Listing 45.14: Training and deploying a PPO agent with Stable-Baselines3 in five lines. The MlpPolicy string selects a two-layer feedforward network; deterministic=True disables exploration noise at deployment time.

As of Gymnasium 0.26+ (2022), env.step() returns five values; replace the four-value unpacking with obs, reward, terminated, truncated, info = env.step(action) and use terminated or truncated in place of done.

Stable-Baselines3 reduces the actor-critic implementation (approximately 100 lines above) to 3 lines, and adds vectorized environments, automatic logging, callback hooks, and pre-implemented algorithms (PPO, SAC, Twin Delayed DDPG (TD3), A2C) that would require thousands of lines to implement correctly.

7. Multi-Objective RL

When the discovery MDP has multiple reward signals (e.g., compound activity and synthetic cost at each step), the policy gradient extends to handle vector-valued rewards. The simplest approach applies linear scalarization (combining multiple objectives into a single number by weighting each one): \(R = \sum_i w_i R_i\) with weights \(\mathbf{w}\) encoding the scientist's preferences. A more sophisticated approach trains a family of policies indexed by \(\mathbf{w}\), producing a Pareto front (the set of solutions where no objective can be improved without worsening another) of policies analogous to the Pareto front of solutions in Non-dominated Sorting Genetic Algorithm III (NSGA-III). Recent multi-objective RL (MORL) methods maintain a set of policies that span the achievable trade-offs, connecting RL to the multi-objective framework of Section 45.2.

The Discovery Workbench integrates RL agents through the same suggest/observe interface as the Bayesian optimizer in Section 45.1. The key deployment difference: RL agents maintain internal state (policy activations or episode context) and require checkpointing, while BO agents are stateless, reconstructing their strategy from the observation history alone.

Research Frontier

The Decision Transformer architecture (a model that recasts RL as autoregressive sequence prediction, conditioning action generation on desired future returns rather than learning a value function) (Chen et al., 2021) reframed RL as sequence modeling, but its impact on scientific discovery accelerated with subsequent work. In 2024, Anstine and Isayev introduced AcTrainer, a framework that applies the return-conditioned transformer paradigm specifically to molecular optimization, training on offline datasets of molecular trajectories to generate novel compounds conditioned on desired property targets. By casting molecule generation as a sequence decision problem and conditioning on high return values at inference time, AcTrainer sidesteps the need for environment simulators or online rollouts entirely. This "offline RL via transformers" approach is especially relevant for discovery settings where real experiments are expensive and historical datasets are abundant: the model learns a policy from logged data without ever interacting with the environment during training.

Try It: Train an RL Agent on CartPole, Then Swap in a Custom Reward

This mini-project takes you from a working RL agent on a standard benchmark to a modified objective that mimics discovery-style reward shaping, all using standard Python libraries.

Step 1. Install dependencies: pip install gymnasium torch numpy matplotlib. Create a new Python file and import Gymnasium's CartPole-v1 environment.

Step 2. Copy the PolicyNetwork and reinforce() function from Listing 45.10 above. Run REINFORCE on CartPole-v1 for 1000 episodes (state_dim=4, n_actions=2) and plot the episode returns over time. You should see returns climbing toward 500.

Step 3. Wrap CartPole in a custom Gymnasium wrapper that replaces the default +1 per-step reward with a "discovery-style" reward: assign +0.1 per step (survival), but add +5.0 bonus whenever the cart crosses the center position (x=0) from either direction (simulating an "informative measurement" event). Track how many center-crossings occur per episode.

Step 4. Train a fresh REINFORCE agent on your wrapped environment for 1000 episodes. Compare the learning curves and final policies: the reshaped agent should learn to oscillate through center rather than simply balancing, demonstrating how reward design steers the learned strategy.

Step 5. Experiment with the discount factor \(\gamma\). Train three agents with \(\gamma \in \{0.9, 0.99, 0.999\}\) on your custom reward and plot all three learning curves. Observe how lower \(\gamma\) makes the agent favor immediate center-crossings while higher \(\gamma\) produces smoother, longer trajectories. This tradeoff mirrors the exploration vs. exploitation balance in real discovery campaigns.

Exercise 45.3.1

Consider a drug screening campaign with a budget of 10 experiments and 50 candidate compounds. The state is the set of tested compounds and their measured activities. The action is which compound to test next. Write out the dimensions of the state space (how many possible states exist after \(k\) tests?) and explain why this rapid growth makes tabular RL infeasible, motivating the function approximation approach used in Listings 45.10 and 45.11.

Hint

After \(k\) tests you have chosen \(k\) compounds out of 50 (order matters for the MDP trajectory), and each tested compound has a continuous activity value. The number of possible ordered selections is \(\frac{50!}{(50-k)!}\), and each comes with \(k\) continuous observations. Even for \(k = 5\), the ordered selection count alone exceeds \(2.5 \times 10^8\). A neural network policy sidesteps this by generalizing across similar states.

Step-Through: One REINFORCE Update

Trace through a single REINFORCE gradient update on a 3-step episode with \(\gamma = 0.9\) and two actions (Left, Right).

Episode trajectory: $s_0 \xrightarrow{a_0=\text{Left}} s_1 \xrightarrow{a_1=\text{Right}} s_2 \xrightarrow{a_2=\text{Left}} s_3$ (terminal). Rewards: \(r_0 = 0\), \(r_1 = 1\), \(r_2 = 3\).

Step 1: Compute returns. \(G_2 = 3\). \(G_1 = 1 + 0.9 \times 3 = 3.7\). \(G_0 = 0 + 0.9 \times 3.7 = 3.33\).

Step 2: Subtract baseline. Suppose the running baseline is \(b = 2.0\). Adjusted returns: \(G_0 - b = 1.33\), \(G_1 - b = 1.7\), \(G_2 - b = 1.0\).

Step 3: Compute loss. Suppose the policy gave \(\log \pi(a_0 \mid s_0) = -0.7\), \(\log \pi(a_1 \mid s_1) = -0.5\), \(\log \pi(a_2 \mid s_2) = -0.8\). The loss is $-[(-0.7)(1.33) + (-0.5)(1.7) + (-0.8)(1.0)] = -(0.931 + 0.85 + 0.8) = -2.581$.

Step 4: Gradient direction. Backpropagation on this negative loss increases the log-probabilities of all three actions (all had positive adjusted returns). Action \(a_1\) (Right at \(s_1\)) gets the largest boost because its adjusted return (1.7) is highest: the policy learns that choosing Right in state \(s_1\) was the most above-average decision in this episode.

Real-World Application: Retrosynthesis Planning

IBM's RXN for Chemistry platform uses an RL-based retrosynthesis planner that treats multi-step synthesis route design as an MDP. The state encodes the target molecule and available building blocks; actions select reaction templates; and the reward is +1 for reaching purchasable starting materials within a step budget. By training the policy on millions of known reactions from patent literature, the system discovers synthesis routes for novel drug candidates that human chemists have rated as plausible in over 80% of evaluated cases, according to IBM's published benchmarks.

The Agent That Learned to Pause

In 2020, researchers at the University of Liverpool (Burger et al.) deployed an autonomous robotic chemist to optimize chemical reactions in a robotic lab and discovered something unexpected: the agent learned to wait. Rather than immediately adding reagents, it would pause mid-reaction, allowing an intermediate to crystallize before proceeding. No human operator had documented this strategy, yet the agent independently discovered that patience improved yield. The finding, published in Nature (2020), demonstrated that RL policies can uncover procedural knowledge invisible to standard optimization methods because static optimizers never consider "do nothing" as a valuable action.

Lab: RL vs. Random Search on a Sequential Discovery Task

Goal: Compare an RL agent against a random baseline on a sequential compound screening task and measure how quickly each finds the top compound.

Tools: Python 3.10+, gymnasium, stable-baselines3, numpy, matplotlib (pip install gymnasium stable-baselines3 numpy matplotlib).

Setup (5 min): Create a custom Gymnasium environment representing a library of 100 compounds with hidden activities drawn from a mixture of two Gaussians (a few high-activity "hits" and many low-activity "misses"). The state is a binary vector indicating which compounds have been tested plus the observed activities. The action selects the next compound. The episode ends after 15 tests.

Experiment (15 min): Train a PPO agent from Stable-Baselines3 for 50,000 timesteps. Then run 200 evaluation episodes each for (a) the trained PPO policy and (b) a random agent that selects untested compounds uniformly. Record the best activity found in each episode.

What to vary: Change the fraction of hits (5%, 10%, 20%) and the budget (10, 15, 25 tests). Plot the median best-found activity for PPO vs. random across these conditions.

What to observe: The RL agent should outperform random search most dramatically when hits are rare and budgets are tight, because the policy learns to exploit early observations (testing compounds similar to early hits). When hits are abundant or budgets large, the gap shrinks because random search stumbles onto hits by chance.

What's Next

We now have three optimization paradigms: Bayesian optimization for sample-efficient black-box search, evolutionary methods for multi-objective population-based search, and reinforcement learning for sequential decision-making. Section 45.4: Building a Multi-Objective Optimizer integrates these into a working pipeline that combines NSGA-III with BoTorch GP surrogates to solve a realistic multi-objective discovery problem, with hypervolume comparison across methods.