Drug DiscoveryDrug Discovery Tools

Multi-Parameter Optimization in Drug Design: Balancing Potency, Safety, and Synthesizability

Balance potency, ADMET, and synthesizability with multi-parameter optimization. Weighted scoring, Pareto fronts, and automated compound prioritization.

Ryan Bethencourt
April 8, 2026
9 min read

The Multi-Parameter Optimization Challenge

A drug is not a molecule that binds a target. A drug is a molecule that binds a target at the right concentration, reaches the target tissue after oral administration, avoids critical off-targets, is not destroyed by liver enzymes before it can act, does not accumulate in the brain or heart, can be manufactured at scale, and is stable on a pharmacy shelf for two years. Optimizing any one of these properties is straightforward. Optimizing all of them simultaneously – in a single molecule – is the central unsolved problem of drug design.

This is multi-parameter optimization (MPO), and it is where most drug discovery programs fail. Estimates from large pharmaceutical companies indicate that 90% of drug candidates that enter clinical trials fail, and the majority of those failures are due to poor pharmacokinetics (40%) or safety concerns (30%) rather than lack of efficacy. The molecules were potent enough; they failed because the optimization campaign did not adequately address the other dimensions.

The fundamental difficulty is that molecular properties are correlated. Adding a hydrophobic group to fill a binding pocket improves potency but increases LogP, which worsens solubility, increases hERG risk, and accelerates CYP450 metabolism. Adding a polar group to improve solubility reduces membrane permeability, cutting oral bioavailability. Reducing molecular weight to improve ADMET properties removes interaction points needed for potency and selectivity. Every change moves you forward on one axis and backward on another.

The medicinal chemist's task is to find the narrow region of chemical space where all properties are simultaneously "good enough." Not optimal on any single dimension, but acceptable on all of them. This requires a systematic framework for quantifying trade-offs and making rational decisions about which properties to sacrifice and which to prioritize.

Weighted Scoring Functions

The most practical approach to MPO is the weighted scoring function: reduce the multi-dimensional property space to a single composite score that can be used to rank and compare molecules. The scoring function encodes the project team's priorities as numerical weights, making the decision process transparent and reproducible.

Step 1: Define Desirability Functions

Each property needs a desirability function that maps raw values to a 0 to 1 scale. A desirability of 1.0 means the value is in the ideal range; 0.0 means it is unacceptable. The shape of the desirability function encodes domain knowledge about acceptable ranges.

Define desirability functions for key drug properties
def desirability_range(value, low_bad, low_good, high_good, high_bad):
    """Trapezoidal desirability: 0 outside bad limits, 1 inside good limits."""
    if value <= low_bad or value >= high_bad:
        return 0.0
    if low_good <= value <= high_good:
        return 1.0
    if value < low_good:
        return (value - low_bad) / (low_good - low_bad)
    return (high_bad - value) / (high_bad - high_good)

def desirability_max(value, bad, good):
    """Higher is better, capped at good."""
    if value >= good:
        return 1.0
    if value <= bad:
        return 0.0
    return (value - bad) / (good - bad)

def desirability_min(value, good, bad):
    """Lower is better, capped at good."""
    if value <= good:
        return 1.0
    if value >= bad:
        return 0.0
    return (bad - value) / (bad - good)

# Property desirability functions for an oral drug
desirability_fns = {
    "mw":       lambda v: desirability_range(v, 150, 200, 500, 600),
    "logp":     lambda v: desirability_range(v, -0.5, 1.0, 3.5, 5.5),
    "tpsa":     lambda v: desirability_range(v, 20, 40, 120, 150),
    "hbd":      lambda v: desirability_min(v, 2, 5),
    "hba":      lambda v: desirability_min(v, 7, 10),
    "sa_score":  lambda v: desirability_min(v, 3.0, 6.0),
}

# Test with example values
test_mw = 420
test_logp = 3.2
print(f"MW {test_mw}: desirability = {desirability_fns['mw'](test_mw):.2f}")
print(f"LogP {test_logp}: desirability = {desirability_fns['logp'](test_logp):.2f}")

Step 2: Assign Weights

Weights reflect the relative importance of each property to the specific program. A safety-critical oncology program might weight hERG and hepatotoxicity heavily; a CNS program might weight blood-brain barrier penetration and P-gp efflux most heavily. The weights should be set by the project team based on the target product profile and the known liabilities of the chemical series.

