Skip to content

Releases: cameronabrams/htpolynet

Release v2.15.0

Choose a tag to compare

@github-actions github-actions released this 04 Oct 20:43

Added

  • Young's modulus now comes with an error bar, and deform stages can be
    replicated.
    plots post fits every pull -- each direction, each replica -- on its
    own and reports E as the mean of those fits with the standard error between them,
    writing the individual fits to E-fits.csv. A single fit's own statistics do not
    say how far a second pull would land; the scatter between pulls does. It warns when
    that error exceeds 20% of E. A deform stage takes replicas: N to run N pulls with
    their own velocities, in <subdir>-r1 ... <subdir>-rN, and seed to make them
    reproducible.

  • fit_strain sets the strain window E is fitted over. The window used to be a
    range of rows, [10, 200], which covers 0.1-2% strain only at the default rate and
    energy-output interval, and silently covered something else whenever either changed
    or the pull was shorter. plots post says when a pull stops short of it.

Changed

  • deform now defaults to a 40 ps pull, three replicas, and a fit over 0.1-3%
    strain
    (was 1000 ps -- a pull to 100% strain -- one replica, and rows 10-200). The
    defaults were measured on example 6's network, 15 pulls to 10% strain: E is flat to
    3-4% and softens past it, a single pull scatters by 80% fitted to 2% but by 30% fitted
    to 3%, and nine pulls give E to about 10% (2.67 +/- 0.19 GPa there). Tutorials 2, 4, 5
    and 6 use them. plots post on a run from before replicas existed still finds its
    single deform-<d> directory.

  • Young's modulus is fitted with a free intercept. The fit used to be forced
    through zero stress at zero strain, which assumes the structure starts unstressed. A
    glass equilibrated for tens of ps does not: example 6's started at +150-200 bar, which
    through the origin adds that offset over the strain to E -- about +1 GPa over a 2%
    window -- and another network's started at -180 bar, biasing it low. Moduli computed
    by earlier versions are off by an amount that depends on each structure's residual
    stress. The R^2 warning added earlier in this cycle is gone: a sound fit to a real
    trace has R^2 near 0.45, and the scatter between pulls is the real test.

Changed

  • Example 6 now cures bisphenol-A dicyanate by real cyclotrimerization. Its one
    monomer, BDC, carries two cyanate groups, and a ring_cure block closes three of them
    at a time into a 1,3,5-triazine, the reaction that actually cures a cyanate ester.
    Conversion is the cyanate-group conversion, counted directly, and an unreacted group
    simply stays -O-C#N, so the example needs no postcure repair. Until now the example
    used a stand-in model -- bisphenol A plus a pre-formed triazine, joined by aryl
    ethers, with a repair stage dismantling incomplete rings afterward -- whose triazines
    existed before cure began, so the way its network formed, and its gel point, were not
    those of the chemistry. The tutorial is rewritten to match, with every number from a
    reference build (234 rings, cyanate conversion 0.975, Tg 529 K heating and 520 K
    cooling, fractional free volume 0.213), and the postcure_repair page now describes
    the repair stage as the tool for reproducing builds made the old way. The tutorial
    no longer quotes a Young's modulus: its deform stages reach 1% strain, which the
    reference build showed is too little to resolve one.

  • htpolynet now requires ycleptic 2.4.4 or later (was 2.4.1). From 2.4.3 on,
    ycleptic checks a base config's shape when it loads, not only its vocabulary: an
    attribute indented one level too deep, a default: that contradicts its own type:,
    or a non-attribute in an attributes: list used to be ignored silently, so whatever it
    declared simply did not exist. 2.4.4 extends that check to the top-level list.
    htpolynet's own schema passes it clean.

Fixed

  • A postsim stage with an unusable subdir failed only when its turn came, after
    every stage before it had run -- a 21-stage grid would spend its allocation on the
    first stages and then die with a bare assertion. postsim now checks every stage's
    directory before running any and names all the bad ones. A subdir must be one name
    inside a project directory (postsim/deform-x); deeper paths such as
    postsim/grid-A/deform-x are not supported.

  • htpolynet plots diag failed on every ring-cure run with a bare IndexError. It
    recognized only CURE's two progress lines, and a ring cure logs its own, so it parsed
    nothing. It now reads both, and a log with neither raises an error that says so. Its
    iteration axes were also off by one for every cure: the cure's start was plotted as
    iteration 1.

  • htpolynet plots post crashed before computing Young's modulus on pandas 3
    ("Columns with duplicate values are not supported in stack"), losing e.png and
    E.csv after Tg had been plotted. The deform curves are now averaged without stacking
    same-named columns.

  • A bond stored with its atoms in descending order could silently keep stale
    parameters.
    Topology.reset_override_from_type put the requested pair in canonical
    order but compared it against the stored row as written, so it missed a bond stored as
    (aj, ai); the postcure repair's refresh_bond_params then swallowed the failure at
    debug level and the cap's C-N bond kept its aromatic parameters. The lookup now
    compares canonical order on both sides, and a refresh failure is a warning. Found by
    the first end-to-end test of the repair driver, which also lands here.

  • A ring cure's setup log no longer quotes a meaningless bond count. "100% conversion
    is N bonds" came from CURE's pairwise count, which treated a three-reactant ring
    closure as three independent pairwise reactions: 360 BADCy monomers reported 1080
    bonds, where full conversion is 240 rings and 720 bonds. A ring cure now says its
    conversion is counted in reactive groups and points at the "Ring cure begins" line.
    In a configuration with both pairwise and ring-closing reactions, the count CURE
    uses for its own target now leaves the ring-closing reaction out.

  • Two documentation passages still described Berendsen coupling as current after 2.14.0
    moved off it: the densification gate's warning now says the packaged npt.mdp uses
    C-rescale (and what that means for densities from older builds), and the container
    guide's list of GPU-update-compatible features names V-rescale and C-rescale.

