Running a case

The command is the orchestration with a shell in front of it: it reads a population and a keyword set, runs one case, and reports where the outputs are. It adds no modelling and no keyword the caller did not write.

python -m fresh_fvsbc population.json \
  --directory run \
  --species FD --tree-age 60 \
  --keyword "STDIDENT=SYNTHETIC   the command" \
  --keyword STDINFO=CAR,IDFdk3/01,0,200,35,16 \
  --keyword MANAGED=0,1 \
  --keyword SITECODE=FD,100 \
  --keyword INVYEAR=0 \
  --keyword DESIGN=0,1,999,1,0,1,1 \
  --keyword NUMCYCLE=4 \
  --keyword RANNSEED=1 \
  --keyword ECHOSUM \
  --keyword PROCESS \
  --keyword STOP

An installed package gets the same command as fresh-fvsbc. The recorded invocation, with its exit status and its outputs, is in notes/reference/synthetic-run.md.

The population file

A JSON list of stem records, shaped like nemora’s output. The adapter reads stand_id, dbh_cm, height_m and crown_ratio, and ignores the rest.

[
  {"stand_id": "S1", "dbh_cm": 20.0, "height_m": 18.0, "crown_ratio": 0.5},
  {"stand_id": "S1", "dbh_cm": 26.0, "height_m": 22.0, "crown_ratio": 0.6}
]

--species and --tree-age are required because nemora supplies neither, and the adapter fails rather than defaulting. See nemora ingestion contract.

The keyword set

--keyword NAME=FIELD,..., repeated in the order FVS will read them. The fields are FVS’s own parameter fields, so a blank field is written as a gap:

--keyword TIMEINT=,5

--block NAME=FIELDS|LINE|LINE writes a keyword that takes a block of lines: the fields come before the first | and each | starts a line. This is how a caller asks for the database tables:

--block "DATABASE|DSNOut|FVSOut.db|Summary|ComputDB"

--keyword and --block keep their relative order, because a DATABASE block must precede the stand setup — see Keyword-set contract. A --block for a keyword that takes no lines is refused with exit 2.

The names are validated against FVS’s table, so a typo is refused rather than passed to Fortran. Two limits are worth knowing:

  • A parameter cannot contain a comma, because the comma separates the fields. The API has no such limit.

  • TREEFMT and TREEDATA are refused, because the run writes them. So are the block keywords (COMMENT, COMPUTE, IF, DATABASE), because the keyword model has no room for a block yet. See Keyword-set contract.

Exit codes

0

The run completed and wrote its outputs.

1

The run was attempted and failed: an error row, no database, a bad projection, or no extension. This is worth reporting.

2

The input was wrong before any run: a missing or malformed population file, an unknown keyword, a parameter wider than its field, a species that does not resolve. This is worth fixing.

argparse already exits 2 for a usage error, so the third code is the convention rather than an invention.

What the run writes

The directory named by --directory, created if absent, holds:

  • run.key, the keyword file, with TREEFMT and TREEDATA inserted before PROCESS;

  • run.tre, the tree list, ending with the -999 line FVS reads to;

  • FVSOut.db, the database, under the fixed name dbsblkd.f declares;

  • run.out and run.sum, FVS’s own report and summary;

  • provenance.json, the record described in Provenance contract;

  • summary.csv and variants.json, what the child process that drove the projection wrote for the parent: the summary FVS produced, and the build’s variant list.

A run owns its directory. The database’s name is fixed, so two runs in one directory overwrite each other’s database. Each run drives its projection in a child process (ADR 0010), so the child owns the working directory and the Fortran state, and two runs in one process are independent: a run is thread-safe, and a second run cannot inherit the first one’s state.