clophfit.prtecan.titration#

Prtecan/prtecan.py.

Classes#

TitrationConfig

Parameters defining the fitting data with callback support.

BufferFit

Store (robust) linear fit result.

Buffer

Buffer handling for a titration.

TitrationResults

Manage titration results with optional lazy computation.

Titration

Build titrations from grouped Tecanfiles and concentrations or pH values.

TecanConfig

Group tecan cli options.

McmcSpec

A per-well MCMC request, decided by the caller rather than parsed downstream.

Functions#

label_is_uninformative(x, y_masked, y_raw, *, floor, ...)

Whether one label of one well carries no usable titration.

Module Contents#

clophfit.prtecan.titration.label_is_uninformative(x, y_masked, y_raw, *, floor, bg_multiplier, turnover_limit, amplitude=None, plate_amplitude=None, plate_amplitude_ratio=_MIN_PLATE_AMPLITUDE_RATIO, ignore_acid_step=False)#

Whether one label of one well carries no usable titration.

Dimness alone does not condemn a channel. Every well here has a dim 400 nm channel by construction, and a 485 nm channel can sit only a few counts above background and still trace a clean titration - L8 H11 runs 5.0 to -1.2 and pins K to 0.193 pH. What makes a dim channel useless is its curve being noise-shaped: a single-pKa titration is monotone, so an interior turnover is evidence there is no titration to fit.

Parameters:
  • x (ArrayF) – Titrant axis, matching y_raw.

  • y_masked (ArrayF) – Signal after outlier masking, used for the brightness test.

  • y_raw (ArrayF) – Signal before masking, used for the shape test. The mask exists to protect the fit from a bad point, so screening the masked curve would delete the very evidence the screen is looking for: masking L8 E12 removes the spike to 22.8 that proves its channel is noise.

  • floor (float) – Read-noise scatter for this label, i.e. bg_noise.

  • bg_multiplier (float) – Multiples of floor the masked mean must reach.

  • turnover_limit (float | None) – Largest turnover a dim channel may show and still count as informative. None disables the exemption, which is right for label 1: its curve turns over at the acid end in most wells (65% by more than 0.05 of its range, 40% by more than 0.2), so monotonicity says nothing there. That turnover is real but not a 400 nm feature: both channels lose emission at the last acid addition, by an amount that depends on the mutant (arslanbaeva’s scripts/tecan_acid_turnover.py). It shows at 400 nm because that channel is bright in acid; without the last step only 20% of wells turn over by more than 0.05. At 485 nm the same loss steepens a curve that already falls toward acid, so a real titration stays monotone there (0.9% of wells turn over by more than 0.2).

  • amplitude (float | None) – This label’s own swing, for the plate comparison below.

  • plate_amplitude (float | None) – Median swing of this label across the plate. When both are given, a label swinging less than plate_amplitude_ratio of it counts as dim however it compares with the read noise - which is what catches a well reading 1.0 count where the plate median is 93.

  • plate_amplitude_ratio (float) – Fraction of the plate median below which a label is dim.

  • ignore_acid_step (bool) – Leave the most acidic point out of the shape test (the brightness test still sees every point). For label 1 under --acid-scale, whose acid turnover is the loss of emission the fit now models.

Returns:

True when the label carries no usable titration.

Return type:

bool

class clophfit.prtecan.titration.TitrationConfig#

Parameters defining the fitting data with callback support.

acid_scale: bool = False#

Free factor on the most acidic step, shared by both labels, in the global fit.

Both channels lose emission at the last acid addition by a mutant-dependent amount; see clophfit.fitting.core.ACID_SCALE. Applies to the two-label global fits (lm/huber, fixed or gain-calibrated noise), not to the single-label or ODR ones.

noise_alpha: tuple[float, ...] = ()#

Proportional noise coefficients per label.

When provided, adds a proportional term to the error estimate so that high-signal wells are appropriately down-weighted: y_err^2 = gain * signal + bg_err^2 + (alpha * signal)^2

Values typically from MCMC multi-noise shared_noise_params.csv. Empty tuple disables the correction (legacy behaviour).