Release v2.14.0

Choose a tag to compare

@github-actions github-actions released this 30 Sep 18:15

Changed

  • No packaged mdp uses a Berendsen thermostat or barostat any more, and every one of
    them now passes grompp with zero warnings
    --- which none of them did before.

  • Every temperature-coupled stage now uses the V-rescale thermostat. All seven
    packaged mdps used Berendsen, which Gromacs deprecates in the same breath as the
    barostat: "does not generate the correct kinetic energy distribution." That is every
    stage htpolynet runs, including the ones that produce published densities and
    transition temperatures. Same cause as the barostat, and the same remedy; tau_t is
    unchanged at 0.1 ps.

  • Constant-pressure stages no longer use the Berendsen barostat. npt.mdp,
    drag-npt.mdp and relax-npt.mdp now use C-rescale, which samples a correct NPT
    ensemble and is robust far from equilibrium; Berendsen is neither, and Gromacs has said
    so at every grompp since 2025 --- unheard, because those warnings were being
    discarded (fixed in 2.13.0). This covers densification, precure and postcure
    equilibration, the CURE relax and drag ladders, and the ring cure's relax and
    equilibrate.

    The deformation stages are the exception: they force pcoupltype = anisotropic, which
    C-rescale does not support at all --- grompp errors outright --- so deform and
    clamped shear now set Parrinello-Rahman, which does support it and also samples
    the right ensemble. Isochoric shear has no barostat and is unaffected.

    This changes the sampled ensemble. Densities, glass-transition temperatures and
    moduli from earlier versions are not strictly comparable with ones produced now. That
    cost is deliberate and was taken on Cameron's decision, in the same spirit as the 2.7.0
    constraint change. tau_p is unchanged at 0.5 ps, so that if anything proves unstable
    there is only one variable to look at; it is the first knob to try.

    A test now fails if any packaged mdp reintroduces a deprecated barostat, or pairs
    C-rescale with the anisotropic coupling it cannot do.

Release v2.13.0

Choose a tag to compare

@github-actions github-actions released this 29 Sep 14:27

Changed

  • Deformation and shear stages now say what a modulus from them is worth. Every run
    reports the strain it will reach and is told that one ramp gives a modulus but not
    its uncertainty
    : eight independent ramps of one protocol on one network gave shear
    moduli from 1.29 to 1.74 GPa --- 10% scatter --- while each run's own fit reported an
    error an order of magnitude smaller. Take the scatter between seeds as the error bar,
    not the fit's.

    A run fast enough that little of it sits at low strain also draws a warning, since what
    gets fitted is then a secant over a large window. It does not claim a bias, because
    none is established: a single slow ramp appeared to show the default understating G
    threefold and a replicate of that same protocol refuted it. Rate dependence past yield
    is real and does replicate --- 1.22 +/- 0.06 GPa at edot = 1e-3 against
    0.96 +/- 0.02 at 1e-4. The default is unchanged.

  • Deformation and shear stages now sample stress ten times more often. Both
    inherited nstenergy = 500 from the packaged npt.mdp, one frame per ps --- and
    stress is the quantity these stages exist to measure. Tested on a real shear run:
    going to a frame every 0.1 ps cut the standard error on the fitted modulus by
    sqrt(10), which is the independent-sample scaling, so the extra frames carry real
    information rather than repeating themselves. The cost is a larger edr and nothing
    else --- no change to the trajectory, the ensemble, or the physics.

