Skip to content

Check impose_resolution against the rules transcribed from the Fortran - #21

Merged
remislp merged 1 commit into
masterfrom
fortran-resolution-tests
Sep 8, 2026
Merged

remislp merged 1 commit into
masterfrom
fortran-resolution-tests

Conversation

@remislp

@remislp remislp commented Sep 8, 2026

Copy link
Copy Markdown
Member

scsim.impose_resolution had no test that owed nothing to it. Its behaviour was pinned against values it produced itself, which fixes whatever it does rather than whether what it does is right.

RESHJC2.FOR in DCPROGS/DCFORTRAN imposed the dead time for every published HJCFIT result, Colquhoun, Hatton & Hawkes (2003) among them. Its rules are short enough to transcribe faithfully, and this checks impose_resolution against that transcription.

The rules

RESOLV, lines 1045–1061 an interval is resolvable when its duration is at least treso if open or tresg if shut — and HJCFIT's simulate-and-fit path sets tresg = treso (Hjcfit1-09122003.for line 2670), so there is one dead time
the shut branch, line 419 a concatenated shut group ends only when the next interval is open and resolvable
the open branch, line 448 an open group ends only when the next interval has a different amplitude, or is shut, and is resolvable

Both are the Colquhoun & Sigworth definition: an unresolvable event is absorbed into the interval in progress rather than split between neighbours.

Result

The two implementations agree interval for interval on five simulated records of 20 000 intervals each, and at five dead times from 1 to 500 µs.

One real difference, asserted rather than hidden

RESHJC2 lines 364–375 concatenate an unresolvable first interval with the second and give the result the second's conductance class, where impose_resolution keeps it as its own interval.

It can affect only the first interval of a record, and burst segmentation discards a leading shut interval anyway, so it has never mattered in practice — but it is a difference, and test_first_interval_is_the_one_known_difference says so. If that ever starts failing, one of the two has changed its mind about the start of a record.

Also pinned

The properties the rule exists to guarantee: every interval after the first is at least the dead time, the record still alternates open and shut, total time is conserved, and a dead time of zero changes nothing.

14 tests. Full suite: 604 passed, 1 skipped.

Written while reproducing Colquhoun, Hatton & Hawkes (2003), where the same transcription showed 0 differences over 196 988 intervals — this brings that check into the repository so it keeps being run.

🤖 Generated with Claude Code

scsim.impose_resolution had no test that owed nothing to it. Its behaviour was
pinned against values it produced itself, which fixes whatever it does rather
than whether what it does is right.

RESHJC2.FOR in DCPROGS/DCFORTRAN imposed the dead time for every published
HJCFIT result, Colquhoun, Hatton & Hawkes (2003) among them, and its rules are
short enough to transcribe faithfully:

  RESOLV, lines 1045-1061: an interval is resolvable when its duration is at
  least treso if open or tresg if shut, and HJCFIT's simulate-and-fit path sets
  tresg = treso, so there is one dead time;
  the shut branch, line 419: a concatenated shut group ends only when the next
  interval is open AND resolvable;
  the open branch, line 448: an open group ends only when the next interval has
  a different amplitude, or is shut, AND is resolvable.

Both readings are Colquhoun & Sigworth: an unresolvable event is absorbed into
the interval in progress rather than split between neighbours.

The two implementations agree interval for interval on five simulated records
of 20000 intervals each and at five dead times from 1 to 500 microseconds.

One difference is real, and is asserted rather than hidden. RESHJC2 lines
364-375 concatenate an unresolvable FIRST interval with the second and give the
result the second's conductance class, where impose_resolution keeps it as its
own interval. It can affect only the first interval of a record, and burst
segmentation discards a leading shut interval anyway, so it has never mattered
in practice -- but it is a difference, and a test now says so. If it ever
starts failing, one of the two has changed its mind about the start of a
record.

Also pins the properties the rule exists to guarantee: every interval after
the first is at least the dead time, the record still alternates, total time is
conserved, and a dead time of zero changes nothing.

14 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@remislp
remislp merged commit ae00bb3 into master Sep 8, 2026
9 checks passed
@remislp
remislp deleted the fortran-resolution-tests branch September 8, 2026 09:13
@remislp remislp mentioned this pull request Sep 11, 2026
remislp added a commit that referenced this pull request Sep 11, 2026
Seven merged pull requests have been waiting since 1.1.0, and one of them is
now blocking work elsewhere: HJCFIT's fitting layer is duck-typed on a
mechanism and needs Rate.is_free, which exists here on master and not in the
published 1.1.0. Its [fitting] extra pins scalcs>=1.1.0, which is too loose --
the integration tests fail on the released wheel with "'Rate' object has no
attribute 'is_free'".

A minor rather than a patch release, because it corrects computed values as
well as adding API.

  #17  root bracketing for the asymptotic pdf. Counts were not recomputed when
       a search bound moved, widening happened once where it needed to repeat,
       and the upper bound was compared against the wrong state count. det[W(s)]
       is also unusable at large |s|, where it is of order 1e53 and changes sign
       hundreds of times in noise, so roots are now refined by bisecting the
       eigenvalue count where the determinant cannot be trusted. Roots are
       validated before being returned.
  #18  scsim re-exports burst segmentation from dcio; one implementation in the
       stack rather than two.
  #20  the dARSdS sign. exact_mean_open_shut_time was too large by exactly
       twice the dead time and the conditional means by once. This changes
       numbers a caller will have seen.
  #21  impose_resolution checked against RESHJC2.FOR interval for interval.
  #22  the simulator checked against Q-matrix theory for multi-state
       mechanisms, not only the two-state case where a reciprocal rate is the
       answer.
  #23  qmatlib.H inverted sI - QFF directly, which fails where the root search
       walks. The singularity is removable; only the factored form has a pole.
  #24  GPL-2.0-or-later rather than GPL-2.0-only, without which HJCFIT
       (GPL-3.0-or-later) could not depend on this at all.

Both version files are bumped; the versions-agree gate added in #16 is what
makes that hard to get wrong. Verified: the built wheel reports 1.2.0, carries
its three data files, declares GPL-2.0-or-later, and Rate.is_free is present
when installed from it into a clean environment.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant