Workflow Files
Workflow files keep input paths, output paths, calculation settings, and classifier settings in a reproducible TOML file.
Use workflows when command lines become long, when you need named batch jobs, or when you want settings committed alongside an analysis.
Commands
zsasa calc --workflow sasa.toml
zsasa batch --workflow bsa.toml
For batch mode, --manifest is still accepted as a compatibility alias for --workflow:
zsasa batch --manifest bsa.toml
Prefer --workflow in new scripts and docs.
Calc Workflow Example
version = 1
kind = "workflow"
[input]
path = "structure.cif"
[output]
path = "sasa.json"
format = "json"
[calculation]
algorithm = "sr"
rsa = true
n_points = 100
probe_radius = 1.4
threads = 0
[classifier]
type = "ccd"
ccd = "components.zsdc"
Run it with:
zsasa calc --workflow sasa.toml
Batch Workflow Example
Batch workflows contain one or more named [[jobs]] entries:
version = 1
kind = "workflow"
[input]
dir = "structures"
[output]
dir = "results"
format = "jsonl"
[output.jsonl]
decimals = 3
atom_areas = false
total_area = true
metadata = "sidecar"
[calculation]
algorithm = "sr"
residue_map = true
n_points = 100
threads = 8
[classifier]
type = "custom"
config = "my_classifier.toml"
[[jobs]]
name = "chain_a"
chains = ["A"]
[[jobs]]
name = "chain_b"
chains = ["B"]
[[jobs]]
name = "complex_ab"
chains = ["A", "B"]
Run it with:
zsasa batch --workflow bsa.toml
For ordinary PDB, JSON, and unfiltered mmCIF/BinaryCIF workflow batch runs with compatible chain-ID settings, zsasa reuses each parsed input structure across jobs internally. For named chain analyses such as chain A, chain B, and complex AB, list only the jobs you want; eligible runs parse each input structure once and then compute each requested chain selection independently. Some inputs or settings, such as SDF files, per-job auth_chain changes, or mmCIF/BinaryCIF workflows with chain filters, use the compatibility job-first path instead so full chain-ID selection matches parser behavior.
Per-file Chain Maps
Use a chain map when each structure needs a different chain selection. Set
chain_map on a workflow job instead of chains:
version = 1
kind = "workflow"
[input]
dir = "structures"
[output]
dir = "results"
format = "jsonl"
[[jobs]]
name = "selected_complexes"
chain_map = "chains.csv"
The CSV must contain filename and chains columns. Add a globally unique
id for each requested selection when a filename appears more than once.
asym_id_type is optional and defaults to label:
filename,id,chains,asym_id_type
1111.cif,a,A,label
1111.cif,bc,"B,C",label
1111.cif,abc,"A,B,C",label
2222.cif,a_2222,X,label
2222.cif,b_2222,Y,label
2222.cif,complex_2222,"X,Y",label
chains is a comma-separated set. A row containing A,C calculates the SASA
of the A+C complex: atoms in both chains are included and occlude each other.
It does not calculate A and C independently. The first three rows above emit
the reusable primitive results SASA(A), SASA(B+C), and SASA(A+B+C). This path
does not calculate ΔSASA or BSA; derive those quantities downstream.
JSON chain maps are also accepted:
[
{"filename": "1111.cif", "id": "a", "chains": ["A"], "asym_id_type": "label"},
{"filename": "1111.cif", "id": "bc", "chains": ["B", "C"], "asym_id_type": "label"},
{"filename": "1111.cif", "id": "abc", "chains": ["A", "B", "C"], "asym_id_type": "label"}
]
For a repeated filename, every row must have an id, IDs must be unique across
the whole map, and all rows must use the same asym_id_type. Legacy maps with
one row per filename may omit id; the output ID then defaults to the
filename, preserving one result per structure.
zsasa groups map rows by filename. It reads/decompresses, parses, and classifies
each discovered structure once, then calculates its selections from that
prepared input. Chain sets are canonicalized as sets, so requests such as
["B", "C"] and ["C", "B"] share one SASA calculation while still emitting
separate rows with their requested IDs and chain order.
Workflow threads controls concurrent structure-file workers. With more than
one file worker, every individual SASA calculation uses one internal thread to
avoid nested oversubscription. With one file, the configured threads are
available to its SASA calculations. Progress advances once per discovered
structure after all of its selections finish.
For multi-selection maps, zsasa schedules discovered structures by descending selection count before file workers claim them. Filename order breaks equal-cost ties deterministically. This longest-processing-time-first estimate starts selection-heavy structures early to reduce the file-level tail without reading or parsing inputs during scheduling. Generic batch scans, BSA workflows, and legacy one-row-per-file maps keep their existing order. Selection calculations within a structure remain serial, so exceptionally heavy final structures can still leave residual tail latency; bounded parse-once work stealing is tracked in GitHub issue #413.
Multi-selection maps require JSONL output. Each requested selection produces a complete, non-interleaved success or error row:
{"status":"ok","filename":"1111.cif","id":"bc","chains":["B","C"],"total_area":1234.5,"atom_areas":[12.3,0]}
{"status":"err","filename":"1111.cif","id":"missing","chains":["Z"],"error":"selected chain not found: Z"}
Success rows always include filename, id, chains, and total_area.
atom_areas follows [output.jsonl].atom_areas, and residue arrays follow
[calculation].residue_map. Missing map rows, missing input structures,
missing selected chains, and per-selection calculation failures are emitted as
error rows without discarding other selections for the same structure.
For stable atom joins across selections, enable identity metadata together with atom areas:
[output.jsonl]
atom_areas = true
atom_identity = true
This adds parallel source_atom_index, atom_chain, atom_residue_name,
atom_residue_number, atom_insertion_code, atom_name, and atom_element
arrays. source_atom_index is zero-based in the once-parsed source atom array
after configured model, alternate-location, hydrogen, and HETATM filtering. It
therefore aligns atoms across A, B+C, and A+B+C rows from the same source and
settings. atom_element supports downstream polar/apolar classification
without adding this metadata when atom output is disabled. atom_identity
currently applies only to chain_map jobs and requires atom_areas = true.
For mmCIF and BinaryCIF, label matches _atom_site.label_asym_id and auth
matches _atom_site.auth_asym_id. PDB has one chain-ID field, so label and
auth select the same value for PDB input.
Filenames must be basenames that exactly match files in the input directory,
including their structure and compression extensions. Every discovered
structure must have one map entry; a missing entry is written as a failed batch
result. Keep a JSON chain map outside the input directory because .json is
also a supported structure-input extension. Missing/duplicate IDs, mixed
asym_id_type values for one filename, empty chain lists, and jobs that specify
both chains and chain_map are rejected. Chain maps are supported for PDB,
mmCIF, and BinaryCIF inputs, not JSON atom arrays or SDF molecule batches.
BSA / ΔSASA Analysis
Batch workflows can also write an analysis JSONL file for a two-partner interface:
version = 1
kind = "workflow"
[input]
dir = "structures"
[output]
dir = "results"
format = "jsonl"
[calculation]
algorithm = "sr"
n_points = 100
threads = 8
[classifier]
type = "ccd"
[analysis]
type = "bsa"
name = "interface_ab"
partner_a = ["A"]
partner_b = ["B"]
level = "residue"
When the two partner groups differ by structure, replace partner_a and
partner_b with an analysis chain_map:
[analysis]
type = "bsa"
name = "interfaces"
chain_map = "interfaces.csv"
level = "residue"
CSV interface maps use one comma-separated chain set for each partner:
filename,id,partner_a,partner_b,asym_id_type
1abc.cif,interaction-001,"A,B","C,D",auth
1abc.cif,interaction-002,E,"C,D",auth
2xyz.cif,interaction-003,A,"B,C",label
The equivalent JSON is:
[
{
"filename": "1abc.cif",
"id": "interaction-001",
"partner_a": ["A", "B"],
"partner_b": ["C", "D"],
"asym_id_type": "auth"
},
{
"filename": "1abc.cif",
"id": "interaction-002",
"partner_a": ["E"],
"partner_b": ["C", "D"],
"asym_id_type": "auth"
}
]
Multiple rows may name the same input file. id is required for every row
when a filename occurs more than once and must be unique across the map. A
legacy one-row-per-file map may omit id; its stable output ID defaults to the
filename. Fixed partner_a/partner_b workflows also use the filename as the
interface ID. All rows for one file must use the same asym_id_type, allowing
zsasa to read, decompress, parse, and classify that structure once before
processing its interfaces.
For BSA workflows, threads and --threads=N control the number of structure
files processed concurrently. Explicit values may exceed the detected CPU
count, which is useful for large I/O-bound datasets. In the multi-file worker
path, each partner A, partner B, and complex SASA calculation uses one internal
thread to avoid nested oversubscription; all interfaces for a structure remain
serial and reuse the same parsed and classified input. When only one file or
one worker is available, the configured thread count is instead available to
the SASA calculations within that file.
BSA workflows show standard terminal progress by completed structure file, not
by interface row. Each discovered file advances progress once after all of its
interfaces finish, including files that produce parse, classification,
selection, or calculation errors. Set quiet = true or pass --quiet to
suppress progress. The standard progress display requires stderr to be a
terminal: redirecting stderr, including with 2>&1 | tee, disables the
interactive bar, while piping stdout alone leaves the bar on the terminal but
does not capture it in tee.
BSA JSONL rows from different files are streamed in completion order, so global input order is not guaranteed. Rows are written atomically, and interface order within each file follows the interface-map order.
For the first row, zsasa calculates isolated A+B, isolated C+D, and the A+B+C+D complex. The reported values are therefore:
delta_sasa_total = sasa(A+B) + sasa(C+D) - sasa(A+B+C+D)
bsa = delta_sasa_total / 2
asym_id_type defaults to label and may be set independently for each
mmCIF or BinaryCIF file. Fixed partner_a/partner_b settings and
analysis.chain_map are mutually exclusive.
Run it with:
zsasa batch --workflow bsa.toml
This writes results/interface_ab.jsonl, with one row per requested
interface. Each success row has status = "ok" and its stable id. Invalid
chain selections and read, parse, classification, or calculation failures are
written as status = "err" rows with the same filename and id. A map row
whose source file is missing also produces an error row. A discovered file
without a map entry uses its filename as the error-row ID.
The workflow computes partner A, partner B, and the AB complex internally, then reports:
delta_sasa_total = sasa_partner_a + sasa_partner_b - sasa_complex
bsa = delta_sasa_total / 2
ΔSASA and BSA are deliberately separate fields. delta_sasa_total,
residue_delta_sasa, and atom_delta_sasa are not halved; bsa is the
two-partner buried surface area after the 1/2 factor.
BSA analysis JSONL uses analysis-specific fields such as sasa_partner_a,
sasa_partner_b, sasa_complex, delta_sasa_total, and bsa. With
level = "residue", parallel residue arrays include residue_partner,
residue identity, residue_sasa_isolated, residue_sasa_complex, and
residue_delta_sasa.
Atom detail is disabled by default because it can greatly increase JSONL volume. Enable it only with residue detail:
[analysis]
type = "bsa"
chain_map = "interfaces.csv"
level = "residue"
atom_output = true
Atom-detail rows include atom_index (zero-based in the selected interface
complex), atom_partner, chain, residue and atom identity, element,
atom_sasa_isolated, atom_sasa_complex, and
atom_delta_sasa. Atom and residue ΔSASA arrays reconcile to the unhalved
delta_sasa_total within floating-point tolerance before optional JSONL
decimal rounding. Polar/apolar decomposition
is not emitted; the atom metadata is available for downstream classification.
The schema does not use normal SASA JSONL total_area and atom_areas fields,
because those names are ambiguous for interface analysis.
Override Precedence
When the same setting appears in multiple places, zsasa applies this order:
built-in defaults < workflow settings < job settings < explicit CLI options
For example, this command uses the workflow but overrides the thread count:
zsasa batch structures/ results/ --workflow bsa.toml --threads=16
Workflow Jobs vs CLI Chain Filters
Use CLI flags for a single ad hoc chain-filtered batch run:
zsasa batch structures/ results/ --chain=A
Use workflow jobs for named, repeatable multi-chain analyses:
[[jobs]]
name = "chain_a"
chains = ["A"]
[[jobs]]
name = "complex_ab"
chains = ["A", "B"]
Custom Classifier Configs
Custom classifier configs are TOML-only. In batch workflows, set them in the workflow classifier section:
[classifier]
type = "custom"
config = "my_classifier.toml"
For single calc commands, you can also use the CLI option:
zsasa calc --config=my_classifier.toml structure.cif output.json
Residue Maps
Set residue_map = true under [calculation] to add compact residue arrays to JSONL rows:
[output]
format = "jsonl"
[calculation]
residue_map = true
This is equivalent to passing --residue-map with --format=jsonl in non-workflow batch mode.
JSONL Output Options
Workflow files can tune batch JSONL output under [output.jsonl]:
[output]
dir = "results"
format = "jsonl"
[output.jsonl]
atom_areas = false # omit per-atom SASA arrays
atom_identity = false # chain-map-only stable atom identity arrays
total_area = true # keep per-structure totals
decimals = 3 # round JSONL floating-point values
metadata = "sidecar" # none | sidecar
Defaults preserve the CLI JSONL schema: atom_areas = true,
atom_identity = false, total_area = true, full precision, and no metadata
sidecar. atom_identity is available only for chain-map jobs and requires atom
areas. Selection-map JSONL also requires total_area = true. When
metadata = "sidecar" is set, workflow batch jobs write a <job>.meta.json
file next to <job>.jsonl with the effective JSONL and calculation settings.