PyBNF constraint specification (pybnf.constraint)¶
Classes for defining various constraints that can be applied to the fitting run. Used when incorporating qualitative data into the fit
- class pybnf.constraint.AlwaysConstraint(quant1, sign, quant2, base_model, base_suffix, weight, altpenalty=None, minpenalty=0.0, pmin=None, pmax=None, tolerance=None, scale=None)[source]¶
- class pybnf.constraint.AtConstraint(quant1, sign, quant2, base_model, base_suffix, weight, atvar, atval, altpenalty=None, minpenalty=0.0, repeat=False, before=False, pmin=None, pmax=None, tolerance=None, scale=None)[source]¶
- class pybnf.constraint.BetweenConstraint(quant1, sign, quant2, base_model, base_suffix, weight, startvar, startval, endvar, endval, altpenalty=None, minpenalty=0.0, pmin=None, pmax=None, tolerance=None, once=False, scale=None)[source]¶
- class pybnf.constraint.Constraint(quant1, sign, quant2, base_model, base_suffix, weight, altpenalty=None, minpenalty=0.0, pmin=None, pmax=None, tolerance=None, scale=None)[source]¶
Abstract class representing an optimization constraint with a penalty for violating the constraint
- bind_scale_param(name)[source]¶
Tie this constraint’s scale (the logit
sor probitsigma) to an estimated free parametername, so a fit can infer it jointly with the model parameters. Only the likelihood families carry a scale: a static (hinge) constraint has no scale to estimate, so binding one is an error. The authored literal scale is kept as the fallback/initial value (used when no pset is supplied, e.g. a bare diagnostic penalty()).
- coerce_penalty_model(target)[source]¶
Override this constraint’s penalty family to
targetin {‘hinge’, ‘probit’, ‘logit’}, deriving the target’s scale from whatever was authored via the scale-matching table in_intrinsic_scale()(logits<-> hingeweight = 1/s<-> probittolerance = 1.6 s).'auto'/Noneis a no-op. This is the mechanism behind thequalitative_lossconfig key – a benchmarking convenience so one.propset runs under all three losses without re-authoring. Authoredpmin/pmaxlabel smoothing is preserved where the target supports it; a probit target with no authored smoothing gets the unclippedpmin=0, pmax=1.
- find_keys(sim_data_dict)[source]¶
Function to be called on the first evaluation of the penalty. Read through the data dictionary and figure out what keys I need to use to access each relevant variable.
This needs to be done kind of by brute force, because we were never told the model file to use, and it’s even possible the input is poorly defined by referring to a suffix that exists in multiple models.
Subclasses that introduce additional “at”/”between” variables override
_find_extra_keys()to resolve those; the common quant/alt key resolution lives here so it isn’t duplicated per subclass. :param sim_data_dict: :return:
- get_difference(sim_data_dict, imin, imax, once=False, require_length=None, imin2=None, imax2=None)[source]¶
Helper function for calculating the penalty, that can be called from the subclasses. Calculates the difference between the two sides of the inequality. A negative value means the inequality is satisfied. Considers the worst case over the specified interval, or the best case if once=True.
The result can be used to calculate the penalty using a static penalty model or a likelihood model.
- Parameters:
sim_data_dict – The dictionary of data objects
imin – First index at which to check the constraint
imax – Last index at which to check the constraint (exclusive)
once – If true, enforce that the constraint holds once at some point during the time interval
require_length – If set to an integer, raise an error if the length of the selected data column(s) is not equal to that value. (Used to check that “at” and “between” constraints are not encountering an unsupported case)
imin2 – If specified, use this different index for quantity 2
imax2 – If specified, use this different index for quantity 2
- Returns:
- get_key(q, sim_data_dict)[source]¶
Converts a string from a constraint file (‘Observable’ or ‘suffix.observable’) into a tuple of the 3 keys you would need to index into the sim_data_dict to get the right column of data.
- Parameters:
q
- Returns:
- get_log_likelihood(sim_data_dict, imin, imax, once=False, require_length=None, imin2=None, imax2=None, pset_values=None)[source]¶
Helper function for calculating the negative log likelihood of constraint satisfaction given the parameters, i.e. a likelihood-based penalty function. The likelihood is calculated with a Gaussian CDF defined in terms of this constraint’s confidence and tolerance (or an estimated scale parameter resolved from
pset_values)- Parameters:
sim_data_dict – The dictionary of data objects
imin – First index at which to check the constraint
imax – Last index at which to check the constraint (exclusive)
once – If true, enforce that the constraint holds once at some point during the time interval
require_length – If set to an integer, raise an error if the length of the selected data column(s) is not equal to that value. (Used to check that “at” and “between” constraints are not encountering an unsupported case)
imin2 – If specified, use this different index for quantity 2
imax2 – If specified, use this different index for quantity 2
- Returns:
- get_log_likelihood_logit(sim_data_dict, imin, imax, once=False, require_length=None, imin2=None, imax2=None, pset_values=None)[source]¶
Negative log likelihood of constraint satisfaction under the logit (Miller et al. 2025) noise model: the comparison outcome is Bernoulli with P(holds) = sigma(-difference/s), so the single-sided BPSL penalty collapses to the softplus
F(difference; s) = ln(1 + e^{difference/s}) = softplus(difference/s).
differenceis the signed constraint margin (negative == satisfied,get_difference()). Ass -> 0withweight = 1/sthis is the 2018 hinge (the large-margin asymptote); largersis a softer penalty.np.logaddexp(0, x)evaluates the softplus without overflow. If optionalpmin/pmaxwere supplied (label smoothing, for probit parity), the probability is clipped topmin + (pmax-pmin)*sigma(-difference/s)before the-log– default off (faithful to the arXiv derivation).- Parameters:
sim_data_dict – The dictionary of data objects
imin – First index at which to check the constraint
imax – Last index at which to check the constraint (exclusive)
once – If true, enforce that the constraint holds once at some point during the interval
require_length – If set, raise if the selected data column length differs (unsupported case)
imin2 – If specified, use this different index for quantity 2
imax2 – If specified, use this different index for quantity 2
- get_penalty(sim_data_dict, imin, imax, once=False, require_length=None, imin2=None, imax2=None, pset_values=None)[source]¶
Helper function for calculating the penalty, that can be called from the subclasses. Chooses to call either get_static_penalty or get_log_likelihood depending on the penalty model used for this constraint.
pset_values(the objective’s live{name: value}map) lets a likelihood constraint resolve an estimated scale parameter.
- get_penalty_gradient(sim_data_dict, raw_sens, index, n_param, imin, imax, once=False, require_length=None, imin2=None, imax2=None, pset_values=None)[source]¶
The gradient of this interval’s penalty w.r.t. the free parameters (#456/#385) – the constraint counterpart of the objective’s per-point gradient. Dispatches to the static or likelihood model, mirroring
get_penalty(). Both reduce tolocal penalty slope * d(readout)/d thetaat the interval’s achieving point: the penalty is a piecewise (static) or Gaussian-CDF (likelihood) function of the at-/between-time readoutq1 - q2, so its gradient is that readout’s forward sensitivity times the model’s local slope.When this constraint’s scale is tied to an estimated free parameter (
scale_param), a closed-formd(penalty)/d(scale)term is added to that parameter’s column – a scalar contribution needing no forward sensitivity, exactly like the estimated-noised loss/d sigmacolumn.
- get_static_penalty(sim_data_dict, imin, imax, once=False, require_length=None, imin2=None, imax2=None)[source]¶
Helper function for calculating the static penalty, that can be called from the subclasses.
- Parameters:
sim_data_dict – The dictionary of data objects
imin – First index at which to check the constraint
imax – Last index at which to check the constraint (exclusive)
once – If true, enforce that the constraint holds once at some point during the time interval
require_length – If set to an integer, raise an error if the length of the selected data column(s) is not equal to that value. (Used to check that “at” and “between” constraints are not encountering an unsupported case)
imin2 – If specified, use this different index for quantity 2
imax2 – If specified, use this different index for quantity 2
- Returns:
- index(sim_data_dict, keys)[source]¶
Shortcut function for applying all 3 indices to the data object :return:
- penalty(sim_data_dict, pset_values=None)[source]¶
penalty function for violating the constraint. Returns 0 if constraint is satisfied, or a positive value if the constraint is violated.
Shared across subclasses: each subclass declares the enforcement interval(s) it checks via
_penalty_intervals()(its “at” / “between” / “always” / “once” logic), and the penalty is the sum ofget_penalty()over them (the static or likelihood model). Factored from the per-subclass inline loops sopenalty_gradient()differentiates exactly the same intervals (#456/#385).- Parameters:
sim_data_dict (dict) – Dictionary of the form {modelname: {suffix1: Data1}} containing the simulated data objects
pset_values – The objective’s live
{name: value}map, so a likelihood constraint whose scale is tied to an estimated free parameter reads its current value.None(a bare diagnostic call) falls back to the authored literal scale.
- penalty_gradient(sim_data_dict, raw_sens, index, n_param, pset_values=None)[source]¶
The gradient of this constraint’s total penalty w.r.t. the free parameters (layer I, #456/#385) –
raw_sens(model, suffix, observable, row)supplies each readout’s native- space forward sensitivity row (the #447 tensor, factor-folded per #448 routing),indexmaps a free-parameter name to its column. Sumsget_penalty_gradient()over the same enforcement intervalspenalty()sumsget_penalty()over, so the gradient differentiates exactly the penalty PyBNF scores.Lives on the scalar-gradient path only: a penalty is a piecewise / CDF function, not a sum of squares, so (like an estimated noise normalizer, #451) it cannot enter the residual- Jacobian – a fit with active constraints is not
least_squares_exact. The static model is non-smooth at the constraint boundary and at themin_penaltyfloor; the subgradient 0 is taken there (documented like the Laplace kink, #454).pset_valuescarries the live parameter values so a constraint with an estimated scale contributes itsd(penalty)/d(scale)column.
- class pybnf.constraint.ConstraintSet(base_model, base_suffix)[source]¶
Represents the set of all constraints provided in one prop file
- load_constraint_file(filename, scale=1.0, qualitative_loss='auto', qualitative_scale=None)[source]¶
Parse the constraint file filename and load them all into my constraint list :param filename: Path of constraint file :param scale: Factor by which we multiply all constraint weights :param qualitative_loss: One of ‘auto’, ‘hinge’, ‘probit’, ‘logit’. ‘auto’ (default) keeps each constraint’s authored penalty family (the keyword-driven per-constraint selection); any other value coerces every constraint to that family via
Constraint.coerce_penalty_model()– a benchmarking convenience. :param qualitative_scale: If set, the name of a free parameter every constraint’s scale is tied to (Constraint.bind_scale_param()), so a fit estimates the qualitative scale jointly. Applied after anyqualitative_losscoercion.
- number_failed(sim_data_dict)[source]¶
Return the number of failed (unsatisfied) constraints, i.e. those with a nonzero penalty. (Matches the method name; the body counts 1 per nonzero penalty.)
- Parameters:
sim_data_dict
- Returns:
- output_itemized_eval(sim_data_dict, output_dir)[source]¶
Output a text file in which we evaluate the given simulation data: each line of the text file contains the penalty associated with the corresponding line in the constraint file.
- Parameters:
sim_data_dict
output_dir – Directory where the file should be saved
- Returns:
- total_penalty(sim_data_dict, pset_values=None)[source]¶
Evaluate the sim_data_dict against all constraints, and return the total penalty
- Parameters:
sim_data_dict – Dictionary of the form {modelname: {suffix1: Data1}} containing the simulated data objects
pset_values – The objective’s live
{name: value}map, threaded so a constraint with an estimated scale parameter reads its current value;Noneuses the literal.
- Returns: