Skip to content
Drybench Assay product page

Docs and methods

Drybench Assay.

How the assay analyser turns a plate export into C50s with intervals, what it withholds and why, what it lets you take away, how the engine is checked against R and drc, and what leaves your browser (nothing).

Updated 14 October 2026 · v0.2.0

Methods

What the app shows, how it works and how it was built, in the words of its own write-up, followed by the parameter defaults and the data sources with their licences.

Standard-curve interpolation

The Standard curve mode fits linear least squares with a free intercept, a four-parameter logistic with the existing bounded fitter, or, for standard curves only, a five-parameter logistic (seven or more standards; asymmetry s within 0.1 to 10, refused at that edge; its midpoint parameter is not a C50). Concentrations retain their selected mass or molar unit. It averages standard responses per concentration and unknown responses per exact sample label and dilution factor before interpolation. Optional mean-blank subtraction applies equally to standards and unknowns. The original-sample estimate multiplies by the explicitly entered pre-assay dilution factor; reagent dilution shared with standards must not be applied again.

Only the retained standard-concentration range is reported. An unknown outside it has a status and no extrapolated concentration. This tested range does not establish LLOQ, ULOQ or a detection limit. R² is descriptive; standards' back-calculated recovery and residuals aid review. Each interpolated unknown carries a 95 percent inverse-prediction interval (classical inverse regression for a linear curve; an approximate delta-method interval for the 4PL and 5PL), with the reading variance pooled from every kept standard reading; it is withheld under weighting, when the standards have unequal numbers of kept readings, with fewer than three degrees of freedom on the standard concentrations (concentrations minus parameters), near an asymptote or when a bound leaves the standards; constant variance is assumed. No automatic outlier removal, model selection or clinical validation is provided. The model must be appropriate for the user's ELISA or protein-assay protocol. NIST/SEMATECH §2.3.6.6 supports linear inverse calibration; Cox et al., Assay Guidance Manual, Immunoassay Methods (2019), covers model choice, recovery and weighting. Methods and primary-source links are also available beside the calibration results.

What it does

Paste or drop a plate-reader export, map the plate, and every compound's dose-response curve is fitted in batch: a four-parameter logistic model in log concentration with any parameter fixable, an unweighted 95 percent profile extra-sum-of-squares interval (Wald as an alternative); weighted intervals are unavailable, the Hill slope, both plateaus and R², with a relative or an absolute C50 and the rules that decide when a value is shown and when it is withheld with its reason. Plate exports are read by recognised layouts at 96 and 384 wells and the header is discarded; a long or wide table with units in its column headers works too. The plate map is assigned by keyboard or touch, saved as a template for next week, or placed from a dilution plan sent by the calculators.

Every result carries its method: the equation, the algorithm, the weighting, the constraints, the interval method, the exclusions, the normalisation formula with the controls it used, Z′ per plate and the engine version, printed as a method block and copied as a paragraph for a methods section. The same settings go into the CSV for GraphPad Prism (XY table) and the R snippet (drc::drm), so a fit can be reproduced elsewhere. Structures are drawn beside potencies when a SMILES column is present, and the C50s are written as your numbers on the target and the compounds, where the other apps can read them.

How it works

The engine is a pure module the suite shares: a bounded Levenberg-Marquardt fit with an analytic Jacobian and deterministic starting values read off the data (the extremes, the half-response crossing and the direction), so the same data give the same fit on every run; unweighted least squares by default, or 1/Y² by the fitted curve, using fixed weights from the preceding fitted response in an IRLS iteration. The Wald interval takes its standard error from (JᵀJ)⁻¹s² at the solution; the profile interval walks logC50 out from the fit, refitting the other parameters, until the objective rises to its minimum times 1 + F(0.95; 1, n − p)/(n − p), and labels a bound not reached within ten log units as not bounded within the search. Constant, unidentifiable and unconverged data have no reportable midpoint. Steep or runaway finite searches are labelled numerically unresolved; this is not a global-minimum proof.

Six rules, applied in order, decide what is shown: fewer than four distinct concentrations refuses the fit; a two-sided within-plate rank permutation test requires response evidence at p < 0.05 under independent exchangeable readings; a C50 the tested concentrations do not bracket on both sides is withheld; a free plateau with fewer than two concentrations at or beyond its bend point (C50 × 4.6805 to the power ±1/|Hill|) withholds the relative C50; an absolute C50 needs two concentrations predicted on each side of 50 percent; and a profile interval wider than two log units, or unbounded, flags the value as poorly determined. Batches run in a Web Worker in chunks of 25 curves with the progress read out and a cancel that stops after the current chunk; one curve fits on the page itself. No account is needed for scientific work: scientific computation stays local, and your plate data are kept as an assay object in this browser and in the session file you export, never in a link.

How it is checked

The engine is compared with named oracles run offline on published fixtures it never saw: 33 synthetic curves from a checked-in generator with known parameters and seeded noise, covering Hill slopes of 0.5, 1 and 3, plateaus away from 0 and 100, missing plateaus, rising curves, raw counts, high noise, 1/Y² weighting, fixed parameters, the four-concentration boundary, a three-concentration refusal, a flat line and a step; the worked MTT plate with three compounds; four control sets for Z′; and six nested model comparisons. Compatible unweighted estimates and resolved intervals are checked against the archived R oracle; historical weighted loss, old reporting rules and unresolved bounds are not validation of version 2. Relative IRLS is separately checked against SciPy least_squares with weights frozen within each solve. Z′ and ordinary nested-comparison arithmetic retain their reference checks. The tolerances are absolute on the logarithm: log10 C50 within 0.00043, Wald bounds within 0.0022 and profile bounds within 0.0043 log10 units; Hill within 0.1 percent; the plateaus within 0.1 percent of the span; legacy rule outcomes archived; the version 2 permutation policy is tested separately. The fixture files, their hashes, the generator and the oracle script are published on the verification page. The wording stays draft until the independent reviewer signs the fixture file and the method page (Q-19); agreement with Prism has not been checked, and no claim is made about it.

Equations and defaults