Fixed

  • Gromacs warnings were discarded, so nobody ever saw one. htpolynet runs every
    grompp with -maxwarn 4, so up to four warnings do not stop a run, and the command
    wrapper only echoes captured output when a command fails. Between them, a warning on
    a successful run reached neither the user nor the diagnostic log.

    That is how the Berendsen barostat deprecation --- emitted by every constant-pressure
    stage htpolynet runs, on every Gromacs since 2025 --- went unnoticed for as long as it
    has been emitted, and it is why the shear stage's off-diagonal conflict had to be found
    by running grompp by hand. Warnings are how Gromacs says a setup is questionable,
    and they are worth seeing even when the run proceeds. They are now logged at WARNING
    level, with the mdp that produced them.

  • Uniaxial deformation was rejected by Gromacs 2025 before it started, so Young's
    modulus could not be measured at all.
    From Gromacs 2025, combining deform with
    generated velocities is a hard grompp error unless deform-init-flow = yes is set,
    because the initial velocities have to carry the flow profile the deformation implies.
    htpolynet never set it. Every deform stage therefore failed at grompp on a current
    Gromacs --- not with a bad number, but with no run. Both deform and the new shear
    now set it.

    This was invisible because nothing ran the stage: analysis/postsim.py had no tests,
    and the deformation traces on hand were produced by an older Gromacs. It was found by
    putting a real mdp through gmx grompp while trying to build a shear fixture.

Added

  • The shear stage's box coupling is now a choice, because it changes the answer by
    29%.
    ring_cure-style shear has to be run somehow, and there are three defensible
    ways to hold the box while shearing it; measured on one network with replicates they
    give G of 1.17, 1.35 and 1.51 GPa. That spread is wider than the trajectory scatter
    of any of them, so it does not average away. coupling: clamped (the default, and
    what every measurement so far used) keeps the two unsheared directions at pressure;
    coupling: isochoric uses no barostat at all, needs no uncoupling, and is the only one
    of the three that passes grompp with no warnings whatsoever.

    The default is unchanged, and not because it is the best: the Poisson ratio read off
    the box dimensions ranks it last of the three. It is unchanged because that is one
    cross-check at one strain, and switching would silently move every G anyone has
    measured. The stage now logs which coupling it used, so a G is never quoted without
    it.

  • A shear postsim stage, for the shear modulus. The counterpart of deform:
    Gromacs' deform drives one off-diagonal element of the box matrix at a constant
    rate, and G is the slope of shear stress against engineering shear strain, exactly
    as E is the slope of tensile stress against tensile strain. direction names the
    plane --- xy, xz or yz --- and edot is the shear rate in ps^-1.

    The normal directions stay pressure-coupled at P while the off-diagonal is driven,
    because the anisotropic barostat is given zero compressibility off the diagonal and so
    does not oppose the deformation. The stage writes the driven box element and the
    matching off-diagonal of the pressure tensor, with derived -strain and -stress
    columns; analysis.utils.compute_G fits those, as compute_E does for tension.

    One thing that differs from the tensile case and is easy to get wrong: the engineering
    shear strain is the driven box element over the length of the sheared face and starts
    at zero, so it carries no -1.

    A known disagreement, stated because it is not resolved in this release. Shearing
    by deform requires the barostat to be switched off for the Cartesian component the
    shear moves, since Gromacs refuses the run otherwise. G from that configuration
    comes out 1.51 +/- 0.05 GPa where a less constrained variant gives 1.17 +/- 0.02 on the
    same network --- and only the latter is consistent with that network's own E and
    Poisson ratio. So G here disagrees with E by about a quarter.

    The tempting explanation, that the constraint inflates G, does not survive its own
    test: a constant-volume variant fixes all three box dimensions rather than one, so it
    is more constrained still, and its mean sits below the clamped value. The cause is
    open. Treat G from this stage as disagreeing with E by roughly a quarter, and do
    not assume you know why.

    The mdp is accepted by Gromacs; no G has been measured. Putting it through
    gmx grompp found two defects the unit tests could not: the barostat must be
    uncoupled for the Cartesian component the shear moves, not merely off the diagonal ---
    grompp warns of "spurious periodicity effects" otherwise --- and deform-init-flow
    must be set, which is a hard error without. Both are fixed and grompp now accepts
    every plane. The energy terms it traces (Box-YX, Box-ZX, Box-ZY, Pres-XY,
    Pres-XZ, Pres-ZY) were confirmed present in Gromacs 2025.4.

    First real run, on a threading-filtered BADCy network of 12,600 atoms: both stages
    completed, the stress-strain curve has the right shape for a glassy thermoset --- an
    elastic rise to about gamma 0.08, a roll-over, then a yield plateau near 160-175 MPa
    --- and G comes out 1.3 +/- 0.2 GPa over gamma 0.08-0.10. deform with
    deform-init-flow was also verified on Gromacs 2026.3, not only the 2025.4 used here.

    That G is one window's slope, not a zero-strain tangent, and should not be quoted as
    a converged modulus. Below gamma 0.03 the fit has no power at all --- the standard
    error exceeds the estimate and the sign flips --- because per-frame virial noise is
    456 bar and a 300 ps ramp at edot = 1e-3 puts only 20 frames below that strain.
    Above it the fitted G falls from 2.08 to 1.31 GPa as the window grows, the same way
    the uniaxial fixture's E falls from 4.74 to 2.72. A slower ramp, or more frequent
    energy output at the same rate, is what a modulus fixture needs. ROADMAP.md carries
    this, and the related finding that the uniaxial trace has no resolved elastic region
    either.

  • Real simulation traces as test fixtures, for Tg and Young's modulus. Until now
    every test of those fits used synthetic data, where a clean straight line recovers its
    slope to eight digits. tests/unit/fixtures/ now carries a real 61-point temperature
    ladder and a real 1001-frame uniaxial deformation, the latter written by
    PostSimDeform itself.

    They show what synthetic data cannot. On the real ladder, Tg spans 377 to 406 K
    across fit windows a reasonable person would choose; on the real deformation, E spans
    2420 to 4398 MPa, nearly a factor of two. The R-squared of a perfectly sensible
    modulus fit is under 0.5, because single-frame pressure in MD scatters hugely --- so
    anyone tempted to add an R-squared quality gate should look at that first. All of
    this is now pinned by tests, and ROADMAP.md carries what to do about it.

    There is deliberately no shear fixture, and a test asserts its absence so that
    adding one forces the accompanying tests to be written. No shear simulation has been
    run.

  • Tests for the two numbers postsim exists to produce. analysis/postsim.py was a
    zero-coverage module, and compute_E and compute_tg --- the fits behind Young's
    modulus and the glass transition --- had no tests at all, while the shear stage added
    hours earlier did. Now covered: both fits against known slopes and a known
    intersection, the mdp construction for every deform and shear direction, that the
    driven axis is not pressure-coupled while the others are, and that an unknown direction
    is refused rather than guessed.

    Two of those tests document behavior rather than assert it is good. compute_E fitted
    across a rolled-over stress curve understates the modulus by more than a factor of two,
    which is what the fit window exists to prevent. And compute_tg trusts its windows: a
    hot window reaching about 20 K below the transition biases Tg roughly 7 K low, with no
    check and no warning. ROADMAP.md carries what a fix would look like.