Define MPO weights for an oral oncology program
# Weights for an oral oncology kinase inhibitor program
weights = {
    "potency":        1.0,   # Non-negotiable: must bind the target
    "selectivity":    0.8,   # Important: kinase selectivity panel
    "herg_safety":    0.9,   # Critical: cardiac safety
    "hepatotox":      0.7,   # Important: liver safety
    "oral_f":         0.6,   # Desired: oral dosing preferred
    "solubility":     0.5,   # Moderate: can use formulation tricks
    "metabolic_stab": 0.6,   # Desired: once-daily dosing
    "sa_score":       0.4,   # Nice to have: affects timeline, not efficacy
}

# Normalize weights to sum to 1.0
total = sum(weights.values())
norm_weights = {k: v / total for k, v in weights.items()}
print("Normalized weights:")
for k, v in norm_weights.items():
    print(f"  {k:<16} {v:.3f}")

Step 3: Compute Composite Scores

For each candidate molecule, compute the desirability of each property, multiply by the corresponding weight, and sum to get the composite MPO score. Molecules with higher composite scores are better overall drug candidates according to the team's stated priorities.

Score molecules with the weighted MPO function
import os, requests

API_KEY = os.environ["SCIROUTER_API_KEY"]
BASE = "https://api.scirouter.ai/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

def compute_mpo_score(smiles, weights, desirability_fns):
    """Compute composite MPO score for a molecule."""
    props = requests.post(f"{BASE}/chemistry/properties",
        headers=HEADERS, json={"smiles": smiles}).json()
    admet = requests.post(f"{BASE}/chemistry/admet",
        headers=HEADERS, json={"smiles": smiles}).json()
    synth = requests.post(f"{BASE}/chemistry/synthesis-check",
        headers=HEADERS, json={"smiles": smiles}).json()

    # Compute desirabilities
    d = {}
    d["mw"] = desirability_fns["mw"](props["molecular_weight"])
    d["logp"] = desirability_fns["logp"](props["logp"])
    d["tpsa"] = desirability_fns["tpsa"](props["tpsa"])
    d["hbd"] = desirability_fns["hbd"](props["h_bond_donors"])
    d["hba"] = desirability_fns["hba"](props["h_bond_acceptors"])
    d["sa"] = desirability_fns["sa_score"](synth["sa_score"])

    # Binary ADMET desirabilities
    d["herg"] = 1.0 if admet["herg_inhibition"] == "low" else 0.3
    d["hepatotox"] = 1.0 if admet["hepatotoxicity"] == "low" else 0.2
    d["oral_f"] = 1.0 if admet["oral_bioavailability"] == "high" else 0.4
    d["solubility"] = 1.0 if admet["solubility_class"] in (
        "soluble", "moderately_soluble") else 0.3

    # Weighted composite score
    score_components = {
        "potency":        d.get("mw", 0.5),      # Proxy: drug-like MW range
        "selectivity":    d.get("tpsa", 0.5),     # Proxy: balanced polarity
        "herg_safety":    d["herg"],
        "hepatotox":      d["hepatotox"],
        "oral_f":         d["oral_f"],
        "solubility":     d["solubility"],
        "metabolic_stab": d["logp"],               # Proxy: moderate LogP
        "sa_score":       d["sa"],
    }

    composite = sum(
        score_components.get(k, 0.5) * w
        for k, w in weights.items()
    )

    return {
        "smiles": smiles,
        "mpo_score": round(composite, 3),
        "desirabilities": d,
        "properties": props,
        "admet": admet,
        "sa_score": synth["sa_score"],
    }

# Score a set of candidate molecules
candidates = [
    "CC1CCN(C(=O)c2cnc3ccccc3n2)CC1",
    "CC1CCN(C(=O)c2cnc3cc(F)ccc3n2)CC1",
    "CC1CCN(C(=O)c2cnc3ccncc3n2)CC1",
    "CC1CCN(C(=O)c2cnc3cc(O)ccc3n2)CC1",
    "Oc1ccc2nc(C(=O)N3CCC(C)CC3)cnc2c1",
]

results = [compute_mpo_score(s, norm_weights, desirability_fns)
           for s in candidates]