noise_gain: tuple[float, ...] = ()#

Poisson gain coefficients per label.

Replaces the hardcoded gain=1 in the shot-noise Poisson term: y_err^2 = gain * signal + bg_err^2 + (alpha * signal)^2

Values typically from MCMC multi-noise shared_noise_params.csv. Empty tuple keeps gain=1 (legacy behaviour).

noise_floor: tuple[float, ...] = ()#

Read-noise floor per label, overriding the measured bg_noise.

bg_noise is whatever one plate’s three to six buffer wells happened to scatter by, and it is estimated on far fewer degrees of freedom than a value pooled over a campaign. Supplying the floor lets a calibration fitted across plates be used instead.

Empty tuple keeps bg_noise (legacy behaviour).

noise_floor_ref_gain: tuple[float, ...] = ()#

Reader Gain each noise_floor was quoted at, per label.

Read noise is amplified along with the signal, so a floor measured at one PMT setting means nothing at another until it is moved there. Given a reference, the floor is scaled by 10 ** ((gain - ref) / 34.1) using the plate’s own Gain metadata, which makes one calibration portable across plates that were read at different settings.

0.0 disables scaling for that label, which is the right choice where no Gain dependence was measured. Empty tuple disables it for every label.

mask_outliers: bool = False#

Mask geometric outliers in each well’s curve before fitting. Default is False.

outlier_threshold: float = 0.2#

Threshold for geometric outlier scoring (0-1). Default is 0.2.

set_callback(callback)#

Set the callback to be triggered on parameter change.

Parameters:

callback (collections.abc.Callable[[], None])

Return type:

None

class clophfit.prtecan.titration.BufferFit#

Store (robust) linear fit result.

property empty: bool#

True when all attributes are NaN, emulating DataFrame’s empty behavior.

Return type:

bool

class clophfit.prtecan.titration.Buffer#

Buffer handling for a titration.

Manages background correction and fitting for buffer wells.

property dataframes: dict[str, pandas.DataFrame]#

Buffer dataframes with fit.

Return type:

dict[str, pandas.DataFrame]

property dataframes_nrm: dict[str, pandas.DataFrame]#

Buffer normalized dataframes with fit.

Return type:

dict[str, pandas.DataFrame]

property wells: list[str]#

List of buffer wells.

Return type:

list[str]

property bg: dict[str, clophfit.clophfit_types.ArrayF]#

List of buffer values.

Return type:

dict[str, clophfit.clophfit_types.ArrayF]

property bg_err: dict[str, clophfit.clophfit_types.ArrayF]#

List of buffer SEM values.

Return type:

dict[str, clophfit.clophfit_types.ArrayF]

property bg_noise: dict[str, float]#

Intrinsic well noise (RMSE/pooled SD) values.

Return type:

dict[str, float]

property bg_read_noise: dict[str, float]#

Buffer measurement scatter, with fixed per-well offsets removed.

bg_noise pools the scatter of the buffer wells about the background trend, and on these plates that pooled number is dominated by fixed positional differences between buffer wells rather than by measurement noise – it runs 1.1x to 6.6x this value across the campaign. A positional offset is absorbed by a well’s own plateaus in any per-well fit, so it does not show up as point-to-point residual scatter and does not belong in a noise-model floor. This is the part that does.

Returns:

Per-label RMS interaction of the buffer readings, 0.0 for a label with no buffer wells.

Return type:

dict[str, float]

plot(*, nrm=False, title=None)#

Plot buffers of all labelblocksgroups.

Parameters:
  • nrm (bool)

  • title (str | None)

Return type:

seaborn.FacetGrid

class clophfit.prtecan.titration.TitrationResults#

Bases: clophfit.fitting.data_structures.ResidualsMixin

Manage titration results with optional lazy computation.

Provide either the small scheme + fit_keys directly, or a titration keyword to snapshot both from a Titration:

TitrationResults(scheme=tit.scheme, fit_keys=tit.fit_keys, results=res)
TitrationResults(results=res, titration=tit)  # equivalent, more concise