Release v2.12.0

Choose a tag to compare

@github-actions github-actions released this 28 Sep 19:15

Added

  • A test now fails when a configuration section is added without documenting it.
    configs-for-run.rst still opens by saying a run config has "at most ten distinct
    sections" while the schema has fourteen: ncpu, ring_cure and postcure_repair have
    never been written up. The new test carries those three in a named list so the
    existing debt does not block the suite, fails on any new undocumented section, and
    fails again if one of the three is documented and left on the list.

  • htpolynet piercings reports any bond threaded through a ring in a finished
    network.
    Since 2.11.4 a ring cure can refuse to create a threading, and there was
    no way to ask whether one is there. That asymmetry is the reason this exists: a
    preventive check whose effect cannot be measured with the same tool is hard to trust,
    and every number that justified those filters came from scripts outside the package.
    Nothing about it is chemistry-specific --- any network built by dragging monomers
    together can thread a bond through a ring.

    It takes a top and gro pair like make-viz, reports each piercing with the ring's
    composition, whether the piercing bond belongs to a residue of the ring, the offset of
    the crossing from the ring centre in nm and as a fraction of the ring radius, the
    angle to the ring normal, and the bond length; it correlates those against bonds too
    long to be bonds, which in measured data are the same set. -json writes the
    findings out. It exits 1 when it finds any, so it can gate a pipeline.

    The geometry moved to htpolynet.geometry.piercing, shared with the ring cure's own
    filters so that a measurement and the thing it measures cannot drift apart.

    Validated against 12 independently measured builds (8 affected, 10 piercings):
    counts agree on all 12, including the four clean ones, and both interlocked cases are
    found with both halves. Bond lengths agree to the last digit, offsets within
    0.001 A, angles within 0.03 degrees. Four of those piercings are in the test suite as
    fixtures, atom-for-atom.

    That comparison found two defects in the reference data rather than in this code ---
    atom indices off by one, and a fraction divided by a mean rather than a maximum
    centre-to-vertex distance --- both since corrected upstream. The index error was
    findable only by testing whether the reported ring was a closed cycle in the topology,
    which is the check worth keeping in mind when two implementations are compared: agree
    with the data, not with the other detector.

    The angle is reported as angle_from_normal, and 0 degrees means the bond runs
    straight through
    , perpendicular to the ring plane. The report says so in a legend,
    because the obvious reading of such a field is the opposite one.

    Documented in the user guide, in the subcommand table and with a section of its own,
    alongside a note that a threaded ring is a topological defect rather than a strain
    relaxation removes: if one is found in a finished build, rebuilding with the ring-cure
    filters on is the remedy and there is no post-hoc repair.

    Image before you average. Every ring atom is brought into one periodic image
    before the centroid is taken. Without that, a ring straddling a boundary gets a
    centroid in the middle of the box and a triangle fan spanning the cell: measured here
    on a 2 nm box, a 0.14 nm ring reads as 0.938 nm about a centre a full box-half away,
    which reports a piercing for nearly everything nearby. A detector written without it
    found 212 piercings in an uncured melt, where the answer is zero by construction, and
    about 15% of rings straddle a boundary in a real system. The test suite covers that
    case, its converse, over-long piercing bonds of 2.4 to 3.2 A, composition classified
    on the whole ring rather than on a residue count or a truncated printout, and two
    interlocked rings.

