Chapter 1: Why Reinforcement Learning for Materials Science

📖 Reading Time: 20-25 min 📊 Difficulty: Beginner 💻 Code Examples: 6 📝 Exercises: 3

Chapter 1: Why Reinforcement Learning for Materials Science

Grasp how to optimize materials and processes within a sequential decision-making framework. We also introduce the pitfalls of reward design.

💡 Note: A reward is a "prize for an action." If you get the balance between short-term and long-term prizes wrong, learning goes astray.

Learning Objectives

In this chapter, you will learn the following:


1.1 Challenges in Materials Discovery and the Role of Reinforcement Learning

Limitations of Conventional Materials Discovery

Developing new materials involves an enormous search space (composition, structure, process conditions):

With the conventional trial-and-error approach: - Reliance on the researcher's experience and intuition - Time- and cost-intensive evaluation (several weeks to several months per material) - Prone to getting stuck in local optima

flowchart LR A[Researcher] -->|Experience & Intuition| B[Material Candidate Selection] B -->|Synthesis & Evaluation| C[Result] C -->|Interpretation| A style A fill:#ffcccc style B fill:#ffcccc style C fill:#ffcccc

Problems: 1. Inefficient: Repeatedly tries similar materials 2. Narrow search: Limited to the scope of the researcher's knowledge 3. Low reproducibility: Reliant on tacit knowledge

Solution via Reinforcement Learning

Reinforcement learning is a framework that learns optimal actions through interaction with an environment:

flowchart LR A[Agent: RL Algorithm] -->|Action: Material Candidate| B[Environment: Experiment/Computation] B -->|Reward: Property Evaluation| A B -->|State: Current Knowledge| A style A fill:#e1f5ff style B fill:#ffe1cc

Advantages of reinforcement learning: 1. Automatic optimization: Automates trial and error and learns efficient search strategies 2. Balance of exploration and exploitation: Adjusts between exploring unknown regions and exploiting known good regions 3. Sequential improvement: Learns from each evaluation result and improves the next choice 4. Closed loop: Can integrate with experimental equipment and operate 24 hours a day

Success Stories in Materials Science

Example 1: Optimization of Li-ion battery electrolytes (MIT, 2022) - Challenge: Optimize the blending ratios of 5 components (search space > $10^6$) - Method: Sequentially select formulations with DQN - Result: Found the optimal solution 5 times faster than conventional methods, with a 30% improvement in ionic conductivity

Example 2: Donor materials for organic solar cells (University of Toronto, 2021) - Challenge: Optimize molecular structure (10^23 candidates) - Method: Integrate molecular generation and evaluation with Actor-Critic - Result: Discovered a new material with 15% photoelectric conversion efficiency in 3 months (conventionally 2 years)


1.2 Fundamentals of the Markov Decision Process (MDP)

What Is an MDP?

The mathematical foundation of reinforcement learning is the Markov Decision Process (MDP). An MDP is defined by the following 5-tuple:

$$ \text{MDP} = (S, A, P, R, \gamma) $$

Mapping to Materials Discovery

MDP Element Meaning in Materials Discovery Concrete Example
State $s$ Current knowledge (evaluation results so far) "Material A: band gap 2.1eV, Material B: 2.5eV"
Action $a$ The next material to try "Material C with Ti-Ni-O composition"
Reward $r$ Evaluation value of material property "Material C band gap 2.8eV (close to the 3.0eV target)"
Policy $\pi$ Material selection strategy "Prioritize elemental compositions whose band gap is close to the target"

The Markov Property

An important assumption of the MDP is the Markov property:

$$ P(s_{t+1}|s_t, a_t, s_{t-1}, a_{t-1}, \dots) = P(s_{t+1}|s_t, a_t) $$

In other words, the next state depends only on the current state and action, and the past history is unnecessary.

In materials discovery, if you choose the next material (action) based on the current evaluation results (state), there is no need to remember the entire past history.

Policy and Value Functions

Policy $\pi(a|s)$: The probability of choosing action $a$ in state $s$

State value function $V^\pi(s)$: The expected cumulative reward when acting according to policy $\pi$ starting from state $s$

$$ V^\pi(s) = \mathbb{E}_\pi \left[ \sum_{t=0}^\infty \gamma^t r_t \mid s_0 = s \right] $$

Action value function (Q-function) $Q^\pi(s, a)$: The expected cumulative reward when taking action $a$ in state $s$ and then following policy $\pi$

