Skip to content

Grow library families with some preset values changed (--family-settings), release 3.9.0 - #381

Merged
psweens merged 6 commits into
masterfrom
claude/epic-bardeen-5tzx42
Oct 15, 2026
Merged

psweens merged 6 commits into
masterfrom
claude/epic-bardeen-5tzx42

Conversation

@psweens

@psweens psweens commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Summary

vsystem-library gains one opt-in, repeatable option, --family-settings FAMILY JSON, which grows a family with some of its preset values changed, for example a foam bed at a smaller spacing or a capillary fill of more generations:

vsystem-library ... --families foam capillary_bed \
    --family-settings foam '{"bed": {"spacing": 5}}' \
    --family-settings capillary_bed '{"capillary_generations": 4}'

The settings reach each member as flags on its recorded command line, which stays a runnable vsystem command line growing the same network, and are recorded in the manifest and the archives, so the library stays reproducible and resumable. A library without the option is 3.8's.

Behaviour

  • Keys: library.FAMILY_SETTING_KEYS (append-only): persistence, anastomosis_fraction, anastomosis_radius, anastomosis_min_separation, aneurysm_prob, stenosis_prob, root_offsets, guidance, capillary_generations, capillary_runs, cross_connect, rung_below, rung_spacing, rung_radius, rung_lateral_deg, rung_axis, rung_min_separation, bed.

  • Bed: merged over the preset's bed and normalised by bed.parse_settings, so every key of bed.KIND_KEYS[kind] is recorded (13 for foam, 21 for fibre). Refused for a family whose preset has no bed, as null, and with a kind or layout other than the preset's. The preset's own kind or layout may be repeated, as a normalised bed does.

  • Other values replace the preset's: arrays whole (null refused), numbers as floats, whole numbers for the integer settings, cross_connect as a bool. A bool is refused where a number is expected.

  • library.normalise_family_settings(family, spec) checks and normalises one family's settings: keys in FAMILY_SETTING_KEYS order, idempotent. It raises ValueError, which the command line turns into an exit naming the family. The command line also refuses a family not listed, a family given twice, and JSON that is malformed, not an object or empty.

  • Refused options, with the reason in the message:

    • what the library fixes for every member (d0, d_min, iterations, the box, collision_margin, avoid_collisions, grow_in_volume, …);
    • what makes a family what it is (tortuosity, anastomose, anastomose_mode, a bed's kind or layout, turning a bed on or off);
    • the grammar's own ranges and the numerical and output settings.

    A negative number in exponent notation (such as -1e-05, reachable only for the two probabilities) is also refused, because argparse would read it as an option and the member's command line would not run. The README gives the reason for each class.

  • Command line: member_argv(..., settings=...) appends one flag per key after the 3.8 flags, in key order. Option strings come from main.build_parser(family)'s actions; values are written as compact sorted JSON, repr for floats and digits for ints, with --cross-connect / --no-cross-connect for the bool. grow_member(..., settings=...) passes the settings through the same path. With settings=None both return what 3.8 returned.

  • Checked before growth: each family with settings has its command line at R_LO and R_HI parsed by main.build_parser(family) and checked by main.validate_shaping. A refusal at either end refuses the library and names the family.

  • Printed before growth, for a family with settings and a bed:

    • one line with the candidate edges planned at R_HI against --bed-max-candidates and the largest R the cap admits (largest_admitted_ratio, bisection on the count grow_member's unchanged cap reads, rounded down);
    • a second line, where feeder_stop exceeds R_LO, with the smallest R it admits.

    Members beyond either bound are recorded as failures, as before.

  • Records:

    • The manifest's growth.family_settings holds the settings, and only when some family has settings, so a rerun with different settings, or none, is refused by the existing comparison.
    • Each archive of the family records them in its metadata under family_settings, and validate_existing compares the command line with them.
    • No index column is added.
    • The family keeps its name: growth.family_settings and the archive's family_settings are where a consumer tells a variant library from a default one.

Compatibility

  • tests/test_pinned_release_3_8.py runs the 3.8.0 modules, copied unchanged into tests/fixtures/reference_code_3_8 (main's import closure including bed, with library, describe, join and frames), beside the current ones in the same environment. It compares:

    • member_argv for every family under each library option;
    • the growth settings;
    • grow_member for every family 3.8 offers at small R (argv, kwargs, nodes, program, index row);
    • a plan of every family;
    • the default library and one of foam, capillary_bed and aligned_bed, each grown and then resumed with its index removed (archives, index rows, manifest content).

    Every record is restricted to the keys 3.8 wrote, listed literally, with seconds, peak_rss_mb, library_version and code_sha256 left out. The global generators start from a fixed state and are compared after each case. The hashes are recorded in tests/fixtures/reference_hashes_3_8.json, and the pin was committed passing before library.py changed.

  • Settings for one family leave the plan, the seeds, the ratios and the other families' members bitwise equal to a library without them.

  • main.py, bed.py, main's import closure, main.FAMILIES, main.RNG_STREAMS and library.RNG_STREAMS are unchanged. Version 3.9.0 in pyproject.toml and library.LIBRARY_VERSION. Python 3.9 syntax, numpy and tifffile only.

Measurement

tests/test_library_settings.SettingsBenchmark (slow-gated) grows each member through grow_member with settings, in a fresh process, seed 1. All 28 members finished, none over 15 minutes.

R 4 R 8 R 16
foam length density, spacing 6 / 5 / 4.5 over 7.5 (density 0.5) 1.60 / 2.29 / 2.83 1.51 / 2.14 / 2.62 1.52 / 2.15 / 2.64
tissue distance mean, preset bed → spacing 4.5, density 0.6 (d_min) 3.45 → 1.65 3.08 → 1.54 3.01 → 1.51
foam growth, preset bed → spacing 4.5, density 0.6 1.2 → 5.4 s 13.1 → 54.9 s 104.9 → 483.0 s (859 MB)
capillary_bed length density, 3 → 4 generations 0.0198 → 0.0166 0.0134 → 0.0160 –

Density 0.6 adds 14–23 % of length at any spacing. At R 8, capillary_bed with 4 generations took 882 s to grow, the slowest member measured. The largest R the default cap (2 × 10⁶ candidate edges) admits, rounded down:

foam bed density 0.5 density 0.6
spacing 7.5 31.33 27.75
spacing 6 25.07 22.21
spacing 5 20.90 18.52
spacing 4.5 18.82 16.67

The full table (points, planned candidates, peak RSS, tissue p90 and max, capillary length share) is in the README.

Tests

  • Baseline at 3.8.0: 704 tests, OK, 62 skipped. With scipy installed the two scipy-gated tests run; 64 are skipped without it.
  • After: 729 tests, OK, 63 skipped (the new benchmark). pyflakes clean.
  • tests/test_library_settings.py covers:
    • normalisation and every refusal, each before anything is written and naming the family, including refusals at one end of the ratio range only;
    • the flags and their parse back to the normalised values;
    • members bitwise equal to main.main on their recorded command line for foam ({"bed": {"spacing": 5}}), capillary_bed ({"capillary_generations": 4}) and a mesh member carrying every non-bed key;
    • the printed lines and the cap they report, against grow_member's own refusal;
    • the records in the manifest and the archives;
    • resuming with the same, different or no settings;
    • the other families' members, bitwise equal to a library without settings.

🤖 Generated with Claude Code

…inst the released modules

tests/test_pinned_release_3_8.py runs the 3.8.0 modules, copied unchanged
into tests/fixtures/reference_code_3_8 (main's import closure, bed
included, with library, describe, join and frames), beside the current ones
over one driver: member_argv for every family under each library option,
the manifest's growth settings, grow_member for every family 3.8 offers at
small R (argv, kwargs, nodes, program, index row), a plan of every family,
and the default library beside one of foam, capillary_bed and aligned_bed,
each grown and then resumed with its index removed (every archive's arrays
and metadata, the index rows and the manifest content). Every record is
restricted to the keys 3.8 wrote; a run's seconds and peak memory and the
manifest's library_version and code_sha256 are left out. The global
generators start from a fixed state and are compared after each case. The
hashes recorded on this machine are checked as the 3.6 pin checks its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…--family-settings

vsystem-library --family-settings FAMILY JSON (repeatable, once per family)
overrides a family's preset values from library.FAMILY_SETTING_KEYS: a bed
object merged over the preset's bed and normalised by bed.parse_settings,
and every other value replacing the preset's. normalise_family_settings
checks and normalises them, refusing the library's own settings, what makes
a family what it is, a bed for a family without one, a null bed, a changed
bed kind or layout, mistyped values and a negative number argparse would
read as an option; the command line also refuses a family not listed, one
given twice and malformed, non-object or empty JSON, naming the family.

The settings reach each member as flags appended to its command line in
key order, so the recorded argv stays a runnable vsystem command line that
grows the same network. Before anything is grown each family's command line
is parsed and checked by main.validate_shaping at both ends of the ratio
range, and for a bed family the planned candidate edges at R_HI, the largest
R the cap admits and the smallest R feeder_stop admits are printed. The
manifest records the settings under growth.family_settings and each archive
under family_settings, only when given; resuming with different settings is
refused and validate_existing compares the argv with them. A library without
the option is 3.8's, and settings for one family leave the plan and the
other families' members unchanged. Version 3.9.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s 3.8 wrote

Anastomosis bridge records, in grow_network's return and in archive
metadata, are restricted to their ten 3.8 fields as rung records already
were, and index.csv's events cell is kept to the 3.8 counters, so that a
field or counter a later version appends never enters the comparison. Every
3.8 key is kept, so the recorded hashes are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d bed object with ValueError

The feeder_stop line printed the bound to six significant digits, which
could round below the member check's strict bound; it now prints %g only
where that is the same number, else the repr. A bed object with a key
that is not a string reached dict(**value) and raised TypeError; the bed
is now merged with dict.update, so bed.parse_settings refuses it with
ValueError. Numpy arrays and scalars inside an array setting given through
the Python API are taken as the JSON values they stand for. The tests add
refusals at one end of the ratio range only (root offsets outside the cube
at R_LO, too many feeder sheets at R_HI) and compare a member's command
line without settings with 3.8's written out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README's library section gains --family-settings in the option table
and a section on family settings: the keys and how each is merged or
replaced, the classes of refused options and why each is refused, the flags
on a member's command line, the checks and lines before growth, the records
in the manifest and the archives, resuming, and how a consumer tells a
variant library from a default one, with the benchmark's measurements of
foam over bed spacing and density and capillary_bed over its generations,
and the largest R the default cap admits at each foam setting. The tests
section names the 3.8 pin and the settings tests, and the option table
lists fibre among the families and box constants. CHANGELOG 3.9.0 with
Library, Measurement and Compatibility sections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lision avoidance

The library-owned class of refused options now reads "fixed for every
member from the plan, the library's options and its box layout", which
holds for growth in the volume and for collision avoidance, set by the
library for trees and by every other preset itself; clip_axes, which the
library neither sets nor records, joins the numerical and output settings.
The README and CHANGELOG state that the up-front check and the printed
lines cover the families given settings, that check_family_settings
returns the lines vsystem-library prints, and round capillary_bed's length
change at R 8 to 20 %.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@psweens
psweens merged commit 36e9468 into master Oct 15, 2026
6 checks passed
@psweens
psweens deleted the claude/epic-bardeen-5tzx42 branch October 15, 2026 09:49
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.

2 participants