MolFrame

Query language

Describe molecular subsets with MolFrame's textual, typed, and reusable selection language.

A MolFrame query describes atoms by their structural identity, chemistry, or geometry. Compile it once when you will reuse it; evaluate it with Structure.select() whenever you need the matching atoms.

Structure.select() returns a structure-bound Selection. It is cheap to pass to another MolFrame operation; call to_coordinates() only when an API requires a gathered coordinate array.

Start with structural selectors

The built-in chemistry terms are the quickest way to state common molecular sets:

QuerySelects
proteinProtein atoms.
ligandLigand atoms.
water / ionWater or ion atoms.
polymerPolymer atoms.
backbone / sidechainProtein backbone or side-chain atoms.
nucleicNucleic-acid atoms.
heavy / hydrogenNon-hydrogen or hydrogen atoms.
polar_hydrogenHydrogen bonded to N, O or S.
nonpolar_hydrogenAll other hydrogen, including unbonded H.
helixAll helices, including PPII and deposited other helices.
strand / sheetBeta ladders only / ladders plus isolated bridges.
alpha_helix / helix_310 / pi_helix / polyprolineOne exact helix state.
bridge / turn / bend / coilOne exact state; coil excludes unassigned residues.

Hydrogen polarity uses the full recorded bond topology even when a preceding filter selects only hydrogen. A hydrogen with any N/O/S neighbour is polar; hydrogen bound only to carbon, phosphorus, halogens or other elements is nonpolar. These are complementary display-selection classes, not a hydrogen-bond donor/acceptor analysis. No bonds are inferred by the query: unavailable connectivity raises MOLFRAME-E4003, whereas a known-empty graph makes all explicit hydrogen nonpolar.

Text, compiled queries and typed expressions share this classification:

polar = structure.select("polar_hydrogen")
nonpolar = molframe.Query("nonpolar_hydrogen").select(structure)
display_atoms = structure.select(~molframe.sel.nonpolar_hydrogen())

Secondary selectors read the authoritative per-residue column without recomputing analysis. Rust col and Python molframe.sel expose the same no-argument names. Python Structure.secondary_structure returns typed SecondaryStructure values: stable codes 0–9 are unchanged and PolyProline appends code 10. molframe.analysis.dssp(structure) computes a native DSSP table with residue and kind code columns.

Canonical CIF and PDB writers project helix classes 1, 3, 5 and 10 and sheet ranges from that column. Other deposited helices remain OtherHelix, not alpha helices. Canonical output uses class 2 for that unspecified class; document-preserving CIF output retains the original deposited class number. CIF also writes turns. Coil, bend and isolated bridge states have no faithful legacy HELIX/SHEET encoding and are not relabelled as helices or strands. PDB output regenerates secondary ranges after chain remapping and selection. PDB endpoints include the deposited insertion code and decimal or hybrid-36 residue number, including negative numbers and blank chains. Both endpoints must resolve uniquely inside one chain; ranges follow deposited residue order, never a numeric approximation. Model-local PDB annotations stay with their model; a dense shared topology uses the first model's local annotations. Canonical writers refuse ambiguous projected residue identities before emitting bytes. CIF secondary endpoints use label identities only, not author aliases. Independent ragged-model secondary annotations require writing a model snapshot: global secondary categories cannot encode their model-specific identity.

Columns constrain structural identifiers. A column can accept one or more values:

chain A
resname HEM
name CA
(element N or element O) and protein

Use chain, resid, resname, name, and element for ordinary structure work. MolFrame also exposes label and author variants such as label_chain / auth_chain and label_resid / auth_resid when a format carries both identifier systems.

A plain chain, resname or name follows the policy's identifier namespace (author names by default). A value can also name its namespace itself with a label: or auth: prefix, so chain label:A selects the same atoms as label_chain A, and chain label:A auth:C is their union. analysis.chain_interface reads its chain names the same way: a bare name that means different chains as a label and as an author id is refused, and "label:A" or "auth:A" resolves it.

Combine terms deliberately

not binds most tightly, then and, then or. Parenthesize a mixed expression so its scientific meaning remains obvious.

protein and name CA
(chain A or chain B) and not water
protein and (name CA or name CB)

all and none are explicit complete and empty selections. Query keywords are case-insensitive; identifier values retain the meaning supplied by the structure.

Expand a molecular neighborhood

Spatial predicates take distances in Å. within matches atoms near a target; byres expands those atoms to their complete residues.