titration is an InitVar: only scheme and fit_keys are copied off it, so the (potentially large) raw plate data is never retained.

noise_model carries the calibrated per-label noise model when the fit produced one (fgls_fit_plate); it is None for plain fit_plate.

residual_table(*, binding_function=None, robust=None, student_t_nu=None, outlier_threshold=3.0)#

Compute the canonical plate-wide residual table.

Delegates to each well’s own FitResult.residual_table(), so robustness is auto-detected per well from that well’s own mini (its lmfit Minimizer, ODR output, or PyMC trace). A plate fitted with method="mcmc" therefore standardizes each well against its own trace, instead of being forced to Normal standardization.

Parameters:
  • binding_function (Callable[..., object] | None) – Model evaluated for yhat; defaults to binding_1site. Forwarded unchanged to each well.

  • robust (bool | None) – Force the Student-t standardization of std_res. None auto-detects per well from that well’s trace.

  • student_t_nu (float | None) – Student-t degrees of freedom (None uses detected/default).

  • outlier_threshold (float) – Threshold for the is_residual_outlier flag.

Returns:

The canonical residual table (see residuals), built by concatenating each well’s own table. Wells whose fit failed carry no dataset or result and are skipped, so the table may cover fewer wells than fit_keys.

Return type:

pd.DataFrame

property dataframe: pandas.DataFrame#

Convert FitResult dictionary to a DataFrame.

Return type:

pandas.DataFrame

compute_all()#

Compute results for all keys.

Return type:

None

n_sd(par='K', expected_sd=0.15)#

Compute median of K.

Parameters:
  • par (str)

  • expected_sd (float)

Return type:

float

static all_computed()#

Check if all keys have been computed.

Return type:

bool

export_pngs(folder)#

Export all fit result plots as PNG files.

Parameters:

folder (str | pathlib.Path)

Return type:

None

export_data(folder)#

Export all datasets as CSV files.

Parameters:

folder (str | pathlib.Path)

Return type:

None

plot_k(xlim=None, title='', exclude=None)#

Plot K values as stripplot.

Wells fitted on fewer labels than the rest are marked with a trailing *, and the title says how many there are. Their K rests on 3 parameters over 7 points rather than 6 over 14 and loses the ratiometric cancellation, so it is systematically less certain than its neighbours - which a bare stripplot would otherwise hide.

Parameters:
  • xlim (tuple[float, float] | None) – Range.

  • title (str) – To name the plot.

  • exclude (Mapping[str, Collection[str]] | None) – Wells to leave off the plot, keyed by the reason (e.g. "undetermined", "non-binding"); the title says how many were left off for each.

Returns:

The figure.

Return type:

figure.Figure

class clophfit.prtecan.titration.Titration#

Bases: clophfit.prtecan.models.TecanfilesGroup

Build titrations from grouped Tecanfiles and concentrations or pH values.

Parameters:
  • tecanfiles (list[Tecanfile]) – List of Tecanfiles.

  • x (ArrayF) – Concentration or pH values.

  • is_ph

  • x_err

Raises:

ValueError – For unexpected file format, e.g. header names.

is_ph: bool = False#

Indicate if x values represent pH.

x_err: clophfit.clophfit_types.ArrayF#

Uncertainties for x values (default is empty array).

buffer: Buffer#

Buffer wells data and fit results. Set during initialization.

property fit_keys: set[str]#

Set of wells to be fitted.

Return type:

set[str]

detect_and_discard_bad_wells(*, outlier_threshold=0.2, bg_multiplier=3.0, max_k_stderr=None, monotone_turnover=_MONOTONE_TURNOVER)#

Detect and discard bad wells from masked per-label signal quality.

By default, each well is converted to a per-label dataset with _create_ds(), masked with apply_outlier_mask(), and then discarded when the mean masked signal of any label falls below a background-derived floor. The floor uses bg_err when available and falls back to bg_noise.

