Skip to content

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.
min_score: 0.5
min_coverage: 0.8
coverage_of: both
tm_normalisation: max

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.

Comment