results.sort(key=lambda x: x["mpo_score"], reverse=True)

print("=== MPO Rankings ===")
for i, r in enumerate(results):
    print(f"{i+1}. Score: {r['mpo_score']:.3f} | "
          f"hERG: {r['admet']['herg_inhibition']} | "
          f"OralF: {r['admet']['oral_bioavailability']} | "
          f"SA: {r['sa_score']:.1f}")
    print(f"   {r['smiles']}")

Pareto Fronts: Visualizing Trade-Offs

Weighted scoring functions are practical but they hide information. A molecule with an MPO score of 0.72 might achieve that score through excellent potency and poor safety, or through moderate potency and excellent safety. Both get the same number, but they represent very different risk profiles. Pareto front analysis preserves the multi-dimensional structure of the trade-offs.

A Pareto front (also called a Pareto frontier or non-dominated set) is the set of molecules where no property can be improved without degrading at least one other property. In two dimensions (say, potency versus solubility), the Pareto front is the boundary curve: every molecule on the front is either more potent or more soluble than every other molecule on the front, but none is better in both dimensions simultaneously.

To compute a Pareto front from your candidate set, test each molecule against every other molecule. A molecule is Pareto-dominated if another molecule exists that is at least as good on every property and strictly better on at least one. The non-dominated molecules form the Pareto front.

Extract the Pareto front from scored candidates
def is_dominated(mol_a, mol_b, objectives):
    """Return True if mol_b dominates mol_a on all objectives."""
    at_least_as_good = all(
        mol_b[obj] >= mol_a[obj] for obj in objectives
    )
    strictly_better = any(
        mol_b[obj] > mol_a[obj] for obj in objectives
    )
    return at_least_as_good and strictly_better

def pareto_front(molecules, objectives):
    """Extract non-dominated set from a list of scored molecules."""
    front = []
    for i, mol_a in enumerate(molecules):
        dominated = False
        for j, mol_b in enumerate(molecules):
            if i != j and is_dominated(mol_a, mol_b, objectives):
                dominated = True
                break
        if not dominated:
            front.append(mol_a)
    return front

# Prepare data: extract the two objectives we care about
for r in results:
    # Invert SA so higher = better (more synthesizable)
    r["synth_score"] = 1.0 - (r["sa_score"] / 10.0)
    r["safety_score"] = r["desirabilities"]["herg"]

# Find Pareto front on safety vs synthesizability
front = pareto_front(results, ["safety_score", "synth_score"])

print(f"Pareto front: {len(front)} of {len(results)} molecules")
for mol in front:
    print(f"  Safety: {mol['safety_score']:.2f} | "
          f"Synth: {mol['synth_score']:.2f} | "
          f"MPO: {mol['mpo_score']:.3f}")
    print(f"  {mol['smiles']}")

In practice, you compute Pareto fronts across the two or three most contentious trade-off dimensions in your program. If the team is debating potency versus safety, visualize that Pareto front and pick the molecule that represents the acceptable compromise. If the debate is between ADMET profile and synthetic feasibility, compute that front. The Pareto front makes the trade-off explicit and removes ambiguity from the decision process.

Note
For programs with more than three competing objectives, computing the full Pareto front becomes less informative because most molecules end up on the front (in high-dimensional space, it is easy to be non-dominated on at least one dimension). In practice, reduce to 2 to 3 key trade-off dimensions or use the weighted scoring function approach for programs with many objectives.

Real-World Trade-Off Decisions

Theory aside, the value of MPO emerges in specific, concrete decisions that occur in every lead optimization campaign. Here are four trade-off scenarios drawn from real drug discovery programs, with guidance on how to navigate each one.

Potency vs. hERG Safety

You have a lead with 30 nM potency against your kinase target but medium hERG risk (IC50 around 3 micromolar, where the safety threshold is typically 10 micromolar). The most effective way to reduce hERG risk is to lower overall lipophilicity, but this reduces hydrophobic contacts in the binding site and weakens potency. Your MPO scoring function reveals that reducing LogP from 4.2 to 3.0 drops potency to 100 nM (still acceptable for the program) while pushing hERG IC50 above 30 micromolar (well within the safety margin). The 3-fold potency loss buys a 10-fold safety margin – a trade-off that every regulatory reviewer would endorse.

