Metadata-Version: 2.4
Name: synfit
Version: 0.9.0
Summary: Dose-response curve fitting and drug-combination synergy
Author-email: Srdjan Sarikas <develop@synfit.app>
License: MIT License
        
        Copyright (c) 2026 Srdjan Sarikas
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://synfit.app
Project-URL: Documentation, https://github.com/szarma/synfit/blob/main/docs/api.md
Project-URL: Source, https://github.com/szarma/synfit
Project-URL: Issues, https://github.com/szarma/synfit/issues
Keywords: pharmacology,dose-response,hill-equation,ic50,ec50,synergy,bliss-independence,loewe-additivity,zip-synergy,curve-fitting
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: MIT License
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=2.0
Requires-Dist: scipy>=1.13
Requires-Dist: pandas>=2.2
Requires-Dist: matplotlib>=3.9
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Dynamic: license-file

# synfit

Dose–response curve fitting and drug-combination synergy scoring for
pharmacology: Hill models (4- and 5-parameter), explicit noise models, and
Bliss, HSA, Loewe and ZIP references for inhibition and activation assays.

## Install

```bash
pip install synfit
```

Python 3.11 or newer. Pure Python — nothing to compile — on top of NumPy,
SciPy, pandas and Matplotlib.

## Quick start

A single-drug inhibition fit. Data goes in as a tidy DataFrame of
`concentration`, `y` and `replicate`:

```python
import pandas as pd
from synfit import SingleDrugFit

data = pd.DataFrame({
    "concentration": [0, 0.1, 1, 10, 100, 100],
    "y": [1.0, 0.95, 0.75, 0.35, 0.08, 0.05],
    "replicate": [0, 0, 0, 0, 0, 0],
})
result = SingleDrugFit(data).fit()
print(f"c50 (κ): {result.c50:.3g}")
print(f"IC50:    {result.half_max:.3g}")
print(f"Hill:    {result.hill:.3g}")
cis = result.param_ci()
if cis and "c50" in cis:
    lo, hi = cis["c50"]
    print(f"c50 95% CI: [{lo:.3g}, {hi:.3g}]")
```

The half-max concentration is `result.half_max`, reported as IC₅₀ for an
inhibition fit and EC₅₀ for an activation one. `result.to_dict()` gives the
full parameter set, confidence intervals and goodness-of-fit statistics in a
JSON-serialisable form.

## What it does

- **Hill fitting** for single-drug dose–response data, 4-parameter or
  5-parameter (asymmetric), for inhibition and activation assays
- **Drug-drug interaction matrices** — `JointMarginalFit` is the recommended
  marginal path: it fits the two single-agent curves together with shared
  plate-level asymptotes. `MatrixFit` is a diagnostic Bliss-null surface model.
- **Synergy references**: Bliss, HSA, Loewe and ZIP — the last implementing the
  published Zero Interaction Potency model, unit- and direction-neutral
- **Explicit noise models**: constant and heteroscedastic Gaussian, and
  Lognormal, fitted by maximum likelihood rather than assumed
- **Confidence intervals** for each fitted parameter, from the covariance
  estimate at the optimum, where the optimiser produced one
- **Ready-made datasets** with known ground truth, shipped with the package
- **Plots**: dose–response curves and synergy heatmaps via Matplotlib

## Modules

Everything in the table is importable from an installed package. The names in
the first row are also re-exported from the top-level `synfit` namespace.

| Module | Holds |
|---|---|
| `synfit` | `SingleDrugFit`, `MatrixFit`, `FitConfig`, `FitResult`, `hill_curve`, `loewe_ci`, `zip_delta`, … |
| `synfit.hill` | the Hill equation, concentration series, the `log_wall` prior |
| `synfit.single` | single-drug fitters and their default bounds and config |
| `synfit.matrix` | drug-drug matrix fitting |
| `synfit.joint_marginal` | two drugs' curves fitted together, sharing plate-level asymptotes |
| `synfit.noise` | the `NoiseSpec` union, its serialisation and likelihoods |
| `synfit.bliss` | Bliss independence and HSA references |
| `synfit.loewe` | Loewe additivity reference and combination index |
| `synfit.zip` | ZIP reference surface and δ-scores |
| `synfit.synthetic` | data generation, and the scenarios shipped with the package |
| `synfit.plotting` | Matplotlib dose–response curves and heatmaps |

