Policies¶
Canonicalisation¶
canonicalise_structure(structure, policy) never guesses. Every normalization decision, how chains are named, how altlocs are resolved, whether ligands are kept, comes from a canonicalisationPolicy (pandora.schemas.canonicalisation.canonicalisationPolicy). Each rule group has its own default below — most fall back to "preserve as reported", but not all: missing_atoms/missing_residues default to annotate and altloc defaults to select_best_occupancy, so an empty policy still records transforms, not just provenance. See each rule group's default below.
Canonicalisation keeps every verbatim category in Structure.raw, except one case: when a run changes which chains or residues exist or what they are called, categories with a column that refers to chains or residues by ID (any column name containing asym_id, seq_id or strand_id) are removed, because they would contradict the new atoms. They are listed in canonicalisationProvenance.dropped_raw_categories. See Trace what changed.
from pandora.schemas.canonicalisation import canonicalisationPolicy
policy = canonicalisationPolicy(
policy_id="my-policy",
policy_name="My Policy",
policy_version="1.0.0",
description="", # optional, free text
)
identifier_rules¶
chain_id.strategy (default preserve)
| Value | Description |
|---|---|
preserve |
Keep the original label_asym_id as the canonical chain ID. |
remap |
Reassign chain IDs to sequential labels (A, B, ... Z, AA, AB, ...). |
use_auth_chain_id |
Use auth_asym_id as the canonical chain ID (falls back to label_asym_id if no author ID is set). A polymer and its ligands/waters usually share one author ID, so the first asym unit gets the plain ID and later ones get a suffix (A, A_2, A_3, ...). |
residue_numbering.strategy (default preserve)
| Value | Description |
|---|---|
preserve |
Keep label_seq_id as the canonical seq_id. |
use_auth_seq |
Use the numeric part of auth_seq_id as the canonical seq_id. |
renumber |
Renumber residues sequentially from 1 per chain, in order of first appearance, ignoring gaps in the original numbering. |
Only polymer residues are numbered. Ligands and waters keep no label_seq_id under every strategy, as mmCIF requires, and get no entry in residue_number_mapping.
residue_numbering.preserve_insertion_codes (default True) only applies
when strategy is preserve or use_auth_seq: True keeps insertion
codes, False strips them. It has no effect under renumber —
renumbering always drops insertion codes.
assembly_id.strategy (default preserve)
| Value | Description |
|---|---|
preserve |
Keep original assembly IDs. |
remap |
Reassign assembly IDs to sequential integers (1, 2, ...) in existing order. |
standardize |
Intended to put the preferred biological assembly first, then renumber the rest. |
remap and standardize currently behave identically — both just renumber sequentially in file order. The "preferred assembly goes first" behavior implied by standardize isn't implemented yet.
missing_data_rules¶
missing_atoms.strategy (default annotate) — a residue is "missing atoms" if any backbone atom is absent: N/CA/C/O for protein, O5'/C5'/C4'/C3'/O3' for DNA/RNA (P is not required, since the 5'-terminal nucleotide normally lacks it).
| Value | Description |
|---|---|
preserve |
Keep residues with missing backbone atoms unchanged, no diagnostic. |
annotate |
Keep residues unchanged; emit a MISSING_ATOMS diagnostic per affected residue (if record_missingness). |
drop_partial_residue |
Remove residues missing any backbone atom (also diagnosed like annotate). |
impute |
Intended to fill missing atom coordinates from ideal geometry, gated by allow_imputation. Not implemented yet — currently identical to preserve. |
missing_residues.strategy (default annotate) — a "gap" is a jump of more than 1 in a chain's seq_id sequence.
| Value | Description |
|---|---|
preserve |
Keep sequence gaps as-is; emits SEQUENCE_GAP diagnostics only if record_gaps. |
annotate |
Same effect as preserve — gaps are flagged, never modified. |
drop_chain_segment |
Remove every atom belonging to a chain that contains any gap. |
impute |
Intended to model missing residues from sequence + ideal geometry, gated by allow_imputation. Not implemented yet — no impute branch exists; falls through with the same behavior as preserve/annotate. |
incomplete_chains.strategy (default preserve) — "incomplete" means the chain's residue range has gaps between its first and last residue.
| Value | Description |
|---|---|
preserve |
Keep chains regardless of completeness. |
exclude |
Drop every asym unit/atom belonging to an incomplete chain. |
truncate_to_complete_regions |
Keep only the single longest contiguous run of residues per incomplete chain. |
Extra flags:
| Field | Description | Default |
|---|---|---|
missing_atoms.allow_imputation |
Precondition for strategy: impute. Currently unused since impute isn't implemented. |
False |
missing_atoms.record_missingness |
Emit a MISSING_ATOMS diagnostic for every affected residue. |
True |
missing_residues.record_gaps |
Emit a SEQUENCE_GAP diagnostic for every detected gap. |
True |
altloc_rules¶
strategy (default select_best_occupancy)
| Value | Description |
|---|---|
preserve |
Keep every altloc variant; no atoms are dropped. |
select_best_occupancy |
Per residue, pick the altloc group with the highest mean occupancy; ties broken by tie_breaker. |
select_first |
Pick the alphabetically-first altloc label. |
select_user_defined |
Pick user_defined_altloc if it's one of the residue's available altlocs, otherwise fall back to the alphabetically-first one. |
tie_breaker (default alphabetical_first) — only consulted when strategy: select_best_occupancy has a tie.
| Value | Description |
|---|---|
alphabetical_first |
Lowest altloc label. |
alphabetical_last |
Highest altloc label. |
lowest_b_factor |
Lowest mean B_iso_or_equiv across the tied groups. |
highest_b_factor |
Highest mean B_iso_or_equiv across the tied groups. |
user_defined_altloc (default None) — the altloc code to select under strategy: select_user_defined.
record_selection (default True) — populate AltlocSelectionMapping for every residue with more than one altloc, regardless of strategy. This flag alone controls altloc mapping; it is independent of provenance_rules.record_original_mappings (which governs the other four mapping types).
assembly_rules¶
strategy (default preserve_as_reported)
| Value | Description |
|---|---|
preserve_as_reported |
Keep assemblies as reported (aside from ID renumbering under identifier_rules.assembly_id). |
standardize_biological_assembly |
Select the assembly identified by preferred_assembly_source, then materialize it in place of the asymmetric unit: every chain its generators list, under every operator they list. A chain under the identity operator keeps its ID; every other operator adds a transformed copy as a new chain (<chain>_<operator id>). Chains the assembly doesn't list are dropped, with their connections and secondary structure. The resulting Structure.assemblies is collapsed to a single record with empty generators/operators, since the assembly is now concrete atoms rather than an instruction to re-derive it; its deposited oligomeric_count is kept, as it already describes the whole assembly. |
select_first_assembly |
Keep only the first listed assembly, discard the rest. |
preferred_assembly_source (default author)
| Value | Description |
|---|---|
author |
Use the assembly gemmi flags as author_determined (from _pdbx_struct_assembly.details). |
pdbe |
Use the PDBe-annotated preferred biological assembly. No external metadata source is wired up yet — falls back to the author/pdbx behavior below and records an ASSEMBLY_SOURCE_UNAVAILABLE diagnostic warning. |
pdbx |
Use the pdbx_struct_assembly preferred flag from the mmCIF file — in practice, whichever assembly gemmi flags as author_determined, else software_determined, else the first listed. |
first |
Use the first listed assembly (assembly 1 in PDB files), ignoring the author/software flags. |
record_original_assembly_mapping (default True) — populate AssemblyMapping for every assembly, including chain_copies (which new chain came from which source chain + operator) when standardize_biological_assembly generated any.
Chains generated by symmetry expansion are also propagated into Structure.connections and Structure.secondary_structure: a bond or secondary-structure span is duplicated onto a generated chain whenever every chain it references was copied under the same symmetry operator (trivially true for a span/bond within a single chain, since a rigid-body copy preserves internal distances and residue ranges exactly). A connection between two chains copied under different operators — or where only one side has a generated copy — has no single valid rigid-body remap and is left unduplicated, rather than fabricating a bond that may not exist in the assembled complex.
entity_rules¶
strategy (default preserve)
| Value | Description |
|---|---|
preserve |
Keep entities and their original IDs. |
standardize |
Renumber entity IDs sequentially (1, 2, ...). |
merge_equivalent_entities |
Merge entities that share an identical canonical polymer sequence (pdbx_seq_one_letter_code_can) into one entity ID. Non-polymer entities (no canonical sequence) are never merged with each other, even if their composition matches — only sequence-based merging for polymers is implemented. With standardize or this strategy, the entity_id column of verbatim raw categories (e.g. _entity_poly_seq) is rewritten to the canonical ids, and the rows of an entity merged into an earlier one are dropped. |
preserve_original_entity_ids (default True) — under strategy: standardize, controls whether EntityMapping records the pre-renumber ID (True) or just the new ID again (False).
ligand_rules¶
strategy (default preserve)
| Value | Description |
|---|---|
preserve |
Include every non-polymer entity as-is, including waters — see warning below. |
filter |
Apply keep_waters / keep_ions / keep_nonpolymer_ligands to decide what to drop. |
annotate_only |
Record ligands in diagnostics but exclude them from the canonical structure. |
keep_waters / keep_ions / keep_nonpolymer_ligands (default True) — only consulted under strategy: filter.
Ion detection is a keyword heuristic, not a chemical-component lookup: an entity counts as an ion only if its pdbx_description contains the whole word ION, following the PDB's own naming (ZINC ION, SULFATE ION). Ligands that merely mention an element or anion (ADENOSINE TRIPHOSPHATE, IRON/SULFUR CLUSTER) count as non-polymer ligands. An ion with a description lacking ION falls into keep_nonpolymer_ligands instead of keep_ions.
preserve keeps waters too
strategy: preserve returns every atom unchanged, including waters —
keep_waters/keep_ions/keep_nonpolymer_ligands are only consulted
under strategy: filter. If you want waters excluded, use
strategy: filter with keep_waters: false, not preserve.
modified_residue_rules¶
strategy (default preserve)
| Value | Description |
|---|---|
preserve |
Keep modified residues (e.g. MSE) as reported. |
map_to_parent |
Rename each modified polymer residue in comp_ids to its parent from _pdbx_struct_mod_residue. MSE always maps to MET, and its SE atom becomes SD (element S). Bonds (struct_conn) and _entity_poly_seq at the renamed positions follow the same rename. Each rename is recorded in CanonicalMappings.modified_residue_mapping. |
comp_ids (default ["MSE"]): which residues to map. An empty list maps every modified polymer residue with a known parent. A residue with no known parent is left unchanged and gets a MODIFIED_RESIDUE_UNMAPPED warning; Pandora doesn't guess a parent.
This step runs first, before chain ids are normalised, so the ids still match _pdbx_struct_mod_residue.
validation_rules¶
strictness (default moderate)
| Value | Description |
|---|---|
strict |
Promote every warning diagnostic to an error. |
moderate |
Default behavior — errors stay errors, warnings stay warnings. |
permissive |
Intended to downgrade error-level rules to warnings. Not implemented yet — accepted but has no effect. |
fail_on_unresolved_issues (default True) — if True, any unresolved error causes status == "failed".
warnings_as_errors (default False) — if True, any warning also causes status == "failed".
provenance_rules¶
| Field | Description | Default |
|---|---|---|
record_original_mappings |
Populate the chain/residue/assembly/entity mapping objects; when False, those four come back empty (smaller output). Does not affect AltlocSelectionMapping — that's gated only by altloc_rules.record_selection, independently of this flag. |
True |
record_transforms |
Intended to gate whether applied transforms are logged. Currently unused — transforms are always recorded. | True |
record_policy_application |
Intended to embed the full applied policy in the result. Currently unused — the policy is never embedded. | True |
emit_canonicalisation_report |
When True, include a warning/error count summary in canonicalisationProvenance.report. |
False |
Naming drift from the design spec
The design spec in docs/policies/canonicalisation.yaml names the report
flag emit_canonicalization_report (American spelling); the
implemented schema field is emit_canonicalisation_report (British,
matching the rest of the codebase). Use the schema's spelling.
Example: three policies, one structure¶
from pandora.schemas.canonicalisation import (
canonicalisationPolicy,
IdentifierRules,
ChainIdRules,
ResidueNumberingRules,
AltlocRules,
AssemblyRules,
EntityRules,
LigandRules,
)
# Preserve everything as reported (including waters — see ligand_rules above).
preserve_policy = canonicalisationPolicy(
policy_id="preserve",
policy_name="Preserve As Reported",
policy_version="1.0.0",
identifier_rules=IdentifierRules(
chain_id=ChainIdRules(strategy="preserve"),
residue_numbering=ResidueNumberingRules(strategy="preserve"),
),
altloc_rules=AltlocRules(strategy="select_best_occupancy"),
ligand_rules=LigandRules(strategy="preserve"),
)
# Remap chain IDs and renumber residues sequentially.
remap_policy = canonicalisationPolicy(
policy_id="remap",
policy_name="Remap Chains And Renumber",
policy_version="1.0.0",
identifier_rules=IdentifierRules(
chain_id=ChainIdRules(strategy="remap"),
residue_numbering=ResidueNumberingRules(strategy="renumber"),
),
altloc_rules=AltlocRules(
strategy="select_best_occupancy", tie_breaker="lowest_b_factor"
),
entity_rules=EntityRules(strategy="merge_equivalent_entities"),
ligand_rules=LigandRules(
strategy="filter", keep_waters=False, keep_ions=False
),
)
# Keep author-assigned identifiers, strict validation.
author_policy = canonicalisationPolicy(
policy_id="author",
policy_name="Author Identifiers",
policy_version="1.0.0",
identifier_rules=IdentifierRules(
chain_id=ChainIdRules(strategy="use_auth_chain_id"),
residue_numbering=ResidueNumberingRules(strategy="use_auth_seq"),
),
altloc_rules=AltlocRules(strategy="select_first"),
assembly_rules=AssemblyRules(
strategy="standardize_biological_assembly",
preferred_assembly_source="author",
),
ligand_rules=LigandRules(strategy="annotate_only"),
)
(This is the same set of policies used in examples/overview.py, minus
the no-op keep_waters=False under the preserve policy — see the
warning above.)
Curation¶
curate_structure(structure, metadata, policy) applies a DatasetCurationPolicy. Entry rules exclude the whole entry; chain rules remove failing chains and keep the rest. An entry left with no polymer chain is excluded as NO_CHAINS_LEFT.
Order: entry rules (resolution, method, R-factors, organism, non-standard residues, max_atoms), then chain rules, then min_chain_length and min_polymer_chains on what is left, then content rules.
When chain rules or content rules remove chains, the Structure.raw categories that refer to chains or residues by ID are removed too, as in canonicalisation, so the curated structure still writes a valid mmCIF file. They are listed in CurationProvenance.dropped_raw_categories.
quality_rules¶
| Field | Default | Level | Description |
|---|---|---|---|
max_resolution |
null |
entry | Maximum resolution (Å). Cryo-EM resolution comes from _em_3d_reconstruction. |
max_resolution_by_method |
{} |
entry | Per-method limits, e.g. {"X-RAY DIFFRACTION": 2.5, "ELECTRON MICROSCOPY": 2.0}. Overrides max_resolution for entries with that method; with several matching methods the strictest wins. Names match regardless of case and whitespace, here and in every method list. |
null_resolution_behavior |
exclude |
entry | What happens to an entry with no resolution when a limit applies. |
include_experimental_methods / exclude_experimental_methods |
[] |
entry | Method allow/deny lists. An entry with several methods is kept if any of them is in the include list, and excluded if any is in the exclude list. |
rfactor_methods |
["X-RAY DIFFRACTION"] |
entry | Methods the R-factor rules apply to. Other methods (cryo-EM, NMR) are never checked, since they have no R-factors. An entry with no method recorded is not checked. |
max_r_free |
null |
entry | Maximum Rfree. |
max_r_free_gap |
null |
entry | Maximum |Rfree − Rwork|. |
max_r_sym |
null |
entry | Maximum Rsym (_reflns.pdbx_Rsym_value), falling back to Rmerge (_reflns.pdbx_Rmerge_I_obs), the same statistic under another name. |
null_rfactor_behavior |
include |
entry | What happens when an active R-factor rule's value is missing. Defaults to include because many X-ray entries lack Rsym. |
exclude_nonstandard_residues |
false |
entry | Exclude an entry with any polymer residue that isn't one of the 20 amino acids, UNK, or a standard nucleotide. Runs on the canonical structure, so MSE mapped by modified_residue_rules counts as MET. |
allowed_nonstandard_residues |
[] |
entry | Residues that don't trigger the rule above. |
max_atoms |
null |
entry | Maximum atoms in the entry. Checked after parsing, so it protects later steps (contacts, export) but not the parse itself. |
missing_residue_definition |
incomplete_backbone |
chain | unobserved: a residue is missing when it has no atoms. incomplete_backbone: also when any backbone atom is missing: N, CA, C, O for proteins; O5', C5', C4', C3', O3' for nucleic acids (P is left out, since the 5'-terminal residue normally lacks it). |
max_missing_tail_fraction |
null |
chain | Remove a chain whose missing N- plus C-terminal residues exceed this fraction of its SEQRES length. |
max_missing_middle_fraction |
null |
chain | Remove a chain whose missing residues between its first and last present residue exceed this fraction of SEQRES length minus the missing tails. |
max_chain_length |
null |
chain | Remove a chain with more observed residues than this. |
min_chain_length |
null |
entry | Exclude the entry if no remaining chain reaches this many residues. |
min_polymer_chains |
null |
entry | Exclude the entry if fewer polymer chains remain. |
When a missing-fraction rule is set, a chain whose completeness can't be measured (no _entity_poly_seq, or residues that don't line up with it, for example after renumbering) is removed as NO_SEQRES. Use chain_completeness() to inspect the numbers directly.
Similarity hit filter¶
HitFilter (pandora.schemas.similarity.HitFilter) decides which MMseqs2/Foldseek hit rows become similarity edges when clustering (iter_edges(), cluster_similar_items()). A pair of items is an edge if any single hit row passes every threshold. None thresholds aren't applied; the others are inclusive (>=). See Similarity for usage.
| Field | Default | Description |
|---|---|---|
min_score |
None |
Minimum score: TM-score for Foldseek (see tm_normalisation), identity for MMseqs2. |
min_identity |
None |
Minimum fraction of identical aligned residues. |
min_coverage |
None |
Minimum alignment coverage (see coverage_of). |
coverage_of |
both |
Which coverage is tested: both (the smaller of query and target), query, target, or either (the larger). |
min_interface_coverage |
None |
Minimum fraction of each side's interface residues inside the alignment. Foldseek only; needs interface_residues, otherwise iter_edges() raises ValueError. |
tm_normalisation |
alignment |
Foldseek TM-score used as the score: alignment (alntmscore), query (qtmscore), target (ttmscore), or max (the larger of the two). Must be alignment for MMseqs2 searches. |
Provenance¶
Every rule that deviates from its "preserve" default is recorded as a transform label (e.g. "chain_id:remap") in the returned canonicalisationProvenance.transforms, alongside the policy id/name/ version and, if provenance_rules.emit_canonicalisation_report is set, a report of warning/error counts. The original-to-canonical identifier mappings themselves come back separately as CanonicalMappings.