Release v2.11.6

Choose a tag to compare

@github-actions github-actions released this 27 Sep 15:15

Added

  • A ring cure now refuses a closure that threads a ring already present, and registers
    the rings it makes.
    Two halves of one gap.

    reject_threaded_rings, added in 2.11.4, asks whether a new ring would close around
    an existing bond. It does not ask the converse --- whether dragging three monomers
    together has run one of their bonds through a ring that was already there. Over ten
    builds htpolynet-study found that second kind is mostly phenyls, which outnumber
    triazines about six to one here, and that two of three were made by the cure rather
    than by packing: one appeared in the same iteration as a triazine piercing, which is
    what identifies the two as a single event. reject_new_threading closes it.

    Only threadings the ladder creates count. The participating monomers are surveyed
    before the closure ladder and again after it, so a monomer that arrived from packing
    already threaded is not silently refused every reaction for the rest of the build ---
    one of those exists in their data.

    The second half is that Topology.rings never contained a cure-formed ring.
    detect_rings() runs once per molecule template and the list is replicated at system
    build; nothing updated it afterwards, and the product splice did not register the
    triazine it creates. So every ring the ring cure ever made was invisible to every
    ring-aware check in htpolynet --- including CURE's own pierces_ring, which means a
    pairwise crosslink could thread a previously formed triazine with nothing noticing.
    form_rings now registers each ring as it makes it.

    No evidence yet that threading of either kind measurably affects Tg or density; the
    case for both filters is mechanism, not measured harm. Set
    ring_cure.reject_new_threading: false to reproduce the old behavior.

Changed

  • The box and density figures in the 2.11.5 and 2.11.4 notes below are retracted.
    Both releases quote traces --- +27.9% in volume, path 0.4104 nm, a two-regime
    expansion and contraction --- produced by relax and equilibrate running against
    the ladder topology, which is the defect 2.11.5 fixes. The box was responding to a
    restrained, unbonded system, so those numbers describe nothing real and should not be
    requoted. They are left in place rather than rewritten, because the releases went out
    with them.

    What survives unaffected: conversion, ring counts and iteration counts, which are
    decided before those stages run; the threading filter's validation; the constant-volume
    control, since NVT cannot move the box whatever topology it reads; and every
    measurement from the pairwise route, which writes its topology correctly.

Release v2.11.5

Choose a tag to compare

@github-actions github-actions released this 26 Sep 14:24

Fixed

  • A ring cure relaxed and equilibrated against the wrong topology. Gromacs reads
    the topology from disk; form_rings changes it only in memory and writes nothing.
    close_rings writes one per ladder stage, so after the rings were formed the newest
    file on disk was still the ladder topology --- restraints in place at
    kb = 3e5, and the new C-N bonds not yet present. relax and equilibrate both
    grompp'd against it. Only settle and the ladder itself wrote what they ran.

    Counted on one iteration of 89 rings: ringclose-1-6.top carries 267 type-6
    restraints, exactly 3 per ring, and 11880 bonds; the post-formation ring-1.top has
    0 restraints and 12147 bonds. The command issued was
    grompp -c ringrelax-1-npt.gro -p ringclose-1-6.top --- current coordinates against a
    stale topology, because each mdrun refreshes the coordinate file and nothing refreshed
    the topology.

    So for the whole 100 ps equilibration the rings were held together by restraints
    rather than bonded, with the ladder ending around 0.229 nm against its 0.150 nm
    target, meaning those restraints were still loaded. Successful runs carried a
    Harmonic Pot. term of 2.1e5 kJ/mol throughout. One failure reached 775 K in a 300 K
    stage at -57429 bar with 1809 LINCS warnings, which is that restraint energy coming
    out.

    Every MD stage now writes the topology it is about to run. relax has had this
    defect since the ring cure was introduced
    , and equilibrate since 2.11.4, so box
    and density traces from any ring-cure build to date are not trustworthy and need
    re-measuring. Conversion, ring counts and the threading filter are unaffected ---
    all are decided before this stage runs.

Release v2.11.4

Choose a tag to compare