The signal test asks two things of a dim label, not one: that it clears the background, and failing that, that its curve still moves further than the read noise and moves monotonically. The second question is what separates a faint titration from a drift - L8 H11 swings six noise widths and pins K to 0.193 pH, while L2 C05 swings 1.7 and puts K at 7.138 +- 254 pH. A fit-quality test still runs alongside, for a well that is bright and uninformative, which no signal test can reach.

The smoothness, roughness and trendline criteria that used to sit here were removed. They were disabled by default, no caller ever passed them, and at plausible thresholds (0.5/0.5/3.0) they discarded 90 of 90 wells on L2 - not a criterion, a bug in waiting.

Parameters:
  • outlier_threshold (float | None) – Threshold passed to apply_outlier_mask() before computing per-label summary statistics. If None, no masking is applied.

  • bg_multiplier (float | None) – Discard a well when any masked per-label mean signal is below bg_multiplier * mean(background_floor). If None, this check is disabled.

  • max_k_stderr (float | None) – Discard a well whose fitted K has a standard error above this, or a non-finite one. None uses the titration’s own x span, which is the scale-free form of the rule: a well whose midpoint cannot be located inside the window actually titrated carries no information about K, however bright it is. Pass math.inf to disable. pH only: a chloride well whose Kd cannot be located may not bind, and is fitted and reported as such rather than discarded.

  • monotone_turnover (float | None) – Largest turnover a dim label past the first may show and still count as informative. None disables the exemption, restoring the rule that any dim label fails.

Returns:

Newly discarded well keys.

Return type:

list[str]

clear_all_data_results()#

Clear fit keys, data, results and bg when buffer or scheme properties change.

Return type:

None

property params: TitrationConfig#

The datafit parameters.

Return type:

TitrationConfig

property bg: dict[str, clophfit.clophfit_types.ArrayF]#

List of buffer values.

Return type:

dict[str, clophfit.clophfit_types.ArrayF]

property bg_err: dict[str, clophfit.clophfit_types.ArrayF]#

List of buffer SEM values.

Return type:

dict[str, clophfit.clophfit_types.ArrayF]

property bg_noise: dict[str, float]#

Intrinsic well noise (RMSE/pooled SD) values.

Return type:

dict[str, float]

property bg_read_noise: dict[str, float]#

Buffer measurement scatter, with fixed per-well offsets removed.

See Buffer.bg_read_noise. Reported alongside bg_noise rather than replacing it: bg_noise is what y_err and the classical --plate-noise fixed path are built on and calibrated against, while this is the candidate for a structured-noise floor.

Returns:

Per-label measurement scatter of the buffer wells.

Return type:

dict[str, float]

property sigma_floor: dict[str, float]#

The read-noise floor per label that the error models actually use.

params.noise_floor when supplied, otherwise the measured bg_read_noise. A supplied floor carrying a non-zero params.noise_floor_ref_gain is scaled from that reference to this plate’s own reader Gain, so one campaign-wide calibration serves plates read at different settings; a reference of 0.0 leaves the value alone, which is right for a label whose floor showed no Gain dependence.

Returns:

Per-label floor. Falls back to the measured value for any label the override does not cover.

Return type:

dict[str, float]

classmethod fromlistfile(list_file, *, is_ph, base_dir=None)#

Build Titration from a list[.pH|.Cl] file.

Parameters:
  • list_file (Path | str) – Path to the list file containing [filenames x x_err].

  • is_ph (bool) – Whether x values represent pH (True) or concentrations (False).

  • base_dir (Path | str | None) – Directory holding the Tecan files. Relative filenames in the list file are resolved against it; defaults to the list file’s own directory. Use it when list files and .xls files are kept in separate trees (e.g. data/processed/ and data/raw/).

Returns:

The constructed Titration object.

Return type:

Titration

property additions: list[float] | None#

List of initial volume followed by additions.

Return type:

list[float] | None

load_additions(additions_file)#

Load additions from file.

Reads a CSV file with a single column ‘add’ containing addition volumes, and updates the Titration’s additions property.

Parameters:

additions_file (Path) – Path to the additions CSV file.

Return type:

None