Oral Bioavailability vs. Molecular Complexity

Your lead has excellent target engagement but 8% oral bioavailability in rat PK studies (the program needs at least 20% for oral dosing). Adding a polar group to improve solubility and reduce CYP metabolism increases oral F to 35% but also increases molecular weight from 420 to 480 and adds a chiral center. The synthesis goes from 6 steps to 11 steps. The MPO scoring function, with appropriate weights on synthesizability, might indicate that a different modification – replacing a metabolically labile methyl with a cyclopropyl – improves oral F to 22% with only one additional synthetic step. The second option scores lower on bioavailability but higher overall because it preserves the synthetic tractability needed for rapid analog progression.

Selectivity vs. Efficacy

For a CDK4/6 inhibitor program, you need selectivity over CDK2 (which causes bone marrow suppression). Your lead has 5-fold selectivity (CDK4 IC50 = 10 nM, CDK2 IC50 = 50 nM). The program needs 100-fold selectivity. Achieving this requires exploiting a structural difference between CDK4 and CDK2 binding sites – a slightly larger gatekeeper residue in CDK4 that accommodates a bulkier substituent. Adding a neopentyl group at the gatekeeper-facing position achieves 200-fold selectivity but drops CDK4 potency to 40 nM. An isopropyl group gives 80-fold selectivity with 15 nM CDK4 potency. The team must decide whether the 2-fold potency loss is worth the 2.5-fold selectivity gain.

Solubility vs. Permeability

Adding polar functional groups to improve aqueous solubility inevitably reduces membrane permeability. Your lead has solubility of 2 micrograms per milliliter (too low for oral dosing) and Caco-2 permeability of 25 (good). Adding a pyridine nitrogen to the core ring improves solubility to 45 micrograms per milliliter but drops permeability to 8 (borderline). The MPO function must balance these: the desirability of solubility improvement (from 0.1 to 0.9) versus the desirability loss in permeability (from 0.95 to 0.4). With appropriate weights, the solver may suggest a hydroxyl group at a different position that achieves 20 micrograms per milliliter solubility (desirability 0.6) while maintaining permeability at 18 (desirability 0.8) – a better overall compromise.

Automating MPO with SciRouter

The real power of computational MPO is automation. Instead of manually evaluating a handful of analogs, generate hundreds of candidates and let the scoring function identify the best ones. SciRouter's Lead Optimization Lab provides the full pipeline: generation, profiling, scoring, and ranking.

End-to-end automated MPO pipeline
import os, requests, time

API_KEY = os.environ["SCIROUTER_API_KEY"]
BASE = "https://api.scirouter.ai/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

LEAD = "CCCS(=O)(=O)Nc1ccc(F)c(C(=O)c2c[nH]c3ncc(-c4ccc(Cl)cc4)cc23)c1"

# Step 1: Generate 300 diverse analogs
job = requests.post(f"{BASE}/chemistry/generate", headers=HEADERS, json={
    "model": "reinvent4",
    "num_molecules": 300,
    "objectives": {
        "similarity": {
            "weight": 0.6,
            "reference_smiles": LEAD,
            "min_similarity": 0.3,
            "max_similarity": 0.8,
        },
        "drug_likeness": {"weight": 1.0, "method": "qed"},
        "synthetic_accessibility": {"weight": 0.7, "max_sa_score": 5.0},
    },
}).json()

while True:
    result = requests.get(
        f"{BASE}/chemistry/generate/{job['job_id']}", headers=HEADERS
    ).json()
    if result["status"] in ("completed", "failed"):
        break
    time.sleep(10)

analogs = result["molecules"]
print(f"Generated {len(analogs)} analogs")