@github-actions github-actions released this 26 Sep 13:13

Added

  • A file of your own that replaces a packaged one now says so. pfs.checkout
    searches the user library, then the user cache, then what htpolynet ships, and took
    the first hit silently. Overriding a packaged file is supported and often the point;
    the trap is that a copy taken from an older version keeps that version's settings for
    as long as it stays on the search path, and nothing said which file a run actually
    used.

    Found the hard way. A study's npt.mdp, copied from 2.6.2 and edited in one place,
    stayed on its path across four upgrades. From 2.7.0 the packaged file sets
    lincs_order = 8, because at dt = 0.002 GROMACS' default of 4 is marginal --- it
    was fatal on one chemistry in 7 of 7 builds, with exit codes 1 and 139. The stale
    copy reverted every run to order 4, and the instability that produced was written up
    for weeks as a property of the systems being built.

    This bears on the 2.11.3 notes below. "Eleven of twenty builds died in the
    anneal" is the rate from builds carrying that stale mdp, so it overstates what the
    unrelaxed final batch alone causes. The defect and the remedy are unaffected: the
    bond energies entering the anneal are single-point energies of the structure and owe
    nothing to integration settings, and the paired seed test showed the settle helping
    even at LINCS order 4. Only the headline failure rate is in doubt.

  • A ring cure now refuses a ring that would close around an existing bond. CURE
    tests every candidate bond for piercing an existing ring; the ring cure has the
    converse problem and tested nothing at all --- ringcontroller.py had no reference to
    bondtest, pierces_ring or BTRC. A triazine could therefore form around a
    monomer that was already there and thread it permanently.

    That is what the stretched monomers were. Over ten finished builds, six carried a
    threaded triazine, and the threading bonds are the same bonds found stretched past
    2 A: 15 stretched, 13 threading, none threading without being stretched. 10 of 11
    were pre-existing monomer backbone bonds. It explains why those bonds sit on monomers
    that took no part in the ring, why a 160 ps anneal left them unchanged to two
    decimals, and why they hold roughly 700 kT without being thermal --- escaping a
    topological trap needs a bond to break, not a barrier to be crossed.

    The test runs after the closure ladder and before the bonds are formed, which is
    where the question is unambiguous: the loop is down to nearly its final size, so what
    is inside it is what the triazine will enclose. It treats the loop as a triangle fan
    from its centroid rather than as a plane, and images every coordinate against an
    anchor atom, since a centroid of raw wrapped positions gives false positives across a
    periodic boundary.

    ring_cure.prefilter_threaded_candidates applies the same test at candidate time as
    well, to avoid spending a ladder on a ring that is already threaded. It is off by
    default
    , because a candidate loop is three groups a search radius apart: a formed
    triazine's circumscribed radius is about 1.37 A and near enough rigid across 233 of
    them, while the candidate loop encloses roughly nine times that area at a 1.0 nm
    search radius and over twenty at 1.6 nm, which is where late iterations run.
    Threading would carry through the shrink if nothing moved, but the ladder runs six
    stages of NVT at 600 K, so a bond inside the candidate loop need not still be inside
    at closure. Set ring_cure.reject_threaded_rings: false to reproduce the old
    behavior entirely.

    Measured across three arms, same image, no mdp override. Filtered builds came out
    clean 6 of 6; unfiltered builds carried a pierced triazine in 2 of 4 --- 4, 2, 0 and 0
    piercings, a mean of 1.5 each. There is no conversion cost: 0.971 to 0.983 in 21 to
    23 iterations against 0.971 to 0.979 in 20 to 22 without it.

    The closure-time test declined 2, 10 and 1 candidates across three builds, a mean of
    4.3 against those 1.5 piercings. Same order of magnitude with overlapping
    distributions, and that is as much as the data supports: at three builds against four
    the smallest two-sided Mann-Whitney p obtainable is 0.057, so the observed 0.47 is no
    more evidence of agreement than of excess. The two are not the same quantity either
    --- a declined candidate is one that would have threaded, and the alternative
    select_disjoint takes instead may itself thread and be declined in turn, so declines
    bound the piercings prevented rather than counting them.

    The candidate-time prefilter is a different matter: it discarded 1216 to 4385
    candidates per build to catch what the closure test catches in single digits, and both
    arms finished equally clean --- which is why it stays off by default.

