Prerequisites
This is the opening section of the book. No prior chapters are needed. We assume familiarity with Python (functions, classes, basic NumPy), undergraduate probability (expectation, Bayes' theorem), and comfort reading mathematical notation. See Appendix A for a refresher on the mathematical foundations and Appendix B for the Python setup.
Discovery, invention, and innovation are three distinct activities that humans have practiced for millennia. All three can be formalized as search: navigating a space of possibilities to find elements that satisfy some objective. This section builds the formal framework, a five-tuple \((S, A, T, f, C)\), that structures every discovery problem in this book. Whether we are discovering a new drug molecule, inventing a novel algorithm, or innovating a business process, the same mathematical skeleton applies. Making this skeleton explicit is the first step toward building AI systems that accelerate each activity.
1. Three Faces of Finding the New
When Alexander Fleming noticed a mold killing bacteria in 1928, when engineers at Bell Labs assembled the first transistor in 1947, and when the Allied military shipped mass-produced penicillin to battlefield hospitals in 1944, were they doing the same thing? All three moments feel related, yet each represents a fundamentally different activity, and the differences determine what kind of search problem we are solving, what objective function we optimize, and what constraints we face.
Discovery is the act of finding something that already exists but was previously unknown. Penicillin existed in mold long before Alexander Fleming noticed its antibacterial properties in 1928. The Higgs boson existed (or did not) regardless of whether CERN's detectors found it. Discovery reveals pre-existing structure in the world. In search terms, the target exists somewhere in the space; our job is to locate it.
Invention is the act of creating something that did not previously exist. The transistor, the TCP/IP protocol, and the transformer architecture were all brought into being by human ingenuity. In search terms, we are not locating a fixed target but constructing a novel element within a generative space of possible artifacts.
Innovation is the act of deploying a discovery or invention in a way that creates value in a specific context. Penicillin the molecule is a discovery; the industrial process for mass-producing it is an invention; deploying it to treat battlefield infections in World War II is an innovation. Innovation searches a space of deployment strategies, business models, and sociotechnical configurations (the interplay of technology, organizations, regulations, and user behavior that determines whether an invention succeeds in practice). In short: Discovery finds what exists, invention builds what does not, and innovation makes either one matter.
Common Misconception
Readers often assume that discovery, invention, and innovation form a fixed pipeline: first you discover, then you invent, then you innovate. In reality, these activities can occur in any order, in parallel, or in feedback loops. An innovation need (such as reducing manufacturing cost) can motivate a new invention (a cheaper catalyst), which in turn drives a discovery (an unexpected reaction mechanism). Treat all three as independent search problems that frequently inform each other, not as sequential stages in a linear process.
Discovery searches a space of existing entities (natural laws, compounds, phenomena). Invention searches a space of possible artifacts (designs, algorithms, devices). Innovation searches a space of deployments (markets, contexts, workflows). The mathematical framework is the same; only the nature of the search space changes. This is why a single AI system can, in principle, assist with all three activities, and why the framework we build in this section applies throughout the entire book.
2. Search Spaces and Solution Spaces
A search space \(S\) is the set of all states that a discovery process might visit. Each state \(s \in S\) represents a complete description of what the discoverer knows or has built at a given moment. In drug discovery, a state might encode the molecular structure under consideration plus all assay results collected so far. In software engineering, a state might encode the current codebase, test results, and open requirements. The search space can be discrete (a finite set of candidate molecules), continuous (the parameters of a neural network), or a hybrid of both.
A search space matters because its size, dimensionality, and topology determine whether a discovery problem is tractable at all. A 10-dimensional continuous space can be sampled systematically (for example, by placing a grid over each dimension). A combinatorial space of \(10^{60}\) elements cannot be enumerated in the lifetime of the universe. Defining \(S\) explicitly partitions the universe of possibilities into a bounded set of states, each of which you can evaluate against the objective function \(f\). Use a search space formulation whenever the problem has enough structure that nearby states tend to have correlated objective values, enabling intelligent navigation rather than blind guessing.
From Search Space to Solution Space
A solution space \(S^* \subseteq S\) is the subset of states that satisfy the discovery objective. Not every state in \(S\) is interesting; we are looking for states that maximize (or minimize) some objective function \(f: S \to \mathbb{R}\). In many real problems, the solution space is vanishingly small relative to the search space. Drug discovery illustrates this starkly: the space of drug-like molecules is estimated at \(10^{60}\) (Bohacek et al., 1996), while the number of U.S. Food and Drug Administration (FDA)-approved drugs is roughly \(2{,}000\). The ratio of solutions to candidates is on the order of \(10^{-57}\), roughly the chance of picking one specific atom out of all the atoms in a human body.
The structure of the search space matters as much as its size. A space with smooth gradients (nearby states have similar objective values) is amenable to local search methods such as gradient descent. A space with many local optima (where the objective value is higher than all immediate neighbors but lower than the global best) separated by deep valleys requires global search strategies. A space with combinatorial structure (discrete choices that interact) may demand entirely different approaches. Understanding this structure is the central challenge of Chapter 45: Optimization for Discovery.
Mental Model
(We define the tuple formally in the next section; this preview shows how all five parts fit together.) Think of the discovery tuple \((S, A, T, f, C)\) like planning a cross-country road trip. The state space \(S\) is the map of every town you could visit. The action space \(A\) is the set of roads you can take from each town. The transition function \(T\) tells you where each road leads (sometimes with uncertainty: a detour or a road closure). The objective \(f\) is your rating of each town (scenery, food, attractions). The constraints \(C\) are your budget, your fuel range, and the requirement to avoid toll roads. Just as a road trip optimizer must know the road network, the fuel costs, and the destination rating to plan an efficient route, a discovery AI must know all five components to search intelligently rather than wandering at random.
3. The Discovery Tuple: \((S, A, T, f, C)\)
Without a shared formal language, teams routinely waste months optimizing the wrong objective or searching a space that omits the actual solution. Making every component of the search explicit turns that silent failure mode into a checklist you can audit before committing resources.
The five-tuple below captures every component of the search process. Figure 1.1 visualizes how the components connect: a state is transformed by an action through the transition function, evaluated by the objective, and filtered by constraints.
A discovery problem is defined by: Figure 1.1.1 illustrates the discovery five-tuple framework.
where:
- \(S\): State space. The set of all possible states the discovery process can occupy. Each state \(s \in S\) encodes everything relevant to the current stage of discovery.
- \(A\): Action space. The set of all actions (experiments, measurements, computations, design choices) available to the discoverer. Actions may depend on the current state: \(A(s) \subseteq A\) gives the actions available in state \(s\).
- \(T\): Transition function. \(T: S \times A \to \Delta(S)\) maps a state-action pair to a probability distribution over next states, where \(\Delta(S)\) denotes the set of all probability distributions over \(S\). The transition is typically stochastic: the same experiment run twice may yield different results due to noise, randomness, or incomplete knowledge.
- \(f\): Objective function. \(f: S \to \mathbb{R}\) (or \(f: S \to \mathbb{R}^k\) for multi-objective discovery) assigns a score to each state. The discoverer seeks states that maximize \(f\).
- \(C\): Constraints. A set of feasibility conditions \(C = \{c_1, c_2, \ldots, c_m\}\) where each \(c_i: S \to \{\text{true}, \text{false}\}\). A state is feasible only if all constraints are satisfied. Constraints encode budgets, safety requirements, physical laws, and ethical boundaries.
Let us ground this abstraction with a concrete example. Consider the problem of discovering a new antibiotic compound.
\(S\): Each state encodes a candidate molecule (as a Simplified Molecular-Input Line-Entry System (SMILES) string or molecular graph) plus all experimental data collected so far (binding assays, toxicity screens, pharmacokinetic measurements).
\(A\): Actions include: synthesize a new candidate, run a binding assay against a target protein, perform a toxicity screen, modify a functional group, request a computational docking simulation.
\(T\): Running a binding assay on molecule \(m\) against target \(t\) transitions the state by adding the measured binding affinity \(k_d\) to the data. The result is stochastic because assays have measurement noise.
\(f\): A composite objective that rewards high binding affinity, low toxicity, good oral bioavailability, and synthetic accessibility. This is inherently multi-objective: \(f(s) = (f_{\text{binding}}(s), f_{\text{tox}}(s), f_{\text{bioavail}}(s), f_{\text{synth}}(s))\).
\(C\): Budget constraint (at most \(N\) wet-lab experiments), safety constraint (toxicity below threshold), regulatory constraint (novel mechanism, not a known resistance target), and Lipinski's Rule of Five for drug-likeness.
The antibiotic example above is a discovery problem. The same five-tuple structure applies to the other two activities, with different contents. For invention (say, designing a new sorting algorithm): \(S\) is the space of possible program structures, \(A\) includes code mutations and recombinations, \(T\) maps a mutation to a new program variant, \(f\) measures runtime and correctness on benchmark inputs, and \(C\) enforces memory limits and required output format. For innovation (say, deploying a new drug to market): \(S\) encodes deployment configurations (pricing, distribution channels, target populations), \(A\) includes pilot launches and marketing campaigns, \(T\) captures market response (stochastic, since consumer adoption is uncertain), \(f\) measures patient outcomes and revenue, and \(C\) enforces regulatory approval and manufacturing capacity. The tuple is the same; the content of each component reflects whether you are finding, building, or deploying.
4. Encoding the Tuple in Python
Abstract mathematics becomes concrete when we write code. The following Python class encodes the discovery tuple as a reusable data structure. We will extend this class throughout the chapter, culminating in the full simulator in Section 1.4.
import numpy as np
from dataclasses import dataclass, field
from typing import Callable, List, Set, Tuple, Optional
@dataclass
class DiscoveryProblem:
"""The five-tuple (S, A, T, f, C) defining a discovery problem.
For tractability, we represent the state space implicitly:
states are generated on demand rather than enumerated.
"""
n_states: int # |S|: size of the discrete state space
n_actions: int # |A|: number of available actions
transition: Callable # T(s, a) -> s': state transition
objective: Callable # f(s) -> float: objective value
constraints: List[Callable] # [c_i(s) -> bool]: feasibility checks
rng: np.random.Generator = field(
default_factory=lambda: np.random.default_rng(42)
)
def is_feasible(self, state: int) -> bool:
"""Check whether a state satisfies all constraints."""
return all(c(state) for c in self.constraints)
def evaluate(self, state: int) -> Optional[float]:
"""Return objective value if feasible, None otherwise."""
if not self.is_feasible(state):
return None
return self.objective(state)
# --- Example: a simple synthetic discovery landscape ---
def make_synthetic_landscape(n: int = 1000, seed: int = 42):
"""Create a synthetic discovery problem with multi-modal objective.
The objective has several peaks (local optima) and one global
optimum, mimicking the rugged fitness landscapes found in
real discovery problems (e.g., protein fitness landscapes).
"""
rng = np.random.default_rng(seed)
# Generate a rugged objective landscape
x = np.linspace(0, 4 * np.pi, n)
landscape = (
np.sin(x) * np.cos(0.5 * x) # multi-modal base
+ 0.3 * np.sin(5 * x) # high-frequency ripple
+ 0.1 * rng.standard_normal(n) # observation noise
)
# Constraints: states in [200, 800] are "feasible"
# (mimicking a drug-likeness filter)
def feasibility(s):
return 200 <= s <= 800
problem = DiscoveryProblem(
n_states=n,
n_actions=n, # each action = "evaluate state i"
transition=lambda s, a: a, # deterministic: action = go to state
objective=lambda s: landscape[s],
constraints=[feasibility],
rng=rng,
)
return problem, landscape
problem, landscape = make_synthetic_landscape()
best_state = max(range(200, 801), key=lambda s: landscape[s])
print(f"Search space size: {problem.n_states}")
print(f"Feasible region: states 200..800 ({601} states)")
print(f"Best feasible state: {best_state}, value: {landscape[best_state]:.4f}")
DiscoveryProblem dataclass encodes the five-tuple \((S, A, T, f, C)\) with implicit state generation, and the make_synthetic_landscape factory produces a rugged multi-modal objective for experimentation.Search space size: 1000
Feasible region: states 200..800 (601 states)
Best feasible state: 585, value: 1.5732
Exercise 1.1.1
Consider a discovery problem where you want to find the best restaurant in a city of 50 restaurants. Define all five components of the discovery tuple \((S, A, T, f, C)\) for this problem. Then answer: if you can visit at most 5 restaurants (a constraint), what fraction of the search space can you explore? How does this compare to the drug discovery ratio of \(10^{-57}\) mentioned in the text?
Hint
The state space \(S\) has 50 elements (one per restaurant). Each action is "visit restaurant \(i\) and rate it." The transition function updates your knowledge by adding the rating. Your constraint limits you to 5 visits, so you explore \(5/50 = 10\%\) of the space. Compare that to \(2{,}000 / 10^{60}\). The key insight: small search spaces make brute-force feasible; astronomical ones demand intelligent search.
Step-Through: Evaluating the Discovery Tuple on a Tiny Landscape
Trace through the synthetic landscape code with a search space of just \(n = 8\) states and a feasible region of states 2 through 5. Suppose the landscape values are:
[0.12, -0.45, 0.88, 1.32, 0.67, -0.11, 0.93, 0.54]
Step 1: Enumerate feasible states: \(\{2, 3, 4, 5\}\). States 0, 1, 6, 7 fail
the constraint \(c(s) = (2 \leq s \leq 5)\).
Step 2: Evaluate \(f(s)\) for each feasible state: \(f(2) = 0.88\), \(f(3) = 1.32\),
\(f(4) = 0.67\), \(f(5) = -0.11\).
Step 3: Rank by objective: state 3 (1.32) > state 2 (0.88) > state 4 (0.67) >
state 5 (\(-0.11\)).
Step 4: Return the best feasible state: \(s^* = 3\) with \(f(s^*) = 1.32\).
Note that state 6 has a higher value (0.93) than states 4 and 5, but it is infeasible.
Constraints narrow the solution space and can exclude attractive candidates.
5. Multi-Objective Discovery
The synthetic landscape above optimizes a single number, but real discovery problems rarely offer that luxury; a drug candidate that binds perfectly to its target is worthless if it also destroys the patient's liver.
Real discovery problems rarely have a single scalar objective. The antibiotic example above involved four competing criteria. When \(f: S \to \mathbb{R}^k\) with \(k > 1\), we enter the domain of multi-objective optimization. The central concept here is Pareto dominance: state \(s_1\) dominates state \(s_2\) if \(s_1\) is at least as good as \(s_2\) on every objective and strictly better on at least one. The set of non-dominated states forms the Pareto front, where the Pareto front is the boundary of the best achievable trade-offs such that improving one objective necessarily worsens another.
$$ s_1 \succ s_2 \iff \forall i: f_i(s_1) \geq f_i(s_2) \;\wedge\; \exists j: f_j(s_1) > f_j(s_2) $$Checkpoint
So far: when the objective function returns multiple scores instead of one, no single "best" state may exist; instead, the Pareto front collects all states where improving one score necessarily worsens another, and the three strategies below determine how to choose among them.
In practice, multi-objective discovery typically proceeds in one of three ways: scalarization (combine objectives into a weighted sum \(f(s) = \sum_i w_i f_i(s)\)), Pareto optimization (find the entire Pareto front and let the human choose), or constraint conversion (optimize one objective while constraining the others to acceptable thresholds). Each approach has trade-offs that we revisit in Chapter 45.
def pareto_front(points: np.ndarray) -> np.ndarray:
"""Find the Pareto-optimal indices for a set of 2D objective vectors.
Parameters
----------
points : np.ndarray of shape (n, 2)
Each row is (f1, f2) for one candidate.
Returns
-------
np.ndarray of int
Indices of Pareto-optimal points.
"""
n = len(points)
is_dominated = np.zeros(n, dtype=bool)
for i in range(n):
if is_dominated[i]:
continue
for j in range(n):
if i == j or is_dominated[j]:
continue
# Check if j dominates i
if np.all(points[j] >= points[i]) and np.any(points[j] > points[i]):
is_dominated[i] = True
break
return np.where(~is_dominated)[0]
# Generate a two-objective landscape
rng = np.random.default_rng(42)
n_candidates = 200
efficacy = rng.standard_normal(n_candidates) # objective 1: efficacy
safety = -0.5 * efficacy + rng.standard_normal(n_candidates) # objective 2: safety (anti-correlated)
points = np.column_stack([efficacy, safety])
front_idx = pareto_front(points)
print(f"Candidates: {n_candidates}")
print(f"Pareto-optimal: {len(front_idx)} ({100*len(front_idx)/n_candidates:.1f}%)")
print(f"Example Pareto point: efficacy={points[front_idx[0], 0]:.2f}, "
f"safety={points[front_idx[0], 1]:.2f}")
Candidates: 200
Pareto-optimal: 14 (7.0%)
Example Pareto point: efficacy=2.44, safety=-0.14
The from-scratch Pareto front computation above runs in \(O(n^2)\) time. The pymoo library provides efficient multi-objective optimization with Non-dominated Sorting Genetic Algorithm II (NSGA-II), Multi-Objective Evolutionary Algorithm based on Decomposition (MOEA/D), and other algorithms that find Pareto fronts in large spaces with continuous or mixed variables. What took 20 lines above is a single call:
from pymoo.util.nds.non_dominated_sorting import NonDominatedSorting
# pymoo expects minimization, so negate for maximization
nds = NonDominatedSorting()
fronts = nds.do(-points) # returns list of arrays, fronts[0] is Pareto-optimal
print(f"Pareto-optimal (pymoo): {len(fronts[0])} points")
NonDominatedSorting, which uses the \(O(n \log n)\) fast non-dominated sort from Deb et al. (2002).pymoo handles the \(O(n \log n)\) non-dominated sorting internally using the fast algorithm from Deb et al. (2002) and, according to its published benchmarks, scales to thousands of objectives and millions of candidates.
6. Why Formalization Matters for AI
With the discovery tuple, search spaces, and multi-objective trade-offs in hand, we have a complete mathematical vocabulary for describing what a discovery problem is; the remaining question is why encoding that vocabulary explicitly is essential for building AI systems that solve these problems.
Why bother with formal tuples when scientists have made discoveries for centuries without them? Because automation requires precision. A human scientist can operate on vague intuitions about what to try next. In practice, an AI system needs an explicit state representation, action space, transition model, objective function, and constraint set; without any one of these, it typically cannot decide what to do or why.
This is not merely an academic exercise. Every real AI discovery system encodes these five components, whether or not its designers use this vocabulary. DeepMind's Graph Networks for Materials Exploration (GNoME) system (Merchant et al., 2023) for crystal structure discovery defines states as crystal structures and actions as structural modifications. A graph neural network serves as the transition function, predicting stability. The objective measures thermodynamic stability via energy above hull (the energy difference between a candidate structure and the most stable known combination of its elements, where lower values indicate greater stability). Constraints enforce charge neutrality and physical realizability. The self-driving laboratories of Chapter 55 make all five components explicit in their control software.
The framework in this section is recursive. You can frame the problem of choosing which search algorithm to use as itself a search problem: the states are algorithm configurations, the actions are hyperparameter tweaks, the objective is cumulative regret (the total cost of suboptimal choices over time; see Section 1.2), and the constraint is your compute budget. This "meta-search" perspective leads to automated algorithm configuration and the field of Automated Machine Learning (AutoML), which we explore in Chapter 25.
Real-World Application: Materials Science
DeepMind's GNoME system (Merchant et al., 2023) used the discovery-as-search framework to explore a space of 2.2 million candidate crystal structures. The state space encoded atomic arrangements as graphs, the objective function was predicted thermodynamic stability (energy above the convex hull), and constraints enforced charge neutrality and physical realizability. By making all five tuple components explicit, GNoME discovered 381,000 novel stable materials, increasing the number of known stable crystals by an order of magnitude.
A rapidly growing line of research (2024-2026) uses large language models and other foundation models as search operators within the \((S, A, T, f, C)\) framework. FunSearch (Romera-Paredes et al., 2024) uses an LLM to propose candidate programs, evaluated by an automated scoring function, with an evolutionary algorithm selecting which candidates to build upon. The LLM serves as both the action generator (proposing new states) and an implicit transition model (its internal representations encode which modifications are likely to be productive). This approach discovered new mathematical results in extremal combinatorics. More recently, SciAgents (Ghafarollahi and Buehler, 2025) demonstrated a multi-agent LLM system that autonomously generates research hypotheses, designs experiments, and reasons over scientific knowledge graphs, effectively distributing the five-tuple components across specialized agents that collaborate within a shared search framework. We return to this theme in Chapter 27: Scientific Foundation Models.
Try It: Map a Real Dataset as a Discovery Tuple
Pick a small public dataset and formalize its exploration as a \((S, A, T, f, C)\) problem, then run a brute-force search in Python.
- Install scikit-learn (
pip install scikit-learn) and load the Iris dataset:from sklearn.datasets import load_iris; data = load_iris(). - Define your search space \(S\) as all possible feature-threshold pairs: for each of the 4 features, consider 20 evenly spaced thresholds. This gives \(|S| = 80\) candidate "rules" of the form "if feature \(i > t\), predict class 1 vs. class 0."
- Define the objective \(f(s)\) as the classification accuracy of each single-threshold rule on the full dataset (use only two classes, e.g., setosa vs. non-setosa).
- Add a constraint \(C\): require that each class has at least 10 samples on each side of the threshold (to avoid trivial splits).
- Enumerate all 80 states, evaluate \(f\) and check \(C\) for each, and print the top 5 feasible rules. Verify that the best rule uses petal length or petal width, the two features known to separate Iris species most cleanly.
Lab: Landscape Topology and Search Difficulty
Goal: Explore how the shape of an objective landscape affects how quickly
random search and greedy hill-climbing find the global optimum.
Tools: Python, NumPy, Matplotlib (all included in a standard scientific Python
install).
Setup (15 min): Use the make_synthetic_landscape function from
Listing 1.1. Generate three landscapes by varying the high-frequency ripple coefficient
(try 0.0, 0.3, and 1.0 in place of the 0.3 * np.sin(5 * x) term). Plot all
three with Matplotlib to see how ruggedness changes.
Experiment (15 min): For each landscape, run two search strategies 100 times
each: (1) random search, drawing 50 feasible states uniformly at random and
returning the best; (2) greedy hill-climbing, starting from a random feasible
state and repeatedly moving to the best neighbor within a window of \(\pm 10\) states,
for 50 steps. Record the best objective value found in each run.
What to observe: Plot histograms of the best values found by each strategy
on each landscape. On smooth landscapes (ripple = 0.0), hill-climbing should dominate.
On rugged landscapes (ripple = 1.0), the gap should shrink because hill-climbing gets
trapped in local optima. This is the core tension between local and global search that
Section 1.2 formalizes.
Exercises
- (Conceptual) Choose a discovery problem from your own field (or one that interests you). Identify all five components of the tuple \((S, A, T, f, C)\). Is the state space discrete, continuous, or mixed? Is the transition function deterministic or stochastic? Write a one-paragraph description in the format of the antibiotic example above.
-
(Coding) Modify the
make_synthetic_landscapefunction to create a landscape with exactly three local optima in the feasible region. Verify your landscape by plotting it with Matplotlib and confirming the number of peaks visually. -
(Analysis) The Pareto front computation in Listing 1.2 is \(O(n^2)\). Derive the time complexity, then run the code with \(n = 10{,}000\) candidates and measure the wall-clock time. Compare it to the pymoo
NonDominatedSortingimplementation. What speedup do you observe?
What's Next
We have defined what discovery searches for. In Section 1.2: Exploration Versus Exploitation, we tackle how to search efficiently: the fundamental tension between exploring unknown regions of the search space and exploiting regions that look promising. We will quantify this trade-off through regret bounds and information gain, deriving the Upper Confidence Bound (UCB) algorithm from first principles.
Bibliography
The source of the widely cited \(10^{60}\) estimate for the size of drug-like chemical space.
GNoME: AI-driven search through crystal structure space, discovering 2.2 million stable materials.
FunSearch: using LLMs as search operators to discover new mathematical constructions.
The NSGA-II algorithm for efficient multi-objective optimization, foundational for Pareto-based discovery.
The foundational work framing scientific discovery as heuristic search in a problem space.
Chapters 3-5 cover search algorithms systematically; Chapter 17 covers multi-criteria decision making.
The pymoo library for multi-objective optimization, used in the library-shortcut example above.
A comprehensive survey of AI for scientific discovery, providing the broader context for the search framework.