# Step 2: Profile and score every analog
scored = []
for mol in analogs:
    try:
        props = requests.post(f"{BASE}/chemistry/properties",
            headers=HEADERS, json={"smiles": mol["smiles"]}).json()
        admet = requests.post(f"{BASE}/chemistry/admet",
            headers=HEADERS, json={"smiles": mol["smiles"]}).json()
        synth = requests.post(f"{BASE}/chemistry/synthesis-check",
            headers=HEADERS, json={"smiles": mol["smiles"]}).json()

        # Compute desirabilities
        d_mw = desirability_range(props["molecular_weight"], 150, 200, 500, 600)
        d_logp = desirability_range(props["logp"], -0.5, 1.0, 3.5, 5.5)
        d_sa = desirability_min(synth["sa_score"], 3.0, 6.0)
        d_herg = 1.0 if admet["herg_inhibition"] == "low" else 0.3
        d_oral = 1.0 if admet["oral_bioavailability"] == "high" else 0.4
        d_hepat = 1.0 if admet["hepatotoxicity"] == "low" else 0.2

        # Weighted MPO score (using normalized weights from above)
        mpo = (d_mw * 0.18 + d_logp * 0.11 + d_herg * 0.16 +
               d_hepat * 0.13 + d_oral * 0.11 + d_sa * 0.07 +
               desirability_range(props["tpsa"], 20, 40, 120, 150) * 0.15 +
               (1.0 if admet["solubility_class"] in
                ("soluble", "moderately_soluble") else 0.3) * 0.09)

        scored.append({
            "smiles": mol["smiles"],
            "mpo": round(mpo, 3),
            "mw": props["molecular_weight"],
            "logp": props["logp"],
            "herg": admet["herg_inhibition"],
            "oral_f": admet["oral_bioavailability"],
            "sa": synth["sa_score"],
        })
    except Exception:
        continue

scored.sort(key=lambda x: x["mpo"], reverse=True)

# Step 3: Report top 15
print(f"\n=== Top 15 of {len(scored)} scored analogs ===")
for i, s in enumerate(scored[:15]):
    print(f"{i+1:>2}. MPO={s['mpo']:.3f} | MW={s['mw']:.0f} "
          f"LogP={s['logp']:.1f} hERG={s['herg']} "
          f"OralF={s['oral_f']} SA={s['sa']:.1f}")
    print(f"    {s['smiles']}")

Tuning Weights as Programs Evolve

MPO weights are not static. As a program progresses through lead optimization, the relative importance of different properties shifts. Early in optimization, potency and selectivity dominate because you need to validate the mechanism. Mid-campaign, ADMET properties become critical as you prepare for in vivo studies. Late-stage, synthesizability and formulation compatibility matter most as you approach candidate nomination and process chemistry scale-up.

A practical approach is to define three weight profiles – early, mid, and late – and switch between them as the program progresses. The early profile weights potency at 1.0 and synthesizability at 0.2. The mid profile weights potency at 0.7, hERG safety at 1.0, and oral bioavailability at 0.8. The late profile weights synthesizability at 1.0, stability at 0.9, and all safety parameters at 0.8 or higher.

Tip
Document your MPO weights and the rationale for each one at every program milestone. This creates an auditable record of the design decisions that led to candidate selection – valuable for regulatory filings, patent applications, and internal knowledge transfer. SciRouter's API logs every scoring run, making it straightforward to reconstruct the decision history.

Common MPO Pitfalls

MPO is a powerful framework, but it can be misapplied. Here are the most common mistakes and how to avoid them.

  • Averaging over cliffs: A molecule with 0.9 desirability on five properties and 0.0 on one property scores 0.75 on a weighted average but is actually a dead compound (the zero-desirability property is a program-killer). Use multiplicative aggregation or hard cutoffs for critical properties. If hERG risk is "high," the molecule is eliminated regardless of its score on other dimensions.
  • Overweighting potency: In most programs, the initial hit already has acceptable potency that can be improved with straightforward SAR. Overweighting potency leads to hydrophobic, high-LogP molecules with poor ADMET profiles. Unless potency is genuinely the bottleneck, weight it at 0.5 to 0.7 rather than 1.0.
  • Ignoring property correlations: LogP, solubility, metabolic stability, and hERG risk are all correlated through lipophilicity. A scoring function that weights all four separately effectively quadruple-counts lipophilicity. Use principal component analysis or careful weight normalization to avoid this bias.
  • Static weights throughout the program: The optimal balance shifts as you learn more about the series. A weight set optimized for the first 50 analogs may be suboptimal by compound 200 when the SAR is better understood. Revisit weights at every major decision point.
  • Optimizing the score instead of the molecule: If the scoring function has a flaw, optimizing it ruthlessly can exploit that flaw. Always inspect the top-scoring molecules visually and apply medicinal chemistry judgment. The score is a tool for ranking, not a replacement for expertise.