A spatial predicate reads its target and neighbours from the whole structure (or the view the query runs in), never from what the other side of an and has already kept, so chain A and within 4 of chain B and within 4 of chain B and chain A select the same atoms: the atoms of A that lie within 4 Å of B. Use global only to escape an enclosing view.

pocket = structure.select("byres (within 5 of resname HEM) and protein")

Other useful structural expansions are same residue as QUERY, same chain as QUERY, and bonded QUERY (or bonded DEPTH QUERY). Keep the radius and the target in reports: a spatial selection is defined by both.

Filter numeric properties

Numeric columns support <, <=, >, >=, =, and !=. Common properties include bfactor, occupancy, charge, mass, plddt, and Cartesian x, y, z coordinates.

bfactor > 40
occupancy >= 0.5 and not altloc B
plddt < 50 and protein

Exact floating-point equality is rarely a useful scientific criterion. Prefer a range or threshold when a property originated in a calculation.

Select a copy in a materialised assembly

A materialised biological assembly tags every atom with the 0-based instance id of the chain copy it belongs to. assembly selects by that id and accepts the same numbers and ranges as the other numeric columns:

assembly 1
assembly 0:3 and protein
same assembly as (resname HEM)

On a structure that was not materialised from an assembly there is no instance id to read, so assembly fails with MOLFRAME-E4003 (annotation molframe.instance_id) instead of returning an empty selection.

Reuse a compiled query

Query validates text once and has a stable fingerprint. It is the right form for a filter used repeatedly across one or more structures.

import molframe

pocket_query = molframe.Query("byres (within 5 of resname HEM) and protein")
pocket = pocket_query.select(structure)
# The equivalent convenience call is structure.select(pocket_query).

The typed builders in molframe.sel describe the same query model without parsing a string:

from molframe import sel

heme = sel.residue("HEM")
pocket_query = sel.residues_within(5.0, heme) & sel.protein()
pocket = structure.select(pocket_query)

Name a query definition

Use QueryAliases when one query should refer to another. References use $name; resolution returns a closed query, detects unknown names and cycles, and never depends on atoms selected by an earlier evaluation.

aliases = molframe.QueryAliases()
aliases.define("heme", molframe.Query("resname HEM"))
aliases.define("pocket", molframe.Query("byres (within 5 of $heme) and protein"))

pocket_query = aliases.resolve(molframe.Query("$pocket"))
pocket = structure.select(pocket_query)

Completion and byte offsets

The native completion API uses UTF-8 byte offsets, not Unicode character or code-point indexes. Pass a cursor in the range 0..=source.len(). A cursor in the middle of a UTF-8 scalar is rejected; returned replacement spans are byte ranges into the original source. This keeps editor replacements aligned with parser diagnostics and Rust string slices. Python and notebook adapters should convert their editor cursor to a UTF-8 byte offset before calling the native API and convert the returned start/end offsets back to their editor convention only at the UI boundary.

After a value column such as chain, resname, or name, query.complete takes candidates from an optional structure argument instead of any client-maintained keyword list. The native layer lends identifiers from the structure's own indexes, so no second vocabulary is built and duplicate identifiers are collapsed. Items typed value are structure-derived; keyword, column, macro, and alias come from the language registry.

Handle a query failure

Query("…") and Structure.select("…") raise when the expression cannot compile. Preserve the original source text beside the exception: it identifies exactly which scientific subset needs correction.

Visualize a query in MolGFX

MolGFX uses this exact language for representation targets and Workbench selections. See Query-driven visualization for the select → show → color → focus workflow.

Implementation and verification status

The parser, typed builders, alias resolver, logical and geometric selectors, and UTF-8 byte-offset completion contract are implemented at the MolFrame query boundary. A dollar-prefixed name is a query alias; an at-prefixed layer name is a separate MolGFX command-language concept. Invalid input remains a diagnostic rather than silently selecting an alternate parser.

The original query examples in this page are documentation fixtures for the structural, boolean, spatial, numeric, alias, and builder forms. They are preserved verbatim; successful browser, Python, and Workbench execution has not been established by this documentation update.

The required verification commands are listed in molframe/AGENTS.md. No formatter, linter, or test command was run for this documentation-only change. Completion replacement spans (including non-ASCII cursor conversion), W4003 unmatched-span diagnostics, dynamic selection invalidation, concurrent native query-result ownership, and every original example still require an observed end-to-end run.

On this page