The model
y = Bottom + (Top − Bottom)/(1 + 10^((logC50 − x)·Hill)), x = log10 of the concentration in mol/L, Hill below zero for a falling curve; the parameterisation Prism documents, mapped to drc's LL.4 by e = C50, b = −Hill, c = Bottom, d = Top. This is the default model; bell-shaped and biphasic forms are fitted only when chosen, each with its own methods entry; any parameter may be fixed (Top at 100, Bottom at 0, Hill at ±1 by direction).
The fit
Bounded Levenberg-Marquardt with analytic derivatives, projected-gradient convergence checks and deterministic starts. A converged, identifiable solution is required. Unweighted least squares is the default. Relative fitting uses IRLS: start unweighted, freeze 1/f_previous² weights for each solve, update until parameters stabilise, and refuse nonpositive observations, nonpositive predictions or nonconvergence. Positive observations are an app domain restriction. SD is proportional to response; variance is proportional to response squared (constant CV). No Prism numerical-equivalence claim.
No finite minimum
Numerically constant observations, unidentifiable parameters and unconverged fits have no reportable midpoint. Finite multistart and limit searches can identify unresolved steep-step or runaway cases, but do not prove a global minimum does not exist. Legacy report codes are retained for file compatibility; the UI says numerically unresolved.
Wald interval
logC50 ± t(0.975, n − p) × SE with the standard error from (JᵀJ)⁻¹s² at the solution, s² the residual variance with n − p degrees of freedom; the named alternative, and drc's default confint.
Profile extra-sum-of-squares interval
Profile extra-sum-of-squares interval, unweighted only: refit nuisance parameters at each midpoint until RSS exceeds RSS_min × (1 + F(0.95;1,n−p)/(n−p)). This is an approximate nonlinear Gaussian constant-variance interval. Failed refits trigger multistart fallback or an unresolved bound. A missing or failed profile bound reads “not resolved within the search”, never infinity. Relative-weighted and numerically zero-residual fits have no interval.
The six rules, in order
Numerical validity is checked first. (i) Fewer than four distinct positive doses refuses fitting. (ii) A two-sided permutation test of dose/response rank covariance must have p < 0.05, permuting within plate; exact enumeration through eight observations, otherwise 1999 seeded permutations and the plus-one p-value. A failed check leaves a descriptive curve and no midpoint. Four unreplicated doses cannot pass: the smallest two-sided exact p is 2/24. This app-specific reporting policy requires independent exchangeable observations within each block and establishes ordering, not biological activity. (iii) Require two doses on each side of C50. (iv) Require two doses beyond each free plateau bend at C50 × 4.6805^(±1/|Hill|); fixed plateaus are exempt. (v) A 50-percent-control crossing needs two doses on each side. (vi) A wide or unresolved unweighted profile is flagged. The flat-line F diagnostic never gates reporting.
Relative and absolute C50
The assay-intent setting names the endpoint: inhibition gives IC50, activation/agonism EC50, and unspecified C50. Intent is preserved in links, saved analyses, exports and object measurements. Direction alone cannot determine that meaning. Relative C50 is the fitted midpoint. Absolute C50 is the 50-percent-control crossing, logC50 − log10((Top−Bottom)/(50−Bottom)−1)/Hill, only on an appropriate percent-control scale with an in-range crossing.
Normalisation and Z′
percent = 100 × (y − μneg)/(μpos − μneg) with each plate's own control means, the formula shown with the wells it used; raw, percent of control and percent inhibition as named alternatives. Z′ = 1 − 3(σpos + σneg)/|μpos − μneg| with sample standard deviations (n − 1) and n shown, refused when either control has fewer than two wells, and read against the 0.5 convention of Zhang, Chung and Oldenburg 1999 as an observation.
Model comparison
Only unweighted Gaussian constant-variance fits: nested extra-sum-of-squares F comparisons are approximate nonlinear-model diagnostics. Flat versus logistic is nonregular because midpoint and slope are unidentified under the flat null. AICc uses K = regression parameters + residual variance and is descriptive model comparison. Relative-weighted F/AICc comparisons are unavailable.
Statistics
The F distribution's upper tail by the regularised incomplete beta function (a continued fraction, modified Lentz), the t quantile by bisection on the t distribution, log-gamma by Lanczos; all in src/lib/fit/stats.ts, checked against known values.
Plate exports
A plate block is recognised by its structure, never by a vendor's name in the header: a row of column numbers over lettered rows (the grid), a Plate: line with a Temperature header or a Results section with a read label, at 96 or 384 wells; byte-order marks, UTF-16 and a decimal comma are read; every line outside the plate block is counted and discarded. Tables are long (one row per well or point) or wide (one column per compound) with the unit in the concentration header, converted to mol/L by the suite's unit table.

Timing

Two cases are timed against a 3,000 ms target at CPU 4×, the browser's cache primed by an earlier visit: each figure is the median of five runs, copied by a person from a run's report with its commit, machine and one-minute load. A figure from a 2-vCPU CI runner is informative only: it is neither a pass nor a failure.

CaseMeasuredMedian (fastest to slowest)MachineReading
Workspace, first fit: Fit pressed to results on the 96-well example2026-10-12, 70b9d93897 ms (657 to 981, 5 runs)a build container, local run, 4 cores, load 1.49 to 2.95within target
Workspace, first fit: Fit pressed to results on the 96-well example2026-10-12, 88319881,000 ms (708 to 1,279, 5 runs)a build container, local run, 4 cores, load 0.85 to 2.49within target
IC50 calculator page: navigation to the pasted table's IC50, slow 4G2026-10-14, 501a124958 ms (903 to 1,116, 5 runs)a build container, local run, 4 cores, load 1.48 to 2.25within target
Workspace, first fit: Fit pressed to results on the 96-well example2026-10-14, 501a1241,431 ms (1,240 to 1,727, 5 runs)a build container, local run, 4 cores, load 2.25 to 3.00within target

Data sources, licences and versions