Putting It All Together

Multi-parameter optimization is the most intellectually demanding phase of drug design. It requires balancing competing objectives, making explicit trade-offs, and navigating a high-dimensional property landscape where every direction has both benefits and costs. The weighted scoring function provides a practical framework for converting expert judgment into quantitative rankings. Pareto front analysis preserves the full trade-off structure for decisions where a single score is insufficient.

SciRouter's computational pipeline – molecular properties for physicochemical descriptors, ADMET-AI for safety predictions, synthesis check for manufacturability, and REINVENT4 for analog generation – provides all the inputs needed for automated MPO. Generate hundreds of candidates, score them against your custom desirability functions, extract the Pareto front, and select the top candidates for synthesis. What traditionally required months of iterative design-make-test cycles becomes a computational exercise that produces a prioritized synthesis queue in minutes.

The molecules that emerge from this process are not just computationally optimal – they are rationally designed with explicit, documented justification for every trade-off. That transparency accelerates regulatory interactions, strengthens patent filings, and builds institutional knowledge that compounds across every future program.

Frequently Asked Questions

What is multi-parameter optimization (MPO) in drug design?

Multi-parameter optimization is the process of simultaneously optimizing a drug candidate across multiple competing objectives: potency against the target, selectivity over off-targets, ADMET properties (absorption, distribution, metabolism, excretion, toxicity), and synthesizability. Unlike single-objective optimization, MPO acknowledges that improving one property often degrades another, and the goal is to find molecules in the narrow region of chemical space where all properties are simultaneously acceptable. MPO is the central challenge of lead optimization in medicinal chemistry.

What is a Pareto front in drug design?

A Pareto front is the set of molecules where no single property can be improved without degrading at least one other property. These molecules represent the best possible trade-offs between competing objectives. For example, on a Pareto front of potency versus solubility, every molecule is either more potent or more soluble than every other molecule on the front, but none is better in both dimensions simultaneously. The medicinal chemist selects from the Pareto front based on which trade-offs are most acceptable for the specific program and therapeutic indication.

How do you build a weighted scoring function for drug candidates?

A weighted scoring function combines multiple molecular properties into a single composite score. Each property is first normalized to a 0 to 1 scale (using desirability functions that define what values are acceptable, ideal, and unacceptable), then multiplied by a weight reflecting its importance to the program. The weighted scores are summed or multiplied to produce a final score. For example, potency might receive weight 1.0, hERG safety weight 0.8, oral bioavailability weight 0.6, and synthetic accessibility weight 0.4. The weights encode the project team&apos;s priorities and can be adjusted as the program evolves.

What are common trade-offs in drug design?

The most common trade-offs are potency versus selectivity (optimizing binding often increases off-target activity), potency versus ADMET (adding hydrophobic groups improves binding but worsens solubility, metabolic stability, and hERG risk), solubility versus permeability (adding polar groups improves aqueous solubility but reduces membrane permeability and oral absorption), and potency versus synthesizability (the most potent analogs often require complex synthesis routes with many steps and low yields). Understanding and quantifying these trade-offs is essential for making rational design decisions.

What is a desirability function in MPO?

A desirability function maps a raw property value to a 0 to 1 score representing how desirable that value is for the drug design program. A desirability of 1.0 means the value is ideal; 0.0 means it is unacceptable. For example, a LogP desirability function might assign 1.0 to LogP values between 1.0 and 3.0, linearly decrease to 0.0 at LogP 5.0, and assign 0.0 to all values above 5.0. Desirability functions encode domain knowledge about acceptable property ranges and allow properties measured in different units (nanomolar, Daltons, unitless scores) to be combined on a common scale.

Can I use SciRouter for multi-parameter optimization?

Yes. SciRouter provides the three essential components for computational MPO: property prediction (molecular properties endpoint for physicochemical descriptors), safety prediction (ADMET-AI endpoint for hERG, hepatotoxicity, oral bioavailability, and other safety parameters), and compound generation (molecule generator with REINVENT4 for creating novel analogs). By combining these endpoints with a custom scoring function, you can computationally evaluate thousands of candidates against your MPO criteria and identify the top candidates for synthesis. The Lead Optimization Lab provides a visual interface for this workflow.

Try this yourself

500 free credits. No credit card required.