fix(best-fit-confirmation): rank a candidate on all of its replicate runs, so one that fails most of them is not pinned as the run's best fit (#720) - #740
Merged
Conversation
…runs, so one that fails most of them is not pinned as the run's best fit (#720) The confirmation stage exists to stop a stochastic fit reporting whichever parameter set got a lucky simulation. It runs the search's top best_fit_candidates parameter sets best_fit_replicates more times each and reports the one with the best average. A replicate that fails, or that returns a non-finite objective value, produces no number to average, and mean_objective averaged only the runs that did -- a survivor-only mean over a sample size that varied per candidate from 1 to best_fit_replicates. The failure count travelled on the row and was printed in a column, but it entered no comparison: ranked keyed on that mean alone, and winner disqualified a candidate only when every one of its runs had failed. So a parameter set that failed nine runs of ten and survived one was scored on a single simulation again -- the max-of-lucky-draws estimate, now with the unlucky draws deleted rather than averaged in -- and it beat a parameter set that returned a slightly worse value in ten runs of ten. Because _emit_best_fit_confirmation pins the winner on the trajectory by design, the saved simulations, the best-fit BNGL, the information criteria, a refine's start point and a bootstrap replicate's answer then all described the failure-prone set. A candidate is now confirmed, and eligible to win, only when more than half of the runs that came back produced a usable value. Half is not a round number picked for convenience: a failure is known to be worse than every finite value, so that is exactly the condition for the middle of all of a candidate's runs to be a real number. Above the bar the survivor mean averages most of what happened; below it, it describes the minority that worked. Unconfirmed candidates are ranked below every confirmed one whatever their averages say, and when none clears the bar the stage pins nothing and the search's own pick stands rather than the best of a bad lot. Nothing is hidden. mean_objective still reports the survivor average, the table gains a confirmed column and an unconfirmed line naming the demoted candidates, and winner_runs and winner_failed put the winner's sample size in the file. The stage was also silent at the point of decision, which is the half a reader would have caught: the console said nothing about failures, and with one survivor standard_error is None, so the documented "raise best_fit_replicates if the standard errors overlap" guidance could not flag it either. The console now reports the winner's failed runs and how many candidates were not confirmed, and where it used to assert flatly that "the one the search liked best does worse when it is run again" -- the opposite of the truth when that candidate had the better average and lost on reliability -- it says which of the two things actually happened. Rejected alternatives are recorded in ADR-0146: counting any failure as infinite (too harsh for a transient cluster timeout, and it contradicts a behaviour pinned since #659), ranking by failure count first (lexicographic, so one failure would outweigh any objective difference), a multiplicative penalty (PyBNF objective values are not sign-constrained), a configuration key, and ranking on the censored median itself, which is the estimator the argument points at but would change the reported value of every stochastic fit including those with no failures at all. Signed-off-by: Bill Hlavacek <bill.hlavacek@gmail.com>
…s false and what the report change touches The ADR and the changelog both said the old console sentence "the one the search liked best does worse when it is run again" was "the opposite of the truth" in the issue's own scenario. It is not: there the search's top pick did have the worse average. The sentence is false in the mirror case, where the search's top pick is the unreliable candidate and a reliable one displaces it, which is the case the new test pins. Say that instead. The ADR's status line also said the report changes only for a fit in which a candidate failed a run. The confirmed column and the winner_runs / winner_failed lines are written on every fit that runs the stage; only the reported parameter set is conditional. Signed-off-by: Bill Hlavacek <bill.hlavacek@gmail.com>
Collaborator
Author
|
Verification update: with |
This was referenced Sep 17, 2026
wshlavacek
added a commit
that referenced
this pull request
Sep 17, 2026
…t were run beside how many produced a usable log-likelihood (#741) (#742) A simulation that fails, that scores nothing, or that scores a different number of points from the rest is left out of the mean behind AIC, BIC and AICc, and replicated_information_criteria set `replicates` to the surviving count. That number was honest. Nothing in the file said a run had been lost, and the header explained `replicates` as though none could be: "the best fit is run best_fit_replicates times and log_likelihood is the mean over those runs". So a log-likelihood averaged over 3 of 10 runs read exactly like one averaged over 3 of 3. The `%d of %d simulation(s)` warning went to the log alone. The console's only mention of the count rides on a clause that appears once two runs have produced a value, so the case where nine of ten were lost was the case it hid. This matters where the criteria are used. _emit_information_criteria's own docstring says they "rank this fit against competing models"; that comparison is made by a reader holding two of these files side by side. A model whose best fit lost seven of its ten runs is scored on a mean over the three that worked, optimistic in the way #720 described, and can win the AIC comparison on it. InformationCriteria now carries replicates_requested and replicated_information_criteria takes `requested`; _compute_information_criteria passes len(jobs). The file carries a replicates_requested line beside replicates and, when they differ, says above the numbers how many runs produced nothing and that the mean is optimistic by however much they would have pulled it down. The console says the same, on its own line rather than folded into the standard-error clause, so a single surviving run still reports what was lost. A criteria object built without a request count is written exactly as before, and a run that lost nothing says nothing. The averaging is unchanged and no minimum-success threshold was added, unlike #720's confirmation ranking. That bar exists because candidate means were ranked against each other inside one run, and a candidate measured over one run could beat one measured over ten. This file holds one parameter set the run has already settled on, so there is nothing for a survivor mean to beat unfairly, and refusing to emit criteria over a low count would remove information rather than add it. ADR-0131 is amended rather than rewritten: its file example and its sentence about dropped runs describe the file as it shipped, with a note pointing here. ADR-0146's Consequences entry, which recorded this as deliberately out of scope for #720, is corrected -- it judged the averaging and missed that the sibling file did not even say it had dropped anything, a gap #740 itself widened. Signed-off-by: Bill Hlavacek <bill.hlavacek@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #720.
The defect
The best-fit confirmation stage (#659) exists to stop a stochastic fit reporting whichever parameter set got a lucky simulation: it runs the search's top
best_fit_candidatesparameter setsbest_fit_replicatesmore times each and reports the one with the best average.A replicate that fails, or that returns a non-finite objective value, produces no number to average.
mean_objectiveaveraged only the runs that did — a survivor-only mean over a sample size that varied per candidate from 1 tobest_fit_replicates. The failure count travelled on the row and was printed in a column, but it entered no comparison:rankedkeyed on that mean alone andwinnerdisqualified a candidate only when every one of its runs had failed.So a parameter set that failed nine runs of ten and survived one was scored on a single simulation again — the max-of-lucky-draws estimate, now with the unlucky draws deleted rather than averaged in — and it beat a parameter set that returned a slightly worse value in ten runs of ten.
_emit_best_fit_confirmationpins the winner on the trajectory by design, so the saved simulations, the best-fit BNGL, the information criteria, a refine's start point and a bootstrap replicate's answer then all described the failure-prone set.Run end to end on the test harness before this change:
and on the console, with no mention of the nine failures, the flatly false line "the one the search liked best does worse when it is run again".
The fix
A candidate is now confirmed, and eligible to win, only when more than half of the runs that came back produced a usable value.
Half is not a round number picked for convenience. A failure is known to be worse than every finite value, so it is exactly the condition for the middle of all of a candidate's runs to be a real number. Above the bar the survivor mean averages most of what happened; below it, it describes the minority that worked, which is the lucky draw the stage exists to remove. The bar is written against the runs that came back rather than against
best_fit_replicates, because a cancelled replicate is the cluster's doing and not the parameter set's.Unconfirmed candidates are ranked below every confirmed one whatever their averages say. When none clears the bar the stage pins nothing and the search's own pick stands, rather than the best of a bad lot.
Nothing is hidden:
mean_objectivestill reports the survivor average, the table gains aconfirmedcolumn and anunconfirmedline naming the demoted candidates, andwinner_runs/winner_failedput the winner's sample size in the file.After:
A candidate that loses a minority of its runs is unaffected: it is confirmed, ranked on its average exactly as before, and can win — with a note saying how many runs it lost.
The silence
The other half of the issue.
console_linesnever mentioned failures, and with a single survivorstandard_errorisNone, so the documented "raisebest_fit_replicatesif the standard errors overlap" guidance could not flag it either. The console now reports the winner's failed runs and how many candidates were not confirmed, and where it used to assert flatly that the search's pick "does worse when it is run again" — the opposite of the truth when that candidate had the better average and lost on reliability — it now says which of the two things actually happened.Alternatives
Recorded in ADR-0146, with why each was rejected: counting any failure as infinite (too harsh for a transient cluster timeout, and it contradicts behaviour pinned in a test since #659), ranking by failure count first (lexicographic, so one failure would outweigh any objective difference), a multiplicative penalty (PyBNF objective values are not sign-constrained — a likelihood objective is routinely negative), a configuration key, falling back to the best unconfirmed candidate, and ranking on the censored median itself — the estimator the argument actually points at, rejected for blast radius since it would change the reported value of every stochastic fit including those with no failures at all.
Testing
_confirm_best_fitwith a synchronous dask client; no winner when every candidate is too unreliable; the report's new columns and lines; and both readings of the search-rank sentence, so the true one is still printed when the search's pick really did run worse.mainin this environment and the failing-test sets are identical (334 pre-existing failures, all simulation tests needing a built BioNetGen that is not installed here); this branch adds 11 passing tests and changes nothing else.sphinx-build -W --keep-goingandruff check .both clean.