property data: dict[str, dict[str, clophfit.clophfit_types.ArrayF]]#

Buffer subtracted and corrected for dilution data.

Return type:

dict[str, dict[str, clophfit.clophfit_types.ArrayF]]

property scheme: clophfit.prtecan.models.PlateScheme#

Scheme for known samples like {‘buffer’, [‘H12’, ‘H01’], ‘ctrl’…}.

Return type:

clophfit.prtecan.models.PlateScheme

load_scheme(schemefile)#

Load scheme from file and set buffer wells.

Reads a scheme file to define buffer wells, known samples, and control wells, then updates the Titration’s scheme and buffer wells.

Parameters:

schemefile (Path) – Path to the scheme CSV file.

Return type:

None

create_ds(key, label)#

Create a dataset for the given key.

Parameters:
  • key (str)

  • label (str)

Return type:

clophfit.fitting.data_structures.Dataset

property excluded_labels: dict[str, set[str]]#

Labels dropped per well, keyed by well.

Returns:

Well to the set of its excluded labels. Wells with every label excluded are discarded outright instead of appearing here.

Return type:

dict[str, set[str]]

exclude_label(key, label)#

Drop one label of one well from the global fit.

The 400 nm channel is dim by construction, so a well can fail a background test on one label while the other is perfectly usable. Discarding the well would throw away a good titration; dropping the label keeps it, at the cost of a less certain K - which is why the results table records how many labels each well was fitted on.

Parameters:
  • key (str) – Well identifier.

  • label (str) – Label to drop for that well. Dropping every label discards the well.

Return type:

None

create_global_ds(key)#

Create a global dataset for the given key.

Applies mask_outliers like create_dataset_dict(). It used not to, and since ppr tecan builds its global datasets here, the flag never reached the global, ODR, MCMC or plate fits - accepted, echoed back in the run configuration, and silently confined to the per-label path. Two builders of the same dataset must not disagree about what a flag means.

Parameters:

key (str) – Well identifier.

Returns:

The well’s labels, masked and weighted as configured.

Return type:

Dataset

create_dataset_dict(label=None)#

Create a dictionary of datasets for all fit_keys, optionally masking outliers.

Parameters:

label (str | None) – Specific label to extract. If None, creates global datasets containing all labels. Default is None.

Returns:

A dictionary mapping well keys to their corresponding Datasets.

Return type:

dict[str, Dataset]

fit_plate(datasets=None, method='', *, label=None, **kwargs)#

Run a single-pass fit on an entire plate of datasets.

Parameters:
  • datasets (dict[str, Dataset] | None) – Mapping of well keys (e.g. ‘A01’) to Dataset objects. When None, datasets are built with create_dataset_dict(), which also applies outlier masking when params.mask_outliers is set.

  • method (str) – The fitting method: ‘lm’ (default), ‘huber’, ‘odr’, or ‘mcmc’. Other methods supported by clophfit.fitting.core.fit_binding_glob() may also be used.

  • label (str | None) – Build per-label datasets for this label instead of global ones. Only valid when datasets is None.

  • **kwargs (Any) – Additional keyword arguments passed to the fitting function.

Returns:

Plate results carrying this titration’s scheme and fit_keys.

Return type:

TitrationResults

Raises:

ValueError – If both datasets and label are given.

fgls_fit_plate(datasets=None, *, label=None, sigma_floor=None, first_pass_method='huber', second_pass_method='lm', max_iter=3, tol=0.001)#

Run iterative Feasible Generalized Least Squares (FGLS) on the plate.

Fits every well with first_pass_method using the existing y_errc, calibrates a per-label noise model from the plate-wide residuals with the floor anchored to sigma_floor, re-applies the calibrated weights and re-fits with second_pass_method, iterating until gain and alpha converge or max_iter is reached.