$$ Q^\pi(s, a) = \mathbb{E}_\pi \left[ \sum_{t=0}^\infty \gamma^t r_t \mid s_0 = s, a_0 = a \right] $$

Optimal policy $\pi^*$: The policy that maximizes the value function in every state

$$ \pi^* = \arg\max_\pi V^\pi(s) \quad \forall s \in S $$


1.3 Q-Learning

The Basic Idea of Q-Learning

Q-learning is a reinforcement learning algorithm that learns the Q-function directly.

Bellman equation: $$ Q^*(s, a) = \mathbb{E}_{s'} \left[ r + \gamma \max_{a'} Q^*(s', a') \mid s, a \right] $$

This means "the optimal Q-function equals the discounted sum of the immediate reward $r$ and the maximum Q-value in the next state."

The Q-Learning Update Rule

Based on the observed transition $(s, a, r, s')$, update the Q-value:

$$ Q(s, a) \leftarrow Q(s, a) + \alpha \left[ r + \gamma \max_{a'} Q(s', a') - Q(s, a) \right] $$

Implementation in Python

We implement Q-learning in a simple grid world (a metaphor for the materials search space):

"""
Implementation of a materials discovery environment using Q-learning

Dependencies (libraries and versions):
- Python: 3.9+
- numpy: 1.24+
- matplotlib: 3.7+

Reproducibility:
- Fixed random seed: 42 (unified across all random operations)
- Number of episodes: 1000 (convergence confirmed)
- Learning rate alpha: 0.1 (prevents excessive updates)
- Discount factor gamma: 0.99 (emphasizes long-term rewards)
- epsilon-greedy: epsilon=0.1 fixed (10% exploration, 90% exploitation)

Pitfalls (practical caveats):
1. Because epsilon is fixed, exploration continues even late in training (room for optimization)
2. Q-table size is 5x5x4=100 elements (only suitable for small environments)
3. Rewards are sparse (+10 only at the goal), so exploration may be difficult
"""

import numpy as np
import matplotlib.pyplot as plt

# Fix the random seed (ensure reproducibility)
RANDOM_SEED = 42
np.random.seed(RANDOM_SEED)

class SimpleMaterialsEnv:
    """Simple materials discovery environment (grid world)

    - 5x5 grid
    - Each cell represents a material candidate
    - Goal: reach the material with the best properties (the goal)
    """
    def __init__(self):
        self.grid_size = 5
        self.state = (0, 0)  # Start position
        self.goal = (4, 4)   # Goal position (optimal material)

    def reset(self):
        """Reset to the initial state"""
        self.state = (0, 0)
        return self.state

    def step(self, action):
        """Execute an action

        Args:
            action: 0=up, 1=down, 2=left, 3=right

        Returns:
            next_state, reward, done
        """
        x, y = self.state

        # Move according to the action
        if action == 0 and x > 0:  # Up
            x -= 1
        elif action == 1 and x < self.grid_size - 1:  # Down
            x += 1
        elif action == 2 and y > 0:  # Left
            y -= 1
        elif action == 3 and y < self.grid_size - 1:  # Right
            y += 1

        self.state = (x, y)

        # Reward design
        if self.state == self.goal:
            reward = 10.0  # Reached the goal (optimal material found)
            done = True
        else:
            reward = -0.1  # Cost of each step (experimental cost)
            done = False

        return self.state, reward, done

    def get_state_space(self):
        """Size of the state space"""
        return self.grid_size * self.grid_size

    def get_action_space(self):
        """Size of the action space"""
        return 4


def q_learning(env, episodes=1000, alpha=0.1, gamma=0.99, epsilon=0.1):
    """Q-learning algorithm

    Args:
        env: environment
        episodes: number of episodes
        alpha: learning rate
        gamma: discount factor
        epsilon: probability of epsilon-greedy exploration

    Returns:
        The learned Q-table
    """
    # Initialize the Q-table (state x action)
    Q = np.zeros((env.grid_size, env.grid_size, env.get_action_space()))

    rewards_per_episode = []

    for episode in range(episodes):
        state = env.reset()
        total_reward = 0
        done = False

        while not done:
            # epsilon-greedy exploration
            if np.random.random() < epsilon:
                action = np.random.randint(env.get_action_space())  # Random exploration
            else:
                action = np.argmax(Q[state[0], state[1], :])  # Exploit the best action

            # Execute the action
            next_state, reward, done = env.step(action)
            total_reward += reward

            # Update the Q-value (Bellman equation)
            current_q = Q[state[0], state[1], action]
            max_next_q = np.max(Q[next_state[0], next_state[1], :])
            new_q = current_q + alpha * (reward + gamma * max_next_q - current_q)
            Q[state[0], state[1], action] = new_q

            state = next_state

        rewards_per_episode.append(total_reward)

        if (episode + 1) % 100 == 0:
            avg_reward = np.mean(rewards_per_episode[-100:])
            print(f"Episode {episode+1}: Avg Reward = {avg_reward:.2f}")

    return Q, rewards_per_episode


# Execution
env = SimpleMaterialsEnv()
Q, rewards = q_learning(env, episodes=1000)

# Visualize the learning curve
plt.figure(figsize=(10, 6))
plt.plot(np.convolve(rewards, np.ones(50)/50, mode='valid'))
plt.xlabel('Episode')
plt.ylabel('Average Reward (50 episodes)')
plt.title('Q-Learning: Learning Progress in the Materials Discovery Environment')
plt.grid(True)
plt.show()

# Visualize the learned Q-values
policy = np.argmax(Q, axis=2)
print("\nLearned policy (best action in each cell):")
print("0=up, 1=down, 2=left, 3=right")
print(policy)

Example output:

Episode 100: Avg Reward = -4.52
Episode 200: Avg Reward = -3.21
Episode 500: Avg Reward = -1.85
Episode 1000: Avg Reward = -1.12

Learned policy (best action in each cell):
[[1 1 1 1 1]
 [1 1 1 1 1]
 [1 1 1 1 1]
 [1 1 1 1 1]
 [3 3 3 3 0]]

Explanation: - Initially the reward is low (-4.52), but it improves as learning progresses (-1.12) - Eventually, the shortest path to the goal is learned (a down-then-right policy)


1.4 Deep Q-Network (DQN)

Limitations of Q-Learning

Q-learning is effective when states and actions are discrete and few in number. However, in materials science:

The DQN Solution

DQN approximates the Q-function with a neural network:

$$ Q(s, a; \theta) \approx Q^*(s, a) $$

Loss function: $$ L(\theta) = \mathbb{E}_{(s,a,r,s') \sim D} \left[ \left( r + \gamma \max_{a'} Q(s', a'; \theta^-) - Q(s, a; \theta) \right)^2 \right] $$

Key Techniques of DQN

  1. Experience Replay: Randomly samples past transitions to reduce data correlation
  2. Target Network: Computes the TD target with a fixed network to stabilize learning
  3. epsilon-greedy exploration: Balances exploration (random) and exploitation (best action)

DQN Implementation in PyTorch

import torch
import torch.nn as nn
import torch.optim as optim
from collections import deque
import random

class DQN(nn.Module):
    """Deep Q-Network

    Takes a state as input and outputs the Q-value for each action
    """
    def __init__(self, state_dim, action_dim, hidden_dim=64):
        super(DQN, self).__init__()
        self.fc1 = nn.Linear(state_dim, hidden_dim)
        self.fc2 = nn.Linear(hidden_dim, hidden_dim)
        self.fc3 = nn.Linear(hidden_dim, action_dim)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        return self.fc3(x)


class ReplayBuffer:
    """Experience replay buffer"""
    def __init__(self, capacity=10000):
        self.buffer = deque(maxlen=capacity)

    def push(self, state, action, reward, next_state, done):
        self.buffer.append((state, action, reward, next_state, done))

    def sample(self, batch_size):
        return random.sample(self.buffer, batch_size)

    def __len__(self):
        return len(self.buffer)


class DQNAgent:
    """DQN agent"""
    def __init__(self, state_dim, action_dim, lr=1e-3, gamma=0.99, epsilon=1.0, epsilon_decay=0.995, epsilon_min=0.01):
        self.action_dim = action_dim
        self.gamma = gamma
        self.epsilon = epsilon
        self.epsilon_decay = epsilon_decay
        self.epsilon_min = epsilon_min

        # Main network and target network
        self.policy_net = DQN(state_dim, action_dim)
        self.target_net = DQN(state_dim, action_dim)
        self.target_net.load_state_dict(self.policy_net.state_dict())
        self.target_net.eval()

        self.optimizer = optim.Adam(self.policy_net.parameters(), lr=lr)
        self.buffer = ReplayBuffer()

    def select_action(self, state):
        """epsilon-greedy action selection"""
        if np.random.random() < self.epsilon:
            return np.random.randint(self.action_dim)
        else:
            with torch.no_grad():
                state_tensor = torch.FloatTensor(state).unsqueeze(0)
                q_values = self.policy_net(state_tensor)
                return q_values.argmax().item()

    def train(self, batch_size=64):
        """Mini-batch training"""
        if len(self.buffer) < batch_size:
            return

        # Mini-batch sampling
        batch = self.buffer.sample(batch_size)
        states, actions, rewards, next_states, dones = zip(*batch)

        states = torch.FloatTensor(states)
        actions = torch.LongTensor(actions).unsqueeze(1)
        rewards = torch.FloatTensor(rewards).unsqueeze(1)
        next_states = torch.FloatTensor(next_states)
        dones = torch.FloatTensor(dones).unsqueeze(1)

        # Current Q-values
        current_q = self.policy_net(states).gather(1, actions)

        # Target Q-values
        with torch.no_grad():
            max_next_q = self.target_net(next_states).max(1)[0].unsqueeze(1)
            target_q = rewards + (1 - dones) * self.gamma * max_next_q

        # Loss computation and optimization
        loss = nn.MSELoss()(current_q, target_q)
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

        # Decay epsilon
        self.epsilon = max(self.epsilon_min, self.epsilon * self.epsilon_decay)

    def update_target_network(self):
        """Update the target network"""
        self.target_net.load_state_dict(self.policy_net.state_dict())


# Materials discovery environment (continuous-state version)
class ContinuousMaterialsEnv:
    """Materials discovery environment with a continuous state space"""
    def __init__(self, state_dim=4):
        self.state_dim = state_dim
        self.target = np.array([3.0, 5.0, 2.5, 4.0])  # Target properties
        self.state = None

    def reset(self):
        self.state = np.random.uniform(0, 10, self.state_dim)
        return self.state

    def step(self, action):
        # Actions: 0=increase, 1=decrease, 2=large increase, 3=large decrease
        delta = [0.1, -0.1, 0.5, -0.5][action]

        # Change a random dimension
        dim = np.random.randint(self.state_dim)
        self.state[dim] = np.clip(self.state[dim] + delta, 0, 10)

        # Reward: distance from the target (negative; closer is better)
        distance = np.linalg.norm(self.state - self.target)
        reward = -distance

        # Termination condition: sufficiently close to the target
        done = distance < 0.5

        return self.state, reward, done


# DQN training
env = ContinuousMaterialsEnv()
agent = DQNAgent(state_dim=4, action_dim=4)

episodes = 500
rewards_history = []

for episode in range(episodes):
    state = env.reset()
    total_reward = 0
    done = False

    while not done:
        action = agent.select_action(state)
        next_state, reward, done = env.step(action)

        agent.buffer.push(state, action, reward, next_state, done)
        agent.train()

        state = next_state
        total_reward += reward

    rewards_history.append(total_reward)

    # Update the target network
    if (episode + 1) % 10 == 0:
        agent.update_target_network()

    if (episode + 1) % 50 == 0:
        avg_reward = np.mean(rewards_history[-50:])
        print(f"Episode {episode+1}: Avg Reward = {avg_reward:.2f}, epsilon = {agent.epsilon:.3f}")

# Learning curve
plt.figure(figsize=(10, 6))
plt.plot(np.convolve(rewards_history, np.ones(20)/20, mode='valid'))
plt.xlabel('Episode')
plt.ylabel('Average Reward (20 episodes)')
plt.title('DQN: Learning Progress in Continuous-State Materials Discovery')
plt.grid(True)
plt.show()

Example output:

Episode 50: Avg Reward = -45.23, epsilon = 0.779
Episode 100: Avg Reward = -32.15, epsilon = 0.606
Episode 200: Avg Reward = -18.92, epsilon = 0.365
Episode 500: Avg Reward = -8.45, epsilon = 0.010

Explanation: - The neural network learns the Q-function for continuous states - epsilon decays, shifting from exploration to exploitation - Eventually, materials close to the target properties are found efficiently


Exercises

Problem 1 (Difficulty: easy)

In the Q-learning update rule, explain what happens when you increase the learning rate $\alpha$. Also, describe what happens in the extreme cases of $\alpha=0$ and $\alpha=1$.

Hint The learning rate controls "how much to weight new information." Take another look at the update rule.
Sample Solution **When you increase $\alpha$**: - New observations (the TD target) are strongly reflected, and Q-values change significantly - Learning is fast but tends to become unstable **Extreme cases**: - **$\alpha=0$**: Q-values are not updated at all (no learning) $$Q(s,a) \leftarrow Q(s,a) + 0 \cdot [\cdots] = Q(s,a)$$ - **$\alpha=1$**: Q-values are completely replaced by the TD target $$Q(s,a) \leftarrow r + \gamma \max_{a'} Q(s', a')$$ Past information vanishes entirely, depending only on the latest observation **In practice**: $\alpha = 0.01 \sim 0.1$ is common

Problem 2 (Difficulty: medium)

In materials discovery, a reward function was designed as follows. State the problems with this design and propose improvements.

def reward_function(material_property, target=3.0):
    if material_property == target:
        return 1.0
    else:
        return 0.0
Hint This reward is called a "sparse reward" and is 0 everywhere unless the target is reached. Think about how it affects learning.
Sample Solution **Problems**: 1. **Sparse reward**: The reward is 0 in most cases, so the learning signal is weak 2. **Difficult exploration**: There is no way to know which direction to move 3. **Exact match**: A perfect match on a real-valued quantity is nearly impossible **Improvement**:
def improved_reward_function(material_property, target=3.0):
    # Continuous reward based on distance from the target
    distance = abs(material_property - target)

    if distance < 0.1:
        return 10.0  # Very close (bonus)
    elif distance < 0.5:
        return 5.0   # Close
    else:
        return -distance  # The farther, the greater the penalty
**Further improvements**: - **Shaping reward**: Give intermediate rewards according to progress toward the target - **Multi-objective reward**: Consider multiple properties (band gap + stability)

Problem 3 (Difficulty: hard)

Explain the roles of "experience replay" and the "target network" in DQN, and use Python code to experiment with what problems occur when each is absent.

Hint To turn off experience replay, use only the latest transition instead of `buffer.sample()`. To turn off the target network, use `self.policy_net` when computing the TD target.
Sample Solution **Role of experience replay**: - Randomly samples past transitions to reduce data correlation - Without it, learning uses only consecutive transitions and overfits to specific patterns **Role of the target network**: - Computes the TD target with a fixed network to stabilize learning - Without it, Q-values oscillate and struggle to converge **Experiment code**:
# Version without experience replay
class DQNAgentNoReplay(DQNAgent):
    def train_no_replay(self, state, action, reward, next_state, done):
        # Train on only the latest transition
        states = torch.FloatTensor([state])
        actions = torch.LongTensor([action]).unsqueeze(1)
        rewards = torch.FloatTensor([reward]).unsqueeze(1)
        next_states = torch.FloatTensor([next_state])
        dones = torch.FloatTensor([done]).unsqueeze(1)

        current_q = self.policy_net(states).gather(1, actions)
        with torch.no_grad():
            max_next_q = self.target_net(next_states).max(1)[0].unsqueeze(1)
            target_q = rewards + (1 - dones) * self.gamma * max_next_q

        loss = nn.MSELoss()(current_q, target_q)
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

# Version without a target network (uses policy_net for the TD target)
# -> Learning becomes unstable

# Result: Without experience replay, convergence is slow; without the target network, it oscillates

Summary of This Section

In the next chapter, we will learn more advanced policy gradient methods and Actor-Critic techniques.


Quality Checklist: Verifying Your Materials Discovery RL Implementation

MDP Formulation Skills

Q-Learning Implementation Skills

DQN Implementation Skills

Considerations Specific to Materials Discovery

Debugging Skills

Code Quality

Next Steps

If your achievement is below 80%: - Re-read this chapter and redo the exercises - Get hands-on implementation experience with a simple grid world

If your achievement is 80-95%: - You are ready to proceed to Chapter 2 (policy gradient methods) - Try implementing the DQN code from scratch yourself

If your achievement is 95% or higher: - Proceed to Chapter 2 and learn more advanced methods - Try RL on an actual materials discovery task


References

  1. Mnih et al. "Playing Atari with Deep Reinforcement Learning" arXiv (2013) - The original DQN paper
  2. Sutton & Barto "Reinforcement Learning: An Introduction" MIT Press (2018) - RL textbook
  3. Zhou et al. "Optimization of molecules via deep reinforcement learning" Scientific Reports (2019)
  4. Ling et al. "High-dimensional materials and process optimization using data-driven experimental design with well-calibrated uncertainty estimates" Integrating Materials and Manufacturing Innovation (2017)

Next Chapter: Chapter 2: Theoretical Foundations of Reinforcement Learning

Disclaimer