Fixed

  • A ring cure ran at constant volume from first ring to last, so it could not
    densify.
    Every ring-cure stage was NVT — the relaxation after each iteration, the
    closure ladder, and the settle — with no constant-pressure step anywhere. Measured
    over 22 iterations of a 233-triazine build, the box stayed at 5.241 nm to four
    decimals and the density at 1155.9 kg/m3 from the first ring to the last, with the
    entire volume change deferred to a single postcure NPT. CURE's density climbs
    through its cure, roughly 1085 to 1170, because its relaxation stages run NPT above
    Tg and are deliberately "the only above-Tg constant-pressure time in a cure".

    Cure shrinkage cannot be reproduced at fixed volume by construction — Snow measures
    0.0324 ml/g for BADCy — and a network that forms in a box which cannot respond
    accumulates internal stress with nowhere to put it, which is a plausible route to the
    stretched bonds recorded in ROADMAP.md.

    ring_cure.relax now mirrors CURE's relax.equilibration exactly --- minimization,
    NVT at 600 K, then NPT at 600 K and 1 bar --- rather than CURE's much longer
    per-iteration equilibration. The whole sequence is above Tg, not just the
    constant-pressure stage: relaxing at 300 K in between two 600 K closure ladders cooled
    the network and would have put a cold, less mobile configuration under the barostat,
    which is a good way to measure no densification and wrongly conclude the stage does
    not work. The closure ladder itself stays constant-volume on purpose: NPT while
    restraints are actively pulling groups together is not a stable combination.

  • A ring cure had no cold equilibration, so once it could move, its box only grew.
    Adding the constant-pressure relax stage above let the box respond, but relax ends
    hot. Measured with relax alone over a full build, the box expands thermally for six
    iterations to +27.9% in volume, then comes back down as the network forms: peak to
    minimum is -7.49% in volume and +71.9 kg/m3. The two legs nearly cancel, so the net
    is near zero and the path length is 0.4104 nm --- a frozen box captures none of it.

    That descending leg is not a measurement of cure shrinkage: 2 ps at 600 K does not
    equilibrate, so it mixes in residual thermal relaxation, and it spans conversion 0.55
    to 0.971. It costs iterations too --- search_radius is an absolute length and does
    not scale with the box, so an expanded box encloses fewer groups per sphere, and the
    hot-only arm needed 25 iterations against 22 for the same conversion. A cure is not only shrinkage --- the hot stages enlarge
    the box and a cold stage pulls it back, and on the pairwise route the box travels
    three to nine times further in length than it ends up moving.

    ring_cure now has an equilibrate stage identical in shape and in defaults to
    CURE.equilibrate --- NPT, 300 K, 1 bar, 50000 steps of 2 fs --- run once per
    iteration after relax, through the same code path the pairwise cure uses, so it
    picks up that route's box logging and density series rather than reimplementing them.
    The per-iteration shape of the two cures is now the same.

    It is also the expensive stage, roughly 99 s against a 280 s iteration. Whether
    100 ps is the right number for either route has never been measured; ROADMAP.md
    sketches the study.

  • Three ring_cure settings could not be set at all, because they were missing from
    the schema, and ycleptic rejects an unknown key outright rather than ignoring it.
    ring_cure.relax and ring_cure.closure.equilibration were never exposed, and
    closure.max_accept — added in 2.11.1 — was written into CURE's drag block by
    mistake, where a config could set it and nothing would read it. All three now sit
    where they belong, and a test asserts that every key the controller honors appears in
    the schema with a matching default.

Changed

  • The installation page now says not to install htpolynet from conda-forge. The
    package published there is several releases behind and cannot currently be updated:
    htpolynet depends on ycleptic, which is not yet available on conda-forge, so the
    feedstock's automatic version bumps produce a package that cannot import and none has
    been published. What is on conda-forge does install and run, so nothing warns a user
    that they have an old htpolynet.

    The conda-only recipe now installs Python and the MD binaries from conda-forge and
    htpolynet itself from PyPI. The warning is scoped to the htpolynet package alone
    --- conda-forge remains the recommended source for AmberTools and Gromacs --- and it
    gives the two commands to check the current state rather than asking the reader to
    trust a page that may itself be stale. The container remains the recommendation, and
    the page now says outright that it requires no installation at all.

Added

  • A build now says so when it hands on a bond that is not a bond any more. At the
    end of a ring cure and again in the final data, any bond longer than 0.2 nm is
    reported, the worst nam...
Read more

Release v2.11.3

Choose a tag to compare

@github-actions github-actions released this 23 Sep 20:38