Parameters:
  • datasets (dict[str, Dataset] | None) – Mapping of well keys to Dataset objects. When None, datasets are built with create_dataset_dict().

  • label (str | None) – Build per-label datasets for this label instead of global ones. Only valid when datasets is None.

  • sigma_floor (dict[str, float] | None) – Known read-noise floor per label. Defaults to sigma_floor.

  • first_pass_method (str) – Method for the first-pass fit.

  • second_pass_method (str) – Method for subsequent passes.

  • max_iter (int) – Maximum FGLS iterations.

  • tol (float) – Relative tolerance for gain/alpha convergence.

Returns:

Plate results carrying this titration’s scheme and fit_keys, with noise_model set to the converged (or last) calibration.

Return type:

TitrationResults

Raises:

ValueError – If both datasets and label are given.

plot_temperature(title='')#

Plot temperatures of all labelblocksgroups.

Creates a line plot showing measured temperatures versus concentration/pH values, with statistics overlays.

Parameters:

title (str) – Additional title text to append to the plot.

Returns:

The matplotlib Figure object containing the plot.

Return type:

figure.Figure

class clophfit.prtecan.titration.TecanConfig#

Group tecan cli options.

detect_bad: bool = True#

Run bad-well detection before fitting, and act on undetermined K after it.

Before: discard unusable wells (Titration.detect_and_discard_bad_wells). After: leave wells whose fitted K is undetermined (see max_k_se) - and, for chloride, wells that do not bind - off the K plot, mark their per-well figure, and list them in discarded_wells.txt. The undetermined (and no_binding) columns of each ffit*.csv are written either way.

ctr_free_k: bool = False#

Give every well its own K in –plate-fit, instead of pooling controls.

Mirrors what the flag already did for --mcmc multi; without it the two fitters answer different questions from the same command line.

plate_screen_z: float | None = None#

Drop points whose calibrated |z| exceeds this, then refit, for –plate-fit.

None fits once. 3.0 is the value measured to help (sum_log -5.92 against -5.49 unscreened, nine plates of eleven improved); 2.5 turns harmful, so this is not a knob to sweep casually.

plate_screen_frac: float | None = None#

Also drop 400 nm points deviating fractionally by more than this.

A z-score fails at both ends of a titration: sigma tracks the signal while model error tracks the curve, so a 5% miss at the dim end reads as 3.6 sigma while a 36% miss at the bright end reads as 2.7. |y - yhat| / yhat separates reviewer keeps (0.097-0.111) from discards (0.126 up) over three plates. Points the 485 nm channel moves with are spared, since a shared multiplicative shift is what the ratiometric measurement cancels. None leaves the z-screen alone.

plate_noise: str = 'fixed'#

How --plate-fit weights its points.

"fixed" uses y_err as built - the bg_noise floor, plus any --noise-gain/--noise-alpha supplied. "calibrated" estimates gain and alpha per label from the fit’s own residuals and refits under them. Calibration describes the residuals better and fits K worse, so it is not the default; see fit_plate_lm. It sets the weights K is fitted with whether or not plate_screen_z is given. "gain" and "floor-gain" weight by floor^2 + gain * yhat with alpha 0, the gain (and the floor) calibrated from dof-corrected residuals between refits; see calibrate_plate_lm().

plate_screen_noise: str = 'calibrated'#

The ruler --plate-screen-z judges points on.

"calibrated" (the default) calibrates gain and alpha from the screening pass’s own residuals so bright and dim points are judged on a comparable scale; "fixed" judges on the weights as built. Independent of plate_noise: before the two were separate options, plate_noise meant the fit’s weights without a screen but not after one.

max_k_se: float = 0.3#

Largest standard error, in pH, at which a fitted pKa counts as determined.

pH is an interval scale, so the cut is absolute; a ratio sK / K is sK / 7 here and never fires. 0.30 is three times the 0.10 pH ROPE and four times the 0.074 pH replicate repeatability; on eleven library plates it catches 32 of 875 library wells and none of 123 controls. Chloride ignores it: Kd is a ratio scale, undetermined when sKd > Kd, and a well whose 94% lower bound is above the highest concentration does not bind.

fit_noise: str = 'fixed'#

How the per-well global lm/huber fit weights its points.