Sources and licences

  • Hill 1910; Ritz, Baty, Streibig and Gerhard 2015 (the four-parameter logistic model)Method (cited)

    The logistic form in log concentration, y = Bottom + (Top − Bottom)/(1 + 10^((logC50 − x)·Hill)), and the published LL.4 parameterisation the fixtures map to (e = C50, b = −Hill, c = Bottom, d = Top); no drc code is used or translated.

    Hill AV. J Physiol 1910;40:iv-vii. Ritz C, Baty F, Streibig JC, Gerhard D. Dose-response analysis using R. PLoS ONE 2015;10:e0146021 (read 5 October 2026).

  • Levenberg 1944 and Marquardt 1963 (the fitting algorithm)Method (cited)

    Damped least squares with bounds by projection and an analytic Jacobian; the deterministic starting values are the suite's own, not drc's heuristic.

    Levenberg K. Q Appl Math 1944;2:164-168. Marquardt DW. J Soc Indust Appl Math 1963;11:431-441 (from memory; brief §4.1).

  • Zhang, Chung and Oldenburg 1999 (Z′)Method (cited)

    Z′ = 1 − 3(σpos + σneg)/|μpos − μneg| with sample standard deviations, and the 0.5 convention it is compared with as an observation.

    Zhang J-H, Chung TDY, Oldenburg KR. A simple statistical parameter for use in evaluation and validation of high throughput screening assays. J Biomol Screen 1999;4:67-73 (read 5 October 2026).

  • Sebaugh 2011; Sebaugh and McCray 2003 (relative and absolute C50, bend points)Method (cited)

    The relative C50 as the curve's own midpoint and the absolute C50 as the crossing of 50 percent of the controls; two concentrations beyond each bend point, at C50 × 4.6805^(±1/|Hill|), before a value is shown (the constant checked by arithmetic; the 2003 paper not yet read).

    Sebaugh JL. Guidelines for accurate EC50/IC50 estimation. Pharm Stat 2011;10:128-134 (abstract read 6 October 2026). Sebaugh JL, McCray PD. Defining the linear portion of a sigmoid-shaped curve: bend points. Pharm Stat 2003;2:167-174.

  • GraphPad Prism user guides (the stated defaults, read as a behavioural reference)Method (cited): the defaults the guides state, no text copied

    The Prism 8 guide's statement that its initial default is the asymmetrical (profile-likelihood) interval and its discussion of asymmetric confidence bounds; the Prism 11 guide's account of 1/Y² weighting by the fitted curve. The CSV for GraphPad Prism (XY table) follows the XY layout the guide describes. GraphPad and Prism are trademarks of GraphPad Software LLC (Dotmatics); this app is not affiliated with or endorsed by them, and no agreement with Prism's numbers is claimed.

    GraphPad Prism 8 and Prism 11 curve-fitting guides, read 5 and 6 October 2026.

  • RDKit.js 2026.03.6BSD-3-Clause

    Structures drawn beside potencies when a SMILES column is present, in the shared Web Worker the molecule app also uses; it loads only then (about 2.4 MB of WebAssembly).

    RDKit: Open-source cheminformatics. https://www.rdkit.org

    Third-party notices

  • The worked MTT example and the oracle fixturesMIT (generated data; no third-party data)

    One 96-well plate of A570 readings with three compounds, vehicle and kill controls and blanks, generated from known parameters with a seeded noise model, and the 33 synthetic curves, control sets and oracle values the engine is checked against.

    pipeline/assay-analyser/generate_curves.py and reference.R (the suite's authors, 6 October 2026).

  • PRISM Repurposing 19Q4CC BY 4.0

    Per-well readings for the guided walk's example: erlotinib's wells and the control wells for HCC827 cells, secondary screen HTS002, three replicate plates. Normalised and fitted in this app; not the Broad's analysis.

    DepMap, Broad; Corsello, Steven; Kocak, Mustafa; Golub, Todd (2019). PRISM Repurposing 19Q4 Dataset. figshare. Dataset. https://doi.org/10.6084/m9.figshare.9393293.v4

    CC BY 4.0

  • Pfaffl 2001, relative quantification in real-time RT-PCRMethod (cited)

    Efficiency-corrected relative expression in log space; explicit measured or assumed amplification efficiencies and calibrators.

    Pfaffl MW. Nucleic Acids Res 2001;29:e45. Primary method reviewed 10 October 2026.

  • Hellemans et al. 2007, qBase relative quantificationMethod (cited)

    Geometric normalization across explicitly selected reference assays. Reference stability, uncertainty propagation and inter-run calibration are not implemented.

    Hellemans J et al. Genome Biol 2007;8:R19. Primary method reviewed 10 October 2026.

Times on screen are local time with the UTC offset; times in exports are UTC. Dates with no time are shown at the precision the source gave them.

Exports

The Export menu offers the results as CSV (compound, C50, log C50, the interval's bounds with the method named, Hill, Top, Bottom, R², points, replicates, exclusions, flags and whether relative or absolute, with the provenance sidecar and README), the session as a JSON project file (the plate blocks, the layout, the settings, the exclusions and the results with the engine version, so it reopens here), the small multiples as SVG or PNG with the provenance embedded, a printed report with the method block, the CSV for GraphPad Prism (XY table), the R snippet (drc::drm) with the data inline and the method paragraph as text. Copy link carries the view and fit settings only; a session file carries the data.

Release gate: before the app leaves the preview, its exports and printed reports will carry the data versions, parameters, limits and a timestamp needed to reproduce a result, as the regulatory position says. This section will describe each format as it ships.

Verification

Each module below is a set of automated tests in the app's source code, with the expected results written into the tests. They check the app's logic against recorded inputs and stored reference records; none of them calls a live service.

src/lib/fit/fixtures.test.ts
Compatible unweighted curves from pipeline/assay-analyser/fixtures/synthetic-curves.json and the MTT compounds against drc-reference.json: estimates, objectives and resolved Wald/profile bounds; historical relative-loss fixtures remain archival. Version 2 relative IRLS is checked separately against a SciPy fixed-weight reference; unresolved profile refits are labelled explicitly rather than forced to reproduce an old finite bound. Also checks the compatible unweighted statistics and historical flat-line F diagnostic, Z′ for the five control sets and six nested F-tests, within the stated tolerances (absolute on the logarithm, never a percentage of one); the files' hashes against manifest.json, so a changed fixture fails before any value is compared. The oracle is R 4.3.3 nls with drc 4.0-0 as a cross-check, run offline by reference.R.
src/lib/fit/fit.test.ts
The statistics against known values; Levenberg-Marquardt on a line with bounds; the 4PL gradient against finite differences; a noiseless curve recovered exactly and the same fit on every run; a numerically unresolved steep step; the refusals; the absolute C50 and the bend constant; the normalisation modes; Z′'s refusal; AICc and the F-test.
src/lib/table/table.test.ts
The eight synthetic plate files and two tables under pipeline/assay-analyser/plates against expected.json: every well read back, every header line discarded, the profile and delimiter detected; byte-order marks, UTF-16 and the decimal comma; the plate map's rectangles, roles, filled series, the plan from the calculators and the curves it reads.
pipeline/assay-analyser/generate_curves.py and reference.R
The generator (NumPy, seeded, fits nothing) and the oracle (R 4.3.3, nls and optim; drc 4.0-0, MASS and jsonlite), MIT; reference.R records the R and package versions, the scripts' and inputs' hashes and the date, and writes manifest.json. drc's source is never translated and its starting heuristic is not used.

Draft until the independent reviewer signs the fixture file and the method page (Q-19).

Release gate: before the app leaves the preview, each release will publish a verification dataset with expected outputs on this page, so you can run your own checks.

Published verification sets

MethodsOracleFixturesToleranceDateFile
four-pl, levenberg-marquardt-bounded, weighting-1-over-y2R32log10 C50 within 0.00043; Hill within 0.1 percent; Top and Bottom within 0.1 percent of the span; the objective within 1e-6 relative2026-10-06drc-reference.jsonRecorded for version 0.1.0; the test suite checks the current engine against it.
four-pldrc30the same minima by LL.4 (e = C50, b = −Hill, c = Bottom, d = Top), recorded as a cross-check2026-10-06drc-reference.jsonRecorded for version 0.1.0; the test suite checks the current engine against it.
wald-log-ic50, profile-likelihood-log-ic50R30Wald bounds within 0.0022 and profile bounds within 0.0043 log10 units; resolved unweighted bounds only; unresolved refits and historical weighted results are qualified2026-10-06drc-reference.jsonRecorded for version 0.1.0; the test suite checks the current engine against it.
ic50-rules, absolute-ic50R36archived version-1 reporting outcomes; compatible dose counts and crossing algebra only; version-2 permutation policy tested separately2026-10-06drc-reference.jsonRecorded for version 0.1.0; the test suite checks the current engine against it.
z-primeR5within 1e-9; the refusal below two wells exact2026-10-06zprime-controls.jsonRecorded for version 0.1.0; the test suite checks the current engine against it.
extra-sum-of-squares-fR6residual sums of squares within 1e-6 relative; F within 1e-5 and p within 1e-4 relative2026-10-06drc-reference.jsonRecorded for version 0.1.0; the test suite checks the current engine against it.
standard-curve-interpolationPython fractions71e-9 relative; range flags and refusals exact2026-10-10calibration-linear-exact.json
standard-curve-interpolationSciPy2parameters and fitted responses within 2e-5 (log10 midpoint absolute); concentrations within 5e-5 relative; range flags exact2026-10-10calibration-4pl-scipy.json
calibration-inverse-predictionPython fractions15linear bounds within 1e-9 relative (exact rational arithmetic); 4PL and 5PL bounds within 1e-6 relative and concentrations within 5e-5 relative (SciPy curve_fit, central finite differences); statuses and withheld reasons exact; coverage simulated, 2,000 replicates in each of 8 replicated designs (linear and 4PL; 5PL not simulated), 94.4 to 95.5 percent against gates of 0.93 and 0.90; against R 4.4.3 investr 1.4.2 (invest, over the individual standard readings) on the 11 intervals for unknowns read once: bounds and the pooled variance within 1e-9 relative (linear) and 1e-6 relative (4PL, 5PL), degrees of freedom exact2026-10-14calibration-inverse-v2.jsonRecorded for version 0.13.0; the test suite checks the current engine against it.
five-pl-calibrationSciPy5parameters (ln s included) and fitted responses within 2e-5 absolute; concentrations within 5e-5 relative (floor 1e-6); RSS 1e-6 (not compared at a zero residual), F 1e-5, p 1e-4 relative; AICc 1e-6 absolute; statuses exact; R drc LL.5 agrees on the 3 fitted curves2026-10-12calibration-5pl-scipy.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
replicate-summariesPython fractions2means and SDs 1e-9 relative; n and wells exact2026-10-10replicate-summaries.json
z-primeR6within 1e-9; refusals exact2026-10-10zprime-degenerate.json
growth-window-rateSciPy9μ and the doubling time within 1e-9 relative (exact cases by Python fractions); residuals within 1e-9; withheld states, rows and n exact2026-10-10growth-window.json
michaelis-menten-initial-rateSciPy8Km and Vmax within 1e-5 relative; the residual sum of squares within 1e-6 relative; correlation within 1e-4; kcat within 1e-5 relative; states, flags and refusals exact2026-10-10michaelis-menten.json
inhibition-global-fitSciPy18Vmax, Km, Ki and Ki′ within 1e-5 relative (hand values 1e-9); the residual sum of squares within 1e-6 relative; correlations within 1e-4; refusals, the named level and the constant at the box exact; against R 4.4.3 nls on the 11 fits without bound contact: constants within 1e-5, the residual sum of squares within 1e-6 relative2026-10-12inhibition-global-v2.jsonRecorded for version 0.5.0; the test suite checks the current engine against it.
inhibition-model-comparisonSciPy8the ranking, the states outside it and refusals exact; constants within 1e-5 relative; the residual sum of squares within 1e-6 relative; AICc and ΔAICc within 1e-6; F within 1e-5 relative; p within 1e-4 relative; against R 4.4.3 nls, AIC and anova on the 6 compared cases: constants within 1e-5, ΔAICc within 1e-6, the nested F within 1e-5 relative2026-10-12inhibition-compare.jsonRecorded for version 0.8.0; the test suite checks the current engine against it.
substrate-inhibition-haldaneSciPy9Vmax, Km, Ksi and the optimum within 1e-5 relative (hand values 1e-9); the residual sum of squares within 1e-6 relative; the Km–Ksi correlation within 1e-4; withheld states, the count beyond the optimum and refusals exact; against R 4.4.3 nls on the 4 estimates: constants within 1e-5, the residual sum of squares within 1e-6 relative2026-10-13substrate-inhibition.jsonRecorded for version 0.8.0; the test suite checks the current engine against it.
inhibition-profile-intervalSciPy7resolved bounds within 1e-3 ln units of the direct profile; the threshold within 1e-8 relative; side states, reasons and the poorly-determined flag exact; coverage simulated, 1,000 replicates per design; R not run: the named oracle for these intervals is a SciPy direct profile with its coverage run2026-10-13inhibition-profile.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
thermal-shift-tm-intervalsSciPy11replicate means and SDs within 1e-3 °C, n and the wells not counted exact; the residuals' lag-1 r1 within 1e-6; the withheld single-well profile within 1e-3 °C of the direct profile; coverage simulated under independent, correlated and misfitting melts, 1,000 per design; R not run: the named oracle for these intervals is a SciPy profile with its coverage simulation2026-10-13thermal-tm-intervals.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
ki-cheng-prusoff-boundsPython fractions10Ki and both bounds within 1e-12 relative; the four-figure display exact; refusals exact2026-10-13ki-bounds.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
thermal-shift-apparent-tmSciPy13sigmoid Tm within 1e-3 °C; ln k within 1e-4; plateaus within 1e-4 of the span; the derivative extremum's temperature exact; refusals and the disagreement flag exact; against R 4.4.3 nls(SSfpl) on the 7 fitted melts: Tm within 1e-3 °C, ln k within 1e-4, plateaus within 1e-4 of the span2026-10-12thermal-shift-v2.jsonRecorded for version 0.5.0; the test suite checks the current engine against it.
michaelis-menten-profile-intervalSciPy9resolved bounds within 1e-3 ln units of the direct profile; the threshold within 1e-8 relative; side states, reasons and the poorly-determined flag exact; coverage simulated, 2,000 replicates per design; against a direct profile computed in R 4.4.3 from its own nls fit on the 7 cases with an interval: resolved bounds within 1e-3 ln units, the threshold within 1e-8 relative2026-10-12michaelis-menten-profile-v2.jsonRecorded for version 0.5.0; the test suite checks the current engine against it.
qpcr-fold-change-welch-intervalSciPy10normalised log2 quantities, differences, standard errors and bounds within 1e-9; degrees of freedom and t within 1e-9 relative; n, statuses and withheld reasons exact; against R 4.4.3 t.test (Welch) and SciPy ttest_ind on the 8 intervals at 1e-92026-10-12qpcr-welch.jsonRecorded for version 0.7.0; the test suite checks the current engine against it.
growth-gompertz-fitSciPy9A and μm within 1e-5 relative, λ within 1e-5 of the time span; the residual sum of squares within 1e-6 relative; the noise-free generating parameters within 1e-8; statuses and withheld reasons exact; against R 4.4.3 nls (port) on the 6 interior fits at 1e-42026-10-12growth-gompertz.jsonRecorded for version 0.7.0; the test suite checks the current engine against it.
progress-curve-initial-ratesPython fractions12slopes, intercepts, rates and percent conversion within 1e-9 relative (exact straight lines by Python fractions; the noisy plate by SciPy linregress); residuals within 1e-9; withheld wells, reads used and the 10 percent flag exact; Km and Vmax on the plate's window rates within 1e-6 of SciPy curve_fit; against R 4.4.3 lm on the 25 estimated wells: slope and intercept within 1e-9 relative2026-10-12progress-curve-rates.jsonRecorded for version 0.7.0; the test suite checks the current engine against it.
ki-cheng-prusoff-competitivePython fractions11Ki within 1e-12 relative; the four-significant-figure display exact; refusals exact, with no number2026-10-10ki-cheng-prusoff.json
curve-comparisonSciPy12residual sums of squares within 1e-6 relative; F within 1e-5 and p within 1e-4 relative; the C50 difference and its Wald bounds within 1e-6 log10; profile bounds within 1e-3 log10; refusals and withholdings exact; coverage simulated, 2,000 replicates per design; against R 4.4.3 drc 4.0-0 (drm with curveid and pmodels, anova, compParm) on the 11 compared cases: residual sums of squares and F within about 1e-15, p within 3e-7 relative where it exceeds 1e-12 (below that drc's anova reports 1 − pf at its precision limit, and both are recorded as below 1e-12), the C50 difference within 4e-8 relative, and the Wald SE within 0.7 percent (drc's Hessian against the engine's JᵀJ, compared at 1e-2): agrees2026-10-13curve-compare-v2.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
experiment-summary-log-c50Python fractions11mean, SD and bounds within 1e-9 log10; the t quantile within 1e-8 relative; k, n, levels, withheld lists, refusals and the carried flag exact; against R 4.4.3 t.test on the 6 cases with an interval, within 1e-9 log102026-10-12experiment-summary.jsonRecorded for version 0.10.0; the test suite checks the current engine against it.
grubbs-two-sided, studentised-residuals-one-wayR21G, t and critical G within 1e-9 relative; p within 1e-8 relative; rows, candidate flags and withheld states exact; the outliers package's folded two-sided p recorded, not used, where twice the bound exceeds 12026-10-13column-outliers.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
outlier-candidatesSciPy13leverages, residuals, internal and external t, p, adjusted p, G and critical values within 1e-6 relative; candidates, indices and withheld states exact; R 4.4.3 rstudent, hatvalues, car outlierTest and outliers grubbs.test agree on 2 linear fits and 3 control groups; R not run on the 4PL fits2026-10-13outlier-candidates.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
power-sample-sizeR26power within 1e-8 (t, proportions) and 1e-7 (ANOVA); computed n within 1e-6 relative; the smallest integer n, flags and withheld states exact; SciPy agrees on 19 of 192026-10-13power-sample-size.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
column-descriptives, column-t-tests, column-rank-tests, median-order-statistic-interval, shapiro-wilk-royston, column-effect-sizesR50descriptives, quantiles, intervals and estimates within 1e-9 relative; t, df and t-test p within 1e-8 relative; exact rank p and achieved coverage within 1e-10; approximate rank p within 1e-8 relative; Shapiro–Wilk W within 1e-7 and its p within 1e-5 relative; counts and withheld states exact; SciPy agrees on 179 of 1792026-10-13column-statistics.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
one-way-anova, post-hoc-comparisons, rank-groups, repeated-measures, two-way-anovaR22sums of squares, F, η², ω², means, differences and SEs within 1e-9 relative; F, t and chi-squared p within 1e-8; Tukey–Kramer and Games–Howell p within 1e-7; Dunnett p within 1e-6; Greenhouse–Geisser and Huynh–Feldt ε within 1e-9; the mixed model's F and p within 1e-6 relative, its variances and means within 1e-5; codes, df and m exact2026-10-13groups.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
p-value-adjustmentR10adjusted p within 1e-12 relative; counts, m and withheld states exact2026-10-13fdr.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
correlation-pearson-spearman, linear-regression-qr, nonlinear-families, relationship-model-comparison, deming-regression, bland-altmanR115r, ρ, S and t within 1e-10 relative, t-based p within 1e-8 relative, correlation bounds within 1e-9; regression within 1e-9 relative, p within 1e-8 relative; curves: noisy residual sums of squares within 1e-8 relative, parameters within 1e-6, profile bounds within 1e-3, polynomials within 1e-9 relative; model comparison ΔAICc within 1e-8 (linear) and 1e-4 (nonlinear); Deming within 1e-10, the jackknife and Bland–Altman within 1e-9; codes, counts and order exact; SciPy agrees on 105 of 1052026-10-13relationships.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
nonlinear-familiesSciPy5simulated coverage of the 95 percent profile intervals at least 0.90 for every quantity, 2,000 seeded replicates per family; growth, one-phase, two-phase, power and Boltzmann cleared, the lowest 0.9452026-10-13nonlinear-coverage.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
kaplan-meier, median-survival, survival-tests, log-rank-hazard-ratioR34S(t), its standard error, the band, the median and its bounds within 1e-10 relative; χ² and expected counts within 1e-9 relative, p within 1e-8 relative; the hazard ratio and its bounds within 1e-9 relative; times, numbers at risk, events, censorings, observed counts and withheld states exact; identities inside R agree on 88 of 882026-10-13survival.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
contingency-tests, two-by-two-measures, mcnemar, cochran-armitageR42odds ratio, relative risk, risk difference, proportions, their bounds and expected counts within 1e-9 relative; X², chi-squared, Fisher 2 × 2, binomial and trend p and the Clopper–Pearson bounds within 1e-8 relative; Fisher r × c p within 1e-6 relative; the conditional odds ratio and its interval within 1e-8 relative of the uniroot(tol = 1e-12) transcription; refused cells and withheld states exact; checks inside R agree on 37 of 372026-10-13counts.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
four-pl, levenberg-marquardt-bounded, profile-likelihood-log-ic50R1the estimates' tolerances: log10 C50 within 0.00043, Hill within 0.1 percent, Top and Bottom within 0.1 percent of the span, Wald 0.0022 and profile 0.0043 log10 units, the objective within 1e-6 relative; the reporting gate's decision exact2026-10-10plate-walk-refit.jsonRecorded for version 0.3.0; the test suite checks the current engine against it.
gaddum-schild-global, gaddum-noncompetitive, operational-model-furchgottSciPy8pA2, s, pKB, log EC50₀, log KA and log τ within 1e-4 log units; shared levels within 1e-4 of the response span; profile bounds within 1e-3; F within 1e-5 and p within 1e-4 relative; KB within 1e-4 relative; states exact; R nls and drc agree on 6 of 7 fitted cases, and where drc's own fit differs SciPy holds the lower residual sum of squares, as it does on every fit (SCI-121)2026-10-13agonist-antagonist-v2.jsonRecorded for version 0.13.0; the test suite checks the current engine against it.
relative-potencySciPy9residual sums of squares within 1e-6 relative; F within 1e-5 and p within 1e-4 relative; ρ, the Hill ratio and the plateau differences within 1e-5 relative; profile bounds on log ρ within 1e-3 log10; states and margin classes exact; R drc LL2.4 agrees2026-10-13relative-potency.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
reporter-ratioPython fractions16ratios, folds, means, SDs, medians and percents within 1e-12 relative; states, reasons, flags and well lists exact; R median agrees2026-10-13reporter-ratio.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
percent-of-vehicle-backgroundPython fractions11percents, the blank share and means within 1e-12 relative; reasons and counts exact; R mean agrees2026-10-13viability-normalisation.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
plate-diagnosticsPython fractions17Z′, signal window, SSMD, Z, means, Welch t and df and F within 1e-9 relative; p within 1e-8; counts, states and reasons exact; R t.test and drop1(lm) agree2026-10-13plate-diagnostics.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
influence-leave-one-outSciPy8every leave-one-out shift within 0.00086 log10; outcomes and the largest well exact; R drc LL.4 refits agree2026-10-13influence-loo.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
design-narrowingSciPy5candidate variances and the current Wald half-width within 1e-6 relative (NumPy, analytic gradient, at SciPy's fit); the chosen candidate, replicates and narrowing exact; base R agrees2026-10-13design-narrowing.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
two-phase-dose-responseSciPy8the engine's residual sum of squares at most the oracle's × (1 + 1e-6); midpoints and Hill slopes within 1e-4, levels within 1e-4 of the span; F and AICc within 1e-6 and p within 1e-4 relative; states exact; R drc agrees2026-10-13two-phase-dose-response.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
negative-resultPython fractions16exact cases equal as fractions; sampled cases equal to the ported stream and within 4·√(p(1 − p)/2000) + 1/2000 of exact enumeration; means within 1e-12 relative; R not run2026-10-13negative-result.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
molar-from-massPython decimal20formula weights within 1e-4 g/mol; protein masses within 0.02 Da (ProtParam) and 0.01 Da (CIAAW 2021); molar values, active enzyme and kcat within 1e-9 relative; refusals exact2026-10-13units-molecule.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
ligand-efficiencyPython decimal13heavy atoms exact; LE and LLE within 1e-6 and as shown; reasons exact; Crippen cLogP from Python RDKit within 1e-3, a drift check, not independent2026-10-13ligand-efficiency.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
assay-driftPython fractions11centre, moving range and limits within 1e-9 relative; Pettitt K, location and flag exact, p within 1e-6 relative; statuses exact; the R qcc and trend cross-check not run2026-10-13assay-drift.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
calibration-blank-limitsPython fractions14the blank mean and SD within 1e-12 relative; limits on lines, original concentrations, percents, CV and the trend's bounds within 1e-9 relative; limits on the 4PL within 1e-6 relative; states, reasons and notes exact; R mean, sd and confint(lm) agree2026-10-13calibration-limits.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
qpcr-dilution-efficiency, qpcr-genorm-mPython fractions17slope, intercept, R², the slope's bounds, E and efficiency within 1e-9 relative on 12 series; geNorm V and M within 1e-9 relative on 5 tables; states, counts, dropped levels and the ranking exact; R lm, confint and sd agree2026-10-13qpcr-efficiency.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
hill-initial-rateSciPy11Vmax, S0.5 and h within 1e-5 relative (hand values 1e-9); the residual sum of squares within 1e-6 relative; the interval on h within 1e-3 in ln h; states exact; R nls agrees2026-10-13hill-kinetics.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
bisubstrate-initial-rateSciPy11Vmax, KmA, KmB and KiA within 1e-5 relative (hand values 1e-9); the residual sum of squares within 1e-6 relative; ΔAICc within 1e-6, F within 1e-5 and p within 1e-4 relative; states exact; R nls agrees2026-10-13bisubstrate.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
morrison-tight-bindingSciPy15v0, Ki_app and the fitted [E] within 1e-5 relative (hand values 1e-9); the residual sum of squares within 1e-6 relative; Ki by mechanism within 1e-12 relative; states exact; R nls agrees2026-10-13morrison.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
slow-binding-one-step, slow-binding-two-stepSciPy13the line's kon_app, koff, standard errors and bounds within 1e-9 relative (exact rational least squares); k5, Ki_app and k6 within 1e-5 relative (hand values 1e-9); states exact; R lm, confint and nls agree2026-10-13slow-binding.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
binding-equilibrium, binding-ki-cheng-prusoff-munson-rodbardSciPy24constants within 1e-5 relative (hand values 1e-9); the residual sum of squares within 1e-6 relative; interval bounds within 1e-3 in ln; ΔAICc within 1e-6, F within 1e-5 and p within 1e-4 relative; Ki within 1e-9 relative; states exact; R nls and drc agree2026-10-13binding.jsonRecorded for version 0.11.0; the test suite checks the current engine against it.
layout-suggestionPython fractions16trigger decisions, m, rules and refusals exact (p against 0.05/m in binary64); counts and layout properties exact2026-10-13layout-suggestion.jsonRecorded for version 0.12.0; the test suite checks the current engine against it.
combination-normalise, combination-technical-mean, combination-hsa, combination-blissPython fractions39expected fractions, excess percentage points and observable vehicle, cell and margin means within 1e-9 absolute; cell statuses, withheld reasons, top-level refusals, retained/excluded counts and contributing observation IDs exact. The planned cross-check against synergy 1.0.0 was not run (each fixture's crossCheck block records why).2026-10-18combination-analysis-v3.jsonRecorded for version 0.15.0; the test suite checks the current engine against it.
z-prime, percent-of-controls, percent-of-vehicle-background, screening-percent-inhibition, screening-robust-z, plate-diagnostics, growth-window-rate, growth-gompertz-fit, gr-metrics, qpcr-fold-change-welch-interval, two-way-anova, repeated-measures, survival-tests, log-rank-hazard-ratio, slow-binding-one-step, slow-binding-two-step, thermal-shift-tm-intervalsPython fractions60statuses, codes, reasons, rows and wells exact, every refusal decided in exact decimal arithmetic; Z′, SDs, percents, the signal window and SSMD within 1e-9 relative (an exact 0 within 1e-12); robust z within 1e-12 relative; growth μ within 1e-9 relative; GR arithmetic within 1e-9; qPCR bounds within 1e-9 log2; two-way sums of squares and F within 1e-9 relative, p within 1e-8 relative; survival χ² and the hazard ratio within 1e-9 relative; no NaN or infinite number under a value status; the survival and two-way cases by exact fractions and NumPy where the memo names R2026-10-20degenerate-v1.jsonRecorded for version 0.16.0; the test suite checks the current engine against it.
survival-tests, slow-binding-one-step, slow-binding-two-stepPython fractions29statuses, codes, counts and refusal precedence exact; survival chi-squared and rational linear controls within 1e-9 relative, p within 1e-8 relative, exact hyperbola parameters within 1e-5 relative; no R or SciPy cross-check2026-10-25survival-slow-degeneracy-v1.jsonRecorded for version 0.17.0; the test suite checks the current engine against it.
plate-diagnosticsPython standard library26edge admission, counts and reasons exact; means, Welch t and df within 1e-9 relative (exact zero within 1e-12 absolute), p within 1e-8 absolute; exact fractions and bounded Student-t sine quadrature with 34 analytical and identity checks, no R or SciPy cross-check or extreme-tail relative-accuracy claim2026-10-25plate-edge-spread-v1.jsonRecorded for version 0.18.0; the test suite checks the current engine against it.

Oracles and build-time tools

Software the app's numbers were checked against, or its data built with. None of it ships with the app or is a dependency of it.

  • R 4.3.3 (GPL-2 or GPL-3; Oracle; 2026-10-06). the nls fits, the root-found profile intervals, the F-tests and the rule checks that wrote the oracle values
  • drc 4.0-0 (GPL-2; Oracle; 2026-10-06). LL.4 and LL2.4 fits as a second route to the minimum, ED(type = 'absolute') and anova() as cross-checks; never shipped, never ported
  • MASS 7.3-60.0.1 (GPL-2 or GPL-3; Oracle; 2026-10-06). confint.nls as a cross-check on the profile intervals
  • R 4.4.3 (GPL-2 or GPL-3; Oracle; 2026-10-12). the 0.5.0 -v2 cross-checks in the oracle image: nls, nls(SSfpl) and the direct profile; the 0.6.0 inhibition comparison and substrate-inhibition fits: nls, AIC and anova; lm for the 0.7.0 progress-curve rates
  • NumPy 2.4.6 (BSD-3-Clause; Build-time tool; 2026-10-06). the seeded generator that wrote the synthetic curves, the MTT plate and the control sets
  • SciPy 1.13.1 (BSD-3-Clause; Oracle; 2026-10-10). curve_fit (Levenberg-Marquardt, three starts) for the 4PL standard curves and (trf, bounded, three starts) for the Michaelis–Menten fits, with NumPy 2.0.2; linregress for the noisy growth windows
  • Python fractions 3.11.15 (Python-2.0; Oracle; 2026-10-10). exact rational least squares for the linear standard curves and the exact growth windows; replicate means and sample SDs; Ki from IC50 by hand
  • Python decimal 3.11.15 (Python-2.0; Oracle; 2026-10-13). the molar-mass and ligand-efficiency arithmetic at 40 digits, recorded in the oracle image
  • synergy 1.0.0 (GPL-3.0-or-later; Oracle; 2026-10-18). a planned cross-check of the HSA and Bliss reference values in an isolated environment outside the repository; not run
  • Python fractions 3.13.16 (Python-2.0; Oracle; 2026-10-20). the 0.16.0 degenerate-input set: every refusal decided in exact decimal arithmetic on the decimal inputs, the exact log-rank covariance and the two-way cross-check
  • Python fractions 3.12.14 (Python-2.0; Oracle; 2026-10-25). the 0.17.0 survival and slow-binding supplemental set: exact decimal-input risk sets, covariance, relative spread and linear controls, with closed-form chi-square tails from the standard library; no R or SciPy cross-check
  • Python standard library 3.12.14 (Python-2.0; Oracle; 2026-10-25). the 0.18.0 plate-edge supplemental set: exact decimal-input relative spread and Welch moments with bounded Student-t sine quadrature and analytical distribution checks; no R or SciPy cross-check
  • SciPy 1.18.1 (BSD-3-Clause; Oracle; 2026-10-20). the 0.16.0 degenerate-input set's numbers for the cases that keep calculating, with NumPy 2.5.4 (means, SDs, least squares, the t, F, chi-squared and normal distributions, the median absolute deviation)

These tables compare compatible unweighted fixtures with the current engine when this page was built. The published R files also contain archived version-1 weighted estimates and reporting rules; those cannot validate version-2 IRLS or the permutation gate and are excluded here. Failed profile refits are marked not comparable, never converted to finite bounds. The full input sets were written by pipeline/assay-analyser/generate_curves.py and reference.R. Draft until the independent reviewer signs the fixture file and the method page (Q-19).

Relative IRLS is checked separately against two independent SciPy fits and fixed-weight estimating equations; this checks point estimation, not interval coverage or Prism equivalence. Download the reference with input/script hashes, Python script and pinned requirements. The new response gate has independent exact-null permutation regression tests; old F-gate outcomes are archival.

Unweighted log10 C50 of the synthetic curves

Expected values from R 4.3.3 nls (drc 4.0-0 as a cross-check), recorded 2026-10-06; tolerance within 0.00043 log10 units; 12 of 28 shown, all comparable rows passing. drc-reference.json sha256 27d99000551acaf2…

InputExpectedOursDifferencePass
C01 logC50 (Hill −0.958)−6.038−6.0381.77e-8yes
C02 logC50 (Hill −1.13)−6.555−6.555-6.86e-10yes
C03 logC50 (Hill −1.01)−6.989−6.9893.52e-10yes
C04 logC50 (Hill −0.508)−6.069−6.0691.69e-8yes
C05 logC50 (Hill −0.459)−5.457−5.457-4.10e-8yes
C06 logC50 (Hill −0.489)−6.462−6.4621.11e-8yes
C07 logC50 (Hill −3.06)−5.999−5.9991.19e-9yes
C08 logC50 (Hill −2.71)−5.250−5.2501.83e-9yes
C09 logC50 (Hill −3.21)−7.202−7.2026.15e-10yes
C10 logC50 (Hill −0.961)−6.018−6.0182.60e-8yes
C11 logC50 (Hill −1.16)−6.025−6.0253.52e-9yes
C12 logC50 (Hill −1.06)−5.002−5.002-1.51e-8yes

Unweighted Wald and profile extra-sum-of-squares intervals on log10 C50

Expected values from R 4.3.3 nls (drc 4.0-0 as a cross-check), recorded 2026-10-06; tolerance Wald within 0.0022 log10 units, resolved profile bounds within 0.0043 log10 units; 12 of 56 shown, all comparable rows passing. drc-reference.json sha256 27d99000551acaf2…

InputExpectedOursDifferencePass
C01 Wald−6.089 to −5.987−6.089 to −5.9872.0e-8, 1.5e-8yes
C01 profile extra-sum-of-squares−6.089 to −5.987−6.089 to −5.987-2.4e-8, 5.9e-9yes
C02 Wald−6.659 to −6.452−6.659 to −6.4521.4e-9, -2.8e-9yes
C02 profile extra-sum-of-squares−6.663 to −6.454−6.663 to −6.454-1.1e-8, 9.8e-9yes
C03 Wald−7.011 to −6.968−7.011 to −6.9681.7e-9, -9.6e-10yes
C03 profile extra-sum-of-squares−7.011 to −6.968−7.011 to −6.9682.0e-8, -1.4e-8yes
C04 Wald−6.177 to −5.961−6.177 to −5.9611.1e-8, 2.3e-8yes
C04 profile extra-sum-of-squares−6.183 to −5.963−6.183 to −5.963-2.1e-8, 3.3e-8yes
C05 Wald−5.674 to −5.240−5.674 to −5.240-1.9e-8, -6.3e-8yes
C05 profile extra-sum-of-squares−5.672 to −5.218−5.672 to −5.2181.2e-8, 1.8e-8yes
C06 Wald−6.591 to −6.333−6.591 to −6.3331.8e-8, 4.5e-9yes
C06 profile extra-sum-of-squares−6.588 to −6.328−6.588 to −6.3285.4e-9, -1.5e-8yes

The worked MTT plate

Expected values from R 4.3.3 nls (drc 4.0-0 as a cross-check), recorded 2026-10-06; tolerance within 0.00043 log10 units; Z′ within 1e-9; 4 of 4 shown, all comparable rows passing. mtt-example.json sha256 50029b58c869ecb4…

InputExpectedOursDifferencePass
Compound A logC50−5.666−5.6666.80e-9yes
Compound B logC50−6.735−6.735-7.94e-8yes
Compound C reportarchived unresolved fitnumerically unresolvedyes
MTT plate Z′ (8 vehicle, 4 kill wells)0.782180.78218-2.2e-16yes

Z′ by hand

Expected values from Z′ worked by hand with sample standard deviations, recorded 2026-10-06; tolerance within 1e-9; refusal exact; 4 of 4 shown, all comparable rows passing. zprime-controls.json sha256 dd3f2b587d4585ac…

InputExpectedOursDifferencePass
Z01: excellent separation0.8334510.8334511.1e-16yes
Z02: marginal: below the 0.5 convention0.4348490.4348495.6e-17yes
Z03: overlapping controls: negative Z'−1.3285−1.3285-4.4e-16yes
Z04: one negative-control well: Z' refused (a sample SD needs two)refusedrefusedyes

Nested model comparisons

Expected values from the oracle's hand-worked F, confirmed by drc's anova(), recorded 2026-10-06; tolerance F within 1e-5 relative; 4 of 4 shown, all comparable rows passing. drc-reference.json sha256 27d99000551acaf2…

InputExpectedOursDifferencePass
C01: Hill fixed at -1 (3 free) against Hill free (4 free); true Hill -1F 0.77745, p 3.86e-1F 0.77745, p 3.86e-11.0e-11yes
C07: Hill fixed at -1 against Hill free; true Hill -3F 564.89, p 6.69e-22F 564.89, p 6.69e-228.8e-13yes
C04: Hill fixed at -1 against Hill free; true Hill -0.5F 125.31, p 4.60e-10F 125.31, p 4.60e-102.7e-12yes
C10: Bottom fixed at 0 against Bottom free; true Bottom 20F 57.476, p 4.77e-8F 57.476, p 4.77e-86.7e-14yes

Security overview

Scientific computation runs in your browser, and individual apps need no account. Optional Bench accounts and backups use the site server and configured providers. The privacy statement, the hosts it contacts and the headers its workspace is served with are generated from its registry entry, the same record that sets the browser's policy.

What leaves your browser

What leaves your device. Requests for Drybench Assay's own files (pages, scripts, data files and images) go to the host that serves it. That host keeps standard web-server logs (IP address, user agent, requested address, time). Anything in the address bar before the # (a search, a SMILES, an accession or a step) is sent to this site's host when a page loads or a link is opened; anything after the # is not sent. No external scientific host is contacted for these operations. The app carries no analytics and no error reporting, and loads no font or script from any other origin. Optional Bench sign-in and billing contact the site server and its configured account providers; choosing an account backup uploads the selected Study.

What stays on your device. Your plate data, plate maps, fit settings and results, your preferences, and anything you have not chosen to export, share or back up to your account. They live in this browser's storage on this device.

Where you choose to send data. A link you copy carries what you were looking at (the view and fit settings you chose, never your responses, well assignments or compound names); anyone who receives it can read it, and mail and chat tools may fetch the link to show a preview. A file you export or share goes wherever you send it.

Hosts the app contacts

The app contacts no host but its own.

Headers the workspace is served with

These are the exact header values, generated from the app's registry entry.

Content-Security-Policy
default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; worker-src 'self' blob:; connect-src 'self' blob:; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline'; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'
Referrer-Policy
no-referrer
Permissions-Policy
camera=(), microphone=(), geolocation=(), payment=(), usb=()
X-Content-Type-Options
nosniff
Cross-Origin-Opener-Policy
same-origin

Limits

  1. 1. For research and education only, and not medical advice: a C50 estimated here describes this assay under these conditions and is not a binding constant.
  2. 2. The reported C50 is always the four-parameter logistic fit, with any parameter fixable; no five-parameter dose-response fits (standard curves, ELISA among them, are fitted in the Standard curve analysis). A bell-shaped or biphasic curve can be fitted beside it for one curve, for information: it describes the curve's shape over the tested concentrations, does not show two binding sites, two targets or two mechanisms, does not say why the response turns, and reports no interval.
  3. 3. The app refuses to fit fewer than four distinct concentrations, treats a concentration of zero as a control, and never extrapolates: a C50 the tested concentrations do not bracket is withheld with the reason.
  4. 4. Confidence intervals are profile extra-sum-of-squares by default and Wald as the named alternative, both on log10 C50 and both assuming independent errors of constant variance; relative fits assume SD proportional to response and variance proportional to its square, and have no intervals; a bound not reached within ten log units is not bounded within the search.
  5. 5. The relative C50 is the curve's own midpoint; the absolute C50 is where the curve crosses 50 percent of the controls, needs responses on a percent-of-control scale and two concentrations predicted on each side of 50, and is not determinable otherwise.
  6. 6. Normalisation uses each plate's own control means, and Z′ uses sample standard deviations: Z′ below 0.5 is an observation against Zhang's convention, not a pass mark, and R² is shown but is not a goodness-of-fit gate.
  7. 7. Hill slope comments are observations, not diagnoses.
  8. 8. GR metrics correct for the division rate within the assay and assume exponential growth and a readout proportional to cell number; a drug that changes signal per cell biases them, GR below 0 means net cell loss rather than a mechanism, GR_AOC compares only across the same concentration range, and they are not checked against the GRmetrics package.
  9. 9. Kinetic read blocks are checked on synthetic files only, not on an instrument's own export. A dose-response fit takes one read per well, the last or the one you choose, named in the method block and paragraph; growth, progress-curve and melt analyses take every read of the wells you name. Reads are never interpolated or filled in; a missing or reader-marked read is left out with the reason; a temperature column beside time is kept as metadata, not corrected for; pre-corrected or dual-wavelength values are not recombined.
  10. 10. No ligand efficiency is computed for cell-viability or phenotypic C50s.
  11. 11. A batch takes at most 400 curves; a larger import is split before fitting.
  12. 12. Plate layouts cover the plain grid and other recognised layouts at 96 and 384 wells; header metadata, including any user name, instrument serial or file path, is read past and discarded, and any other layout is read as a table with units in its headers.
  13. 13. Your plate data are not placed in links: an assay is kept in this browser as an object and in the session file you export, and a shared link carries the view and fit settings only.
  14. 14. Literature potencies are links to ChEMBL, never rows in the app or its exports.
  15. 15. No offline use: the pages are served by the site, and nothing is installed.
  16. 16. Version 2 intervals are unavailable for relative IRLS and numerically zero-residual fits. Unweighted intervals condition on normalised observations and omit uncertainty shared through estimated control means. Permutation response evidence assumes exchangeability within plate; technical wells are not biological replicates. Relative IRLS requires positive observed responses and positive fitted predictions throughout; nonpositive responses are refused rather than offset or silently excluded. This is an app domain restriction, not a requirement of all constant-CV estimators.
  17. 17. A compound known only by its label stays in the assay's data; its C50 is written on the target only when the assay names one, and a structure (a SMILES column, or a compound sent from the molecule app) is what lets a value sit on the compound itself.
  18. 18. Reopening an assay under a newer engine version recomputes only on request and shows the differences against the stored results; the stored results stay until you refit.
  19. 19. Reads a plate export or table up to 4.1 MB and opens a project file up to 50.0 MB; a larger file is refused, not cut.
  20. 20. The figure is edited through its settings: text, reference lines, shaded ranges and arrows are anchored to data values, never placed or drawn by hand, at most 50 on a figure; a saved figure style is kept in this browser on this device.
  21. 21. A negative result is a statement about the test, not the compound: no monotonic trend at p < 0.05 within plates; offered only where the design could have reached p < 0.05; no power calculation.

Where it runs and what it sends

  1. Import a plate or a tablePaste or drop the export; the layout it was recognised by is shown and the header, including any user name, is ignored. The worked MTT example opens with one click.
  2. Map the plateSelect wells on the grid or in the well table, assign a compound and a concentration or a control role, fill a dilution series, or place a plan the calculators sent; save the layout as a template.
  3. Fit and readChoose the normalisation, the fixed parameters, the weighting and the interval method; the batch fits in a worker with the progress read out, and each curve shows its C50 with its interval or the reason it is withheld, beside its small multiple and residuals.
  4. Report and reproducePrint the report with its method block, copy the method paragraph, export the results CSV with provenance, the session file, the figures, the CSV for GraphPad Prism (XY table) or the R snippet. Your plate data stay in this browser; no account is needed to analyse and export, and saving needs one.

Built for the keyboard and for screen readers

Everywhere in the app

Show every keyboard shortcut
?
Go to the search field
/
Open the command palette
ControlK
Open Settings
Control,
Close the innermost panel, menu or dialog
Escape

In this app

Go to Import
1
Go to the plate map
2
Go to Fit
3
Go to the report
4
Fit the curves
g
Exclude or include the focused well
e

Single-key shortcuts work outside text fields and can be turned off in Settings.

Commitments

  • Controls work from the keyboard, with focus always visible.
  • One polite live region announces what changes, for screen readers.
  • Reduced motion is honoured, from your system setting or the app's own switch.
  • Works at 375 px wide.

Questions buyers ask

Which confidence interval does it report?

For identifiable unweighted fits with residual degrees of freedom and nonzero variance, a 95 percent profile extra-sum-of-squares interval on log10 C50, with Wald as the named alternative. Relative IRLS intervals are unavailable until validated. Both are back-transformed to concentration; a profile bound not reached within ten log units is not resolved within the search, and an interval wider than two log units flags the value as poorly determined.

Why is a C50 sometimes withheld?

Because the data cannot support it: fewer than four distinct concentrations, insufficient response ordering by the within-plate permutation test, a C50 the tested concentrations do not bracket on both sides, a free plateau with fewer than two concentrations at or beyond its bend point, or an absolute C50 without two concentrations predicted on each side of 50 percent. Each withheld value says which rule and why, and a curve with no finite least-squares minimum (a flat line, a step between two concentrations) is reported as such with no estimates. The app never extrapolates outside the tested range.

Relative or absolute C50: which should I use?

The relative C50 is the curve's own midpoint, the fitted parameter, and is what most papers mean. The absolute C50 is the concentration where the fitted curve crosses 50 percent of the 0 and 100 percent controls, which needs responses normalised to controls and, following Sebaugh, two concentrations predicted on each side of 50. The app shows either, says which it is in the method block, and withholds the absolute value when the curve does not cross 50 in the tested range.

How is the engine checked?

Against R 4.3.3 nls and drc 4.0-0 LL.4 run offline on 33 synthetic curves from a checked-in generator with known parameters, the worked MTT plate, four control sets for Z′ and six nested model comparisons, within tolerances stated absolutely on the logarithm (0.00043 log10 units on the C50, 0.0022 on Wald bounds, 0.0043 on profile bounds). The fixture files, their hashes, the generator and the oracle script are on the verification page. The wording is draft until an independent reviewer signs the fixture file and the method page; agreement with Prism has not been checked.

Is anything sent to a server?

No. The app contacts no host besides its own, and your plate data never travel in a link: an assay is kept in this browser as an object and in the session file you export, and a shared link carries the view and fit settings only. A compound sent to the molecule app goes by a hand-off record in this browser, not through the address. No account is needed to analyse and export; saving needs one.

Can it read my plate reader's file?

It reads a plain grid (a row of column numbers over rows A to H or A to P) and other recognised plate layouts at 96 and 384 wells, detecting the layout from the block's structure and showing what it found, with the header, including any user name, instrument serial or file path, discarded. Anything else is read as a table with units in its column headers. The layouts were built on files the suite generated itself, so a real export that does not read is worth reporting.

Can I use it for dosing, clinical or regulated work?

No. It is research and education software and not medical advice: a C50 estimated here describes this assay under these conditions and is not a binding constant, and nothing here has been validated for clinical or regulated use. A screening core that names the fitter in its own procedure owns that qualification.

Next step

Want this tuned to your assays?

Email m.beale@icloud.com with what you need, or use the contact page.