For API details and worked examples, see the
[API reference](https://github.com/szarma/synfit/blob/main/docs/api.md) and
[tutorials](https://github.com/szarma/synfit/blob/main/docs/tutorials.md).

## Datasets included

Twenty scenarios install with the package, so there is data to fit
immediately: clean and noisy single-drug curves, an activation curve, additive
and synergistic matrices, and a Loewe sham — a drug combined with itself, whose
noise-free combination index is 1 wherever it is defined. Each keeps the `config.json` that
generated it, so the true parameters behind every fit are known:

```python
from synfit.synthetic import list_scenarios, scenario_config, scenario_dir

print(list_scenarios())
#  ['matrix_antagonism', 'matrix_asymmetric_potency', 'matrix_independent', ...]

path = scenario_dir("single_drug_clean")
csv = path / "single_drug_clean.csv"           # concentration, y, replicate
truth = scenario_config("single_drug_clean")   # the generating parameters
print(truth["hill_params"]["c50"])
```

Matrix scenarios hold one `rep*.csv` per replicate; single-drug scenarios hold
one tidy CSV. The installed CSVs are fixed within a release, so you can use them
as reproducible fixtures and record `synfit.__version__` with your analysis.

### Fit a bundled synthetic dataset

The bundled scenarios make a compact, reproducible starting point. This one
loads a noisy three-replicate curve and fits it. Its ground truth is available
because the dataset is synthetic; real experimental data does not have one.

```python
# Generates synthetic-fit.png
import pandas as pd
from synfit import FitConfig, SingleDrugFit
from synfit.plotting import dose_response_plot
from synfit.synthetic import scenario_config, scenario_dir

# load dataset (here, one of the synthetic ones)
name = "single_drug_noisy"
directory = scenario_dir(name)
data = pd.read_csv(directory / f"{name}.csv")
truth = scenario_config(name)["hill_params"]

# fit
result = SingleDrugFit(data, FitConfig(noise="lognormal")).fit()

# plot
dose_response_plot(
    data, result, reference=truth, x_label="Concentration [µM]",
)
```

![Synthetic observations with the fitted Hill curve, its 95% pointwise confidence band, and the known generating curve.](https://raw.githubusercontent.com/szarma/synfit/v0.9.0/docs/images/synthetic-fit.png)

The shaded band is an approximate **confidence interval for the fitted
curve**, derived from the parameter covariance. It is not a prediction interval
for individual observations, and it need not contain the whole true curve in
every experiment. The plotting helper uses a symmetric logarithmic axis when the data
contains a zero-dose control.

For an observed matrix, reference surfaces, and synergy score heatmaps generated
by their accompanying code, see the
[matrix synergy tutorial](https://github.com/szarma/synfit/blob/main/docs/tutorials.md).

## How the fitting works

Fits run `scipy.optimize.minimize` (L-BFGS-B) in scale-relative coordinates, so
they are stable across response magnitudes. Parameters are bounded through
`FitBounds`: L-BFGS-B enforces box bounds, and the objective also includes a
`log_wall` penalty that is zero inside the bounds and rises linearly outside
them. Variance is profiled out of the likelihood
analytically where the noise model allows it. When a heteroscedastic Gaussian
fit stalls on its first iteration, it is retried once with variance initials
refreshed from the data.

### Noise models

The noise model is part of the fit, configured through `FitConfig.noise` as a
tagged `NoiseSpec` union that serialises to JSON — for example
`{"kind": "gaussian_linear", "a_init": 0.001, "b_init": 0.0}`. The fittable
variants are `GaussianConstant`, `GaussianLinear`, `GaussianQuadratic` and
`Lognormal`.

Constant Gaussian and Lognormal profile their single variance term
analytically. The linear and quadratic Gaussians instead fit coefficients of a
response-dependent variance, σ²(μ) = a + b·d + c·d², where
d = μ − min(effect_0, effect_inf). This anchors the variance at the lower
asymptote when scatter grows with signal. Per-point `y_err` weights remain
available for constant Gaussian and Lognormal fits.

## Synergy references

Each score follows its published definition:

- **ZIP (Zero Interaction Potency)** — Yadav B, Wennerberg K, Aittokallio T, Tang J.
  *Searching for drug synergy in complex dose–response landscapes using an interaction
  potency model.* Computational and Structural Biotechnology Journal 13:504–513 (2015).
  [doi:10.1016/j.csbj.2015.09.001](https://doi.org/10.1016/j.csbj.2015.09.001) —
  the best-known implementation of this method is SynergyFinder. `synfit` implements
  the published formulation for symmetric (4-parameter) curves; for 5-parameter drugs
  it keeps the moving drug's asymmetry in the slice fits, which is an extension of the
  published method and reduces to it when the asymmetry equals 1.

  Tested scope: the δ-scores are checked against fixtures generated by the
  independent [`synergy`](https://pypi.org/project/synergy/) package for two
  dose–response landscapes (one synergistic, one potentiated), agreeing to better
  than `1e-3` in δ units. That is evidence for these cases, not a general proof of
  equivalence to any other implementation.
- **Bliss independence** — Bliss CI. *The toxicity of poisons applied jointly.*
  Annals of Applied Biology 26:585–615 (1939).
- **HSA (Highest Single Agent)** — the combination is compared against whichever
  single agent acts more strongly; see Berenbaum MC. *What is synergy?*
  Pharmacological Reviews 41:93–141 (1989) for the reference frames and how they
  differ. HSA is undefined for an inhibitor combined with an activator.
- **Loewe additivity** — Loewe S, Muischnek H. *Über Kombinationswirkungen.*
  Naunyn-Schmiedeberg's Archiv für experimentelle Pathologie und Pharmakologie
  114:313–326 (1926).

### What each function returns

The synergy helpers do not share one sign convention — they return three
different kinds of quantity, so read the units before reading the sign:

| Function | Returns | Synergy is |
|---|---|---|
| `bliss_independence`, `bliss_reference`, `hsa_reference`, `loewe_reference`, `zip_reference` | the **expected response** under that null model, in response units | not a sign — compare against the observed response |
| `zip_fitted_surface` | the fitted ZIP **combination** surface, in response units | not a null model |
| `zip_delta` | a signed δ-score, `-(f_c - f_zip)` in fraction-affected units | **negative** |
| `zip_scores` | `ZipResult`: reference, fitted surface, δ-score, and slice-failure metadata | δ is **negative** for synergy |
| `loewe_ci` | a **combination index**, a dimensionless ratio | **below 1** (1 is additive, above 1 antagonistic) |

The top-level `synfit` namespace exports the reference helpers above.
`synfit.bliss` also exposes `bliss_deviation` and `hsa_deviation`, which return
normalised deviations (negative for synergy); see the
[API reference](https://github.com/szarma/synfit/blob/main/docs/api.md).

## Contributing

See [CONTRIBUTING.md](https://github.com/szarma/synfit/blob/main/CONTRIBUTING.md) for the development setup and the test
and release commands.

## License

MIT — see [LICENSE](https://github.com/szarma/synfit/blob/main/LICENSE).