"fixed" uses y_err as built. "gain" and "floor-gain" fit every well, pool all wells’ dof-corrected residuals into one gain per label (and with "floor-gain" one floor), and refit until it settles; see calibrate_single_well().

class clophfit.prtecan.titration.McmcSpec#

A per-well MCMC request, decided by the caller rather than parsed downstream.

Parameters:
  • model (Literal["single", "single-refit", "multi"]) – Which fit to run. "single" samples each well once; "single-refit" runs the robust screening pass then refits; "multi" fits every well jointly with control K shared across each control group.

  • sampler (SamplerConfig) – NUTS controls forwarded to pm.sample.

  • structured_noise (bool) – Build the physical floor + gain * y + (alpha * y) ** 2 observation noise instead of scaling y_err by a learned ye_mag multiplier.

  • floor_mode (Literal["centered", "fixed"] | None) – Override the mode for the floor alone; None follows noise_mode.

  • gain_mode (Literal["centered", "fixed"] | None) – Override the mode for gain alone; None follows noise_mode.

  • alpha_mode (Literal["centered", "fixed"] | None) – Override the mode for alpha alone; None follows noise_mode.

  • noise_mode (Literal["centered", "fixed"]) – How a supplied gain/alpha hint is treated when structured_noise is set: centred on (a hint the posterior may leave) or pinned to it. A parameter with no supplied value is always free.

  • per_well_ye_mags (bool | None) – Whether the ye_mag multiplier is per well rather than per label. None leaves the library to resolve it from the noise family, which couples the two; pass a bool to keep them independent. Only meaningful for model="multi", since a single-well fit has one well.

  • ye_mag_parameterization (Literal["centered", "hierarchical", "separable", "separable_step"]) – How per-well ye_mags are structured: independent per label ("centered"), a shared well factor with per-label deviations ("hierarchical"), or a per-label level plus one shared well factor ("separable").

  • robust (RobustConfig) – Likelihood family. The default is a plain Normal; a Student-t with nu=3 is the arm that scored best on this campaign’s plates.

  • ctr_free_k (bool) – Give every well its own K instead of pooling each control group onto a shared one. Only meaningful for model="multi". Pooling buys no accuracy at the construct level and makes the stated interval too narrow, and the library wells have no group to pool with, so free K is the setting that matches what a plate is fitted for.

  • x_error_model (Literal["deterministic", "per_well"]) – The latent pH axis of model="multi". "deterministic" is one pipetting walk shared by every well; "per_well" gives each well its own walk, with step SDs split from the measured pH errors into read noise plus accumulated pipetting variance. pH is measured in a few wells and their spread grows along a titration, so an unmeasured well’s pH is uncertain in a way only the per-well axis carries into its K: on eleven plates it took free-K single-well bulk z-SD from ~1.65 to ~0.95 at unchanged accuracy. The default keeps the historical shared axis.

  • x_start_between_sigma (float | None) – For x_error_model="per_well", the prior SD of each well’s offset at the first step. A per-well offset is degenerate with that well’s K, so this SD passes straight into K’s interval; set it to the measured well-to-well spread at the first step rather than a round number. None keeps the library default.

  • learn_x_start_between (bool) – For x_error_model="per_well", estimate the between-well spread of the first-step offset instead of pinning it at x_start_between_sigma, which then becomes its prior scale.

  • ctr_sigma_w_prior (float | None) – Prior scale, in pH, of the spread of a control group’s replicates around the group’s own K (K_sigma_w). Given, each replicate keeps its own K a learned distance from the group’s: the middle ground between one shared K and a free one. None (default) leaves the model as it was.

noise_ye_mag: bool = False#

Learn a ye_mag multiplier on top of a structured noise model.

A structured model builds sigma from floor, gain and alpha and, by default, has no overall multiplier: the terms themselves carry the level. With this set, sigma is also scaled by a learned ye_mag - per label, or per well when per_well_ye_mags is set - which lets a calibrated shape keep its shape while the data set the level. Meaningless without structured_noise.

acid_scale: bool = False#

Per-well factor on the most acidic step, shared by labels (model="multi" only).