Fixed

  • A ring cure handed its last batch of rings to postcure unrelaxed. Every batch
    but the last is relaxed twice: once by the per-iteration relax as soon as its bonds
    exist, and again by the next iteration's closure ladder, which is eight
    minimizations and 16 ps at 600 K against the relax's one minimization and 4 ps at
    300 K. The final batch only ever gets the first, and then goes straight into a
    500 K anneal.

    htpolynet-study measured it on three 2.11.1 builds, all of which had ended on a
    ring-forming iteration and had therefore been relaxed. Bond energy entering the
    anneal tracked how many rings that last iteration formed -- 1, 1 and 4 rings for
    18849, 26113 and 35959 kJ/mol, against 13619 +/- 226 for a route that enters already
    relaxed. An inventory of one such structure found five bonds beyond 2.0 A: one
    triazine's three ring bonds at 2.24 A, and two monomers whose own backbones the
    ladder had stretched to 2.34 and 3.20 A while pulling their arms toward different
    rings. Eleven of twenty builds died in the anneal.

    The cure now runs a minimization and a short 300 K NVT once on exit, mirroring what
    the postcure repair stage already did for the route that never saw this. It is
    unconditional -- the cure has four ways to end and which of them leaves strain behind
    is not worth reasoning about per exit -- and configurable as ring_cure.settle.

    Confirmed paired, same eight velocity seeds against the same two structures on the
    same hardware: two anneals exploded without the settle and none with it. The two
    came from different source structures, one of which had never failed in production,
    so the effect is not particular to a pathological build, and nothing exploded during
    the settle itself -- the minimization absorbs the strain rather than the failure
    moving earlier. Eight seeds is a small sample and that comparison alone would be
    weak; it is corroboration of the bond energies above, not the case on its own.

Release v2.11.2

Choose a tag to compare

@github-actions github-actions released this 23 Sep 15:25

Fixed

  • A ring cure near its target kept re-proposing the triples it had just declined.
    Declining a ring that the closure ladder could not pull shut leaves its three groups
    unreacted, which is the point -- but the next search then offered the same groups,
    ran the same eight-stage ladder, and declined them again. htpolynet-study measured
    the cost on a 2.11.1 build: at conversion 0.97 with 24 groups left, nine iterations
    running produced 77 declines and not one ring, at about 67 s each.

    A declined combination is now passed over while any untried ring is within the search
    radius. It is a preference and not a prohibition: that cure escaped on iteration 41
    and finished at 0.971, because the relaxation between iterations moves the groups, so
    a triple that failed once can still close later. When the memory is the only thing
    left keeping an iteration idle, the search lifts it and offers those rings again
    rather than stand still.

Release v2.11.1

Choose a tag to compare

@github-actions github-actions released this 23 Sep 00:31

Fixed

  • A cyclotrimerization's charges were computed on a ring that was never closed. The
    trimer template reached antechamber with its three ring bonds recorded but still about
    2.2 A long: the closure ladder cannot beat the reactants' own C#N, whose sp parameters
    hold each group linear, and no force constant wins that. Charges come from the
    geometry, so the semiempirical step charged three separate cyanates rather than a
    triazine, leaving every ring carbon about 0.21 e low with the deficit smeared over
    the rest of the molecule.

    Nothing caught it. The atom types were right, because antechamber perceives those from
    the connectivity, which was right; the molecules were neutral, because the error sums
    to zero; and the system's own ring bonds relaxed to 1.347 A after the splice, because
    the bonded parameters were fine. It surfaced only when htpolynet-study ran a campaign
    with bcc charges and half their builds died in a 500 K anneal -- the release smoke
    test had used gas charges, which are computed from the topology and could not have
    seen it.

    A product whose reaction closes a ring is now relaxed against its own connectivity
    before it is parameterized, and refused if the ring is still open afterward.
    Measured on a methyl cyanate trimer, ring-carbon charge: +0.699 before, +0.909
    after, against +0.909
    for the same molecule built independently from SMILES.
    htpolynet-study measured the same 0.21 e deficit on a bisphenol A dicyanate trimer.

    Two things fell out of the investigation that are worth knowing:

    • Correcting the bond orders does not fix it. An addition bond leaves the cyanate
      carbon pentavalent -- it keeps its C#N, gains the ring bond, and still carries its
      ester oxygen -- and that is now repaired too, since RDKit cannot read a molecule with
      an impossible valence. But repairing the orders alone produced charges identical to
      the broken ones, because the semiempirical step reads coordinates, not bond orders.
    • antechamber's output geometry is its input geometry. It is not evidence about
      what the charge calculation did or did not relax, and it should not be read as such.
  • A ring the closure ladder could not pull shut is now declined rather than bonded
    long.
    Late in a cure the matrix is rigid enough that a ring sometimes stops well
    short -- one batch at conversion 0.95 ended 0.327 nm apart against a target of 0.150 --
    and forming the bond anyway leaves it long for good: two ring bonds survived relaxation
    and a 500 K anneal still 2.6 A apart, strained but stable, a defect the network then
    carries permanently. Declining costs almost nothing, since the groups stay unreacted
    and are offered again next iteration once the neighbourhood has moved. The limit is
    ring_cure: closure: max_accept, default 0.30 nm; 0 restores the old behavior. Only
    visible now because the charge fix let a build reach conversion 0.95 for the first
    time.

  • Topology.rebalance_mol2_bond_orders reduces a multiple bond when an addition would
    otherwise leave an atom over-valent, preferring a bond that is over-valent at both ends
    because reducing it repairs two atoms at once.