Files
Jungfraujoch/gemmi_gph/gemmi/pymol_select.hpp
T
leonarski_f dd0bffb283
Build Packages / Unit tests (push) Skipped
Build Packages / build:windows:nocuda (push) Successful in 11m6s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 10m27s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 10m54s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 9m25s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 10m5s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 11m33s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 11m19s
Build Packages / build:rpm (rocky8) (push) Successful in 12m23s
Build Packages / build:rpm (rocky9) (push) Successful in 13m21s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 12m30s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 11m55s
Build Packages / DIALS test (push) Successful in 13m42s
Build Packages / XDS test (durin plugin) (push) Successful in 9m26s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 6m41s
Build Packages / XDS test (neggia plugin) (push) Successful in 6m12s
Build Packages / Generate python client (push) Successful in 19s
Build Packages / Build documentation (push) Successful in 52s
Build Packages / Create release (push) Skipped
Build Packages / build:viewer-tgz:cpu (push) Successful in 5m29s
Build Packages / build:viewer-tgz:cuda (push) Successful in 6m12s
Build Packages / build:windows:cuda (push) Successful in 18m36s
v1.0.0-rc.159 (#69)
This is an UNSTABLE release. It includes many experimental features, as well as many AI generated fixes. We recommend using rc.152 for production use.

* rugnux: Add `--model model.pdb` - score the merged data against an atomic model and compute initial maps. It reports R-work/R-free (scaling the model to the observed amplitudes with an overall scale, an anisotropic B and a flat bulk solvent - the standard few-parameter model, so a batch of maps stays directly comparable) and writes 2Fo-Fc / Fo-Fc electron-density maps (CCP4) plus a map-coefficient MTZ. The structure itself is not refined; the model is only re-fractionalised into the data cell.
* rugnux: The merged reflection output now carries French-Wilson amplitudes (|F| and its sigma) next to the intensities - MTZ `F`/`SIGF`, mmCIF `_refln.F_meas_au`, and the text HKL - computed with the correct centric/acentric Wilson prior and epsilon multiplicity, so a downstream program (e.g. phenix.refine) can refine against amplitudes. The intensity columns are unchanged.
* rugnux: R-free test-set flags are now assigned deterministically and consistently across symmetry - a Bijvoet pair I(+)/I(-) is never split between the work and free sets, and the assignment is a reproducible per-hkl hash that depends only on the reflection index, so every dataset of one crystal form gets the same ~5% free set (what a multi-dataset campaign such as PanDDA needs). On small data the fraction is floored so the test set stays large enough for a stable R-free (~500 reflections, capped at 10%); it stays flat at 5% on ordinary data. When a reference MTZ carries a `FreeR_flag` column its test set is imported instead, letting a whole campaign inherit one shared free set.
* rugnux: A reference MTZ (`--reference-mtz`) can now fix the space group and cell for rotation data too (previously rejected), without being used to scale - the rotation merge stays self-consistent. When the crystal has an indexing (merohedral) ambiguity - a lattice symmetry higher than its Laue symmetry, e.g. P3/P4/P6/C2 - the reference also resolves it: each candidate reindexing (identity plus the twin-law cosets of the metric symmetry) is scored by its intensity correlation against the reference and the data are re-merged in the best-correlating one. This is a metric-preserving relabelling of hkl (the cell is unchanged) and a no-op for a holohedral crystal such as lysozyme.
* rugnux: `--model` validation now aligns the data to the model before scoring - the observed reflections are reindexed into the model's enantiomorph when the two differ only by hand (indistinguishable from merged intensities). A merohedral indexing ambiguity is resolved against the reference MTZ when one is given (so a whole campaign shares one indexing convention); only with a model and no reference does validation fall back to fitting each candidate reindexing and keeping the lowest R-free.
* rugnux: De-novo symmetry - recover a genuine high-symmetry group whose data are imperfectly scaled. Such a merge's within-orbit chi² lands just past the self-consistency bound (each real symmetry step adds a little systematic scatter), right where a merohedral twin also lands, so the chi² ratio alone cannot separate them. The candidate is now rescued when the extra intensity-proportional systematic error it invokes stays small relative to the confirmed subgroup - a genuine symmetry step gains multiplicity without inflating the merge error model's b, whereas a twin forces non-equivalent reflections together and b balloons. Fixes cubic insulin (I23 instead of I222) with no change to any other crystal in the test battery, including the twins that must stay in their lower symmetry.
* Docs: Document the French-Wilson amplitude estimation, R-free flagging, reference-based space-group/ambiguity resolution, and model-based validation/maps in CPU_DATA_ANALYSIS.md.
* Frontend: The status-bar pill now shows a progress bar during detector calibration (previously only during measurement), and the calibration state and its button are labelled "Calibration"/"CALIBRATE" (the internal `Pedestal` state name is unchanged for back-compatibility).Reviewed-on: #69

Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-07-13 13:54:03 +02:00

650 lines
20 KiB
C++

// Copyright Global Phasing Ltd.
//
// PyMOL Selection syntax
#ifndef GEMMI_PYMO_SEL_HPP_
#define GEMMI_PYMO_SEL_HPP_
#include "flat.hpp"
#include "glob.hpp" // for glob_match
#include "third_party/tao/pegtl.hpp" // IWYU pragma: keep
#include <string>
#include <vector>
#include <memory>
#include <iostream>
#include <cstring>
// Only define this if we are not in C++14 mode
#if __cplusplus < 201402L && !defined(_MSC_VER)
namespace std {
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
}
#endif
// ============================================================================
// PHASE 1: Abstract Syntax Tree (AST)
// ============================================================================
namespace gemmi {
namespace psimpl {
struct Node {
virtual ~Node() = default;
virtual bool match(const gemmi::FlatAtom& a) const = 0;
};
// --- Logic Nodes ---
struct AndNode : Node {
std::unique_ptr<Node> left, right;
bool match(const gemmi::FlatAtom& a) const override {
return left->match(a) && right->match(a);
}
};
struct OrNode : Node {
std::unique_ptr<Node> left, right;
bool match(const gemmi::FlatAtom& a) const override {
return left->match(a) || right->match(a);
}
};
struct NotNode : Node {
std::unique_ptr<Node> child;
explicit NotNode(std::unique_ptr<Node> c) : child(std::move(c)) {}
bool match(const gemmi::FlatAtom& a) const override {
return !child->match(a);
}
};
// --- Property Nodes ---
struct ChainNode : Node {
std::vector<std::string> names;
explicit ChainNode(std::vector<std::string> v) : names(std::move(v)) {}
bool match(const gemmi::FlatAtom& a) const override {
for (const auto& n : names)
if (glob_match(n, a.chain_id)) return true;
return false;
}
};
struct ResnNode : Node {
std::vector<std::string> names;
explicit ResnNode(std::vector<std::string> v) : names(std::move(v)) {}
bool match(const gemmi::FlatAtom& a) const override {
for (const auto& n : names)
if (glob_match(n, a.residue_name)) return true;
return false;
}
};
struct AtomNameNode : Node {
std::vector<std::string> names;
explicit AtomNameNode(std::vector<std::string> v) : names(std::move(v)) {}
bool match(const gemmi::FlatAtom& a) const override {
for (const auto& n : names)
if (glob_match(n, a.atom_name)) return true;
return false;
}
};
struct AltLocNode : Node {
char alt;
explicit AltLocNode(char c) : alt(c) {}
bool match(const gemmi::FlatAtom& a) const override {
return a.altloc == alt;
}
};
struct ResiRangeNode : Node {
int min, max;
ResiRangeNode(int a, int b) : min(a), max(b) {}
bool match(const gemmi::FlatAtom& a) const override {
return *a.seq_id.num >= min && *a.seq_id.num <= max;
}
};
struct IndexRangeNode : Node {
int min, max;
IndexRangeNode(int a, int b) : min(a), max(b) {}
bool match(const gemmi::FlatAtom& a) const override {
return a.serial >= min && a.serial <= max;
}
};
struct ElementNode : Node {
std::vector<Element> elems;
explicit ElementNode(std::vector<Element> v) : elems(std::move(v)) {}
bool match(const gemmi::FlatAtom& a) const override {
for (const auto& e : elems)
if (a.element == e) return true;
return false;
}
};
struct HetatmNode : Node {
bool hetatm; // true = hetatm, false = not hetatm (i.e., ATOM)
explicit HetatmNode(bool h) : hetatm(h) {}
bool match(const gemmi::FlatAtom& a) const override {
return hetatm ? (a.het_flag == 'H') : (a.het_flag == 'A');
}
};
struct EntityTypeNode : Node {
EntityType etype;
explicit EntityTypeNode(EntityType e) : etype(e) {}
bool match(const gemmi::FlatAtom& a) const override {
return a.entity_type == etype;
}
};
struct HydrogenNode : Node {
bool match(const gemmi::FlatAtom& a) const override {
return a.element == El::H || a.element == El::D;
}
};
enum class CompareOp { LT, LE, GT, GE, EQ, NE };
struct BfactorNode : Node {
CompareOp op;
float value;
BfactorNode(CompareOp o, float v) : op(o), value(v) {}
bool match(const gemmi::FlatAtom& a) const override {
switch (op) {
case CompareOp::LT: return a.b_iso < value;
case CompareOp::LE: return a.b_iso <= value;
case CompareOp::GT: return a.b_iso > value;
case CompareOp::GE: return a.b_iso >= value;
case CompareOp::EQ: return a.b_iso == value;
case CompareOp::NE: return a.b_iso != value;
}
return false;
}
};
struct OccupancyNode : Node {
CompareOp op;
float value;
OccupancyNode(CompareOp o, float v) : op(o), value(v) {}
bool match(const gemmi::FlatAtom& a) const override {
switch (op) {
case CompareOp::LT: return a.occ < value;
case CompareOp::LE: return a.occ <= value;
case CompareOp::GT: return a.occ > value;
case CompareOp::GE: return a.occ >= value;
case CompareOp::EQ: return a.occ == value;
case CompareOp::NE: return a.occ != value;
}
return false;
}
};
struct BackboneNode : Node {
bool match(const gemmi::FlatAtom& a) const override {
// Standard protein backbone atoms
return std::strcmp(a.atom_name, "CA") == 0 ||
std::strcmp(a.atom_name, "C") == 0 ||
std::strcmp(a.atom_name, "N") == 0 ||
std::strcmp(a.atom_name, "O") == 0;
}
};
struct SidechainNode : Node {
bool match(const gemmi::FlatAtom& a) const override {
// Sidechain = not backbone and not hydrogen
return std::strcmp(a.atom_name, "CA") != 0 &&
std::strcmp(a.atom_name, "C") != 0 &&
std::strcmp(a.atom_name, "N") != 0 &&
std::strcmp(a.atom_name, "O") != 0 &&
a.element != El::H && a.element != El::D;
}
};
struct AllNode : Node {
bool match(const gemmi::FlatAtom&) const override { return true; }
};
namespace p = tao::pegtl;
// --- State ---
struct State {
std::vector<std::unique_ptr<psimpl::Node>> stack;
std::vector<std::string> string_list; // temp storage for building value lists
CompareOp current_op = CompareOp::EQ;
};
// --- Helpers ---
struct ws : p::star<p::space> {};
struct sep : p::plus<p::space> {}; // mandatory separator
// --- Values ---
struct integer : p::seq<p::opt<p::one<'-'>>, p::plus<p::digit>> {};
struct float_num : p::seq<p::opt<p::one<'-'>>, p::plus<p::digit>,
p::opt<p::seq<p::one<'.'>, p::star<p::digit>>>> {};
// Allow wildcards in identifiers and names
struct wildcard_char : p::one<'*', '?'> {};
struct identifier : p::plus<p::sor<p::alnum, p::one<'_'>, wildcard_char>> {};
struct atom_name_str : p::plus<p::sor<p::alnum, wildcard_char>> {};
struct element_str : p::seq<p::upper, p::opt<p::lower>> {}; // e.g., C, Ca, Fe (no wildcards for elements)
// --- Comparison operators ---
struct op_le : p::string<'<','='> {};
struct op_ge : p::string<'>','='> {};
struct op_ne : p::sor<p::string<'!','='>, p::string<'<','>'>> {};
struct op_lt : p::one<'<'> {};
struct op_gt : p::one<'>'> {};
struct op_eq : p::one<'='> {};
struct compare_op : p::sor<op_le, op_ge, op_ne, op_lt, op_gt, op_eq> {};
// --- Keywords ---
// Using istring for case-insensitive matching
struct kw_chain : p::istring<'c','h','a','i','n'> {};
struct kw_resn : p::istring<'r','e','s','n'> {};
struct kw_resi : p::istring<'r','e','s','i'> {};
struct kw_name : p::istring<'n','a','m','e'> {};
struct kw_alt : p::istring<'a','l','t'> {};
struct kw_elem : p::istring<'e','l','e','m'> {};
struct kw_index : p::istring<'i','n','d','e','x'> {};
struct kw_id : p::istring<'i','d'> {};
struct kw_b : p::istring<'b'> {};
struct kw_q : p::istring<'q'> {};
struct kw_and : p::istring<'a','n','d'> {};
struct kw_or : p::istring<'o','r'> {};
struct kw_not : p::istring<'n','o','t'> {};
// Stand-alone keywords (no arguments)
struct kw_hetatm : p::istring<'h','e','t','a','t','m'> {};
struct kw_polymer : p::istring<'p','o','l','y','m','e','r'> {};
struct kw_solvent : p::istring<'s','o','l','v','e','n','t'> {};
struct kw_water : p::istring<'w','a','t','e','r'> {};
struct kw_hydrogens : p::istring<'h','y','d','r','o','g','e','n','s'> {};
struct kw_h_dot : p::istring<'h','.'> {};
struct kw_backbone : p::istring<'b','a','c','k','b','o','n','e'> {};
struct kw_sidechain : p::istring<'s','i','d','e','c','h','a','i','n'> {};
struct kw_all : p::istring<'a','l','l'> {};
// --- Property Rules ---
// Chain: chain A or chain A+B+C
struct val_chain_item : identifier {};
struct val_chain_list : p::list<val_chain_item, p::one<'+'>> {};
struct rule_chain : p::seq<kw_chain, sep, val_chain_list> {};
// Resn: resn ALA or resn ALA+GLY+VAL
struct val_resn_item : atom_name_str {};
struct val_resn_list : p::list<val_resn_item, p::one<'+'>> {};
struct rule_resn : p::seq<kw_resn, sep, val_resn_list> {};
// Name: name CA or name CA+CB+N
struct val_name_item : atom_name_str {};
struct val_name_list : p::list<val_name_item, p::one<'+'>> {};
struct rule_name : p::seq<kw_name, sep, val_name_list> {};
// Alt: alt A
struct val_alt : p::alnum {}; // single char
struct rule_alt : p::seq<kw_alt, sep, val_alt> {};
// Resi: resi 100 OR resi 100-200
struct val_resi_range : p::seq<integer, p::one<'-'>, integer> {};
struct val_resi_single : integer {};
struct rule_resi : p::seq<kw_resi, sep, p::sor<val_resi_range, val_resi_single>> {};
// Index/ID: index 5 OR index 5-10 OR id 5
struct val_index_range : p::seq<integer, p::one<'-'>, integer> {};
struct val_index_single : integer {};
struct rule_index : p::seq<p::sor<kw_index, kw_id>, sep, p::sor<val_index_range, val_index_single>> {};
// Elem: elem C or elem C+N+O
struct val_elem_item : element_str {};
struct val_elem_list : p::list<val_elem_item, p::one<'+'>> {};
struct rule_elem : p::seq<kw_elem, sep, val_elem_list> {};
// B-factor: b > 50, b < 20, b = 0
struct val_b_compare : float_num {};
struct rule_b : p::seq<kw_b, ws, compare_op, ws, val_b_compare> {};
// Occupancy: q < 1, q > 0.5
struct val_q_compare : float_num {};
struct rule_q : p::seq<kw_q, ws, compare_op, ws, val_q_compare> {};
// Stand-alone keywords
struct rule_hetatm : kw_hetatm {};
struct rule_polymer : kw_polymer {};
struct rule_solvent : kw_solvent {};
struct rule_water : kw_water {};
struct rule_hydrogens : p::sor<kw_hydrogens, kw_h_dot> {};
struct rule_backbone : kw_backbone {};
struct rule_sidechain : kw_sidechain {};
struct rule_all : kw_all {};
// Combined Property
struct property : p::sor<
rule_chain,
rule_resn,
rule_resi,
rule_index,
rule_name,
rule_alt,
rule_elem,
rule_b,
rule_q,
rule_hetatm,
rule_polymer,
rule_solvent,
rule_water,
rule_hydrogens,
rule_backbone,
rule_sidechain,
rule_all
> {};
// --- Boolean Logic Rules ---
struct expression; // forward decl
struct parens : p::seq<p::one<'('>, ws, expression, ws, p::one<')'>> {};
// Factor: NOT factor | parens | property
struct not_factor : p::seq<kw_not, sep, p::seq<expression>> {}; // simplified recursion
// Actually, to handle precedence properly with PEGTL without left-recursion:
// factor = (NOT ws factor) | parens | property
struct factor;
struct rule_not : p::seq<kw_not, ws, factor> {};
struct factor : p::sor<rule_not, parens, property> {};
// Term (AND)
// To allow easy "reduce" actions, we explicitly name the sequence
struct and_rest : p::seq<ws, kw_and, ws, factor> {};
struct term : p::seq<factor, p::star<and_rest>> {};
// Expression (OR)
struct or_rest : p::seq<ws, kw_or, ws, term> {};
struct expression : p::seq<term, p::star<or_rest>> {};
// Root
struct grammar : p::must<ws, expression, ws, p::eof> {};
// ============================================================================
// PHASE 3: Actions
// ============================================================================
template<typename Rule>
struct action : p::nothing<Rule> {};
// --- List item actions (accumulate into string_list) ---
template<> struct action<val_chain_item> {
template<typename Input> static void apply(const Input& in, State& s) {
s.string_list.push_back(in.string());
}
};
template<> struct action<val_resn_item> {
template<typename Input> static void apply(const Input& in, State& s) {
s.string_list.push_back(in.string());
}
};
template<> struct action<val_name_item> {
template<typename Input> static void apply(const Input& in, State& s) {
s.string_list.push_back(in.string());
}
};
template<> struct action<val_elem_item> {
template<typename Input> static void apply(const Input& in, State& s) {
s.string_list.push_back(in.string());
}
};
// --- Rule actions (create nodes from accumulated lists) ---
template<> struct action<rule_chain> {
static void apply0(State& s) {
s.stack.push_back(std::make_unique<psimpl::ChainNode>(std::move(s.string_list)));
s.string_list.clear();
}
};
template<> struct action<rule_resn> {
static void apply0(State& s) {
s.stack.push_back(std::make_unique<psimpl::ResnNode>(std::move(s.string_list)));
s.string_list.clear();
}
};
template<> struct action<rule_name> {
static void apply0(State& s) {
s.stack.push_back(std::make_unique<psimpl::AtomNameNode>(std::move(s.string_list)));
s.string_list.clear();
}
};
template<> struct action<rule_elem> {
static void apply0(State& s) {
std::vector<Element> elems;
for (const auto& str : s.string_list)
elems.push_back(Element(str));
s.stack.push_back(std::make_unique<psimpl::ElementNode>(std::move(elems)));
s.string_list.clear();
}
};
template<> struct action<val_alt> {
template<typename Input> static void apply(const Input& in, State& s) {
std::string str = in.string();
char c = str.empty() ? ' ' : str[0];
s.stack.push_back(std::make_unique<psimpl::AltLocNode>(c));
}
};
template<> struct action<val_resi_single> {
template<typename Input> static void apply(const Input& in, State& s) {
int val = std::stoi(in.string());
s.stack.push_back(std::make_unique<psimpl::ResiRangeNode>(val, val));
}
};
template<> struct action<val_resi_range> {
template<typename Input> static void apply(const Input& in, State& s) {
std::string str = in.string();
size_t split_pos = str.find('-', 1); // Skip potential leading negative sign
int v1 = std::stoi(str.substr(0, split_pos));
int v2 = std::stoi(str.substr(split_pos + 1));
s.stack.push_back(std::make_unique<psimpl::ResiRangeNode>(v1, v2));
}
};
template<> struct action<val_index_single> {
template<typename Input> static void apply(const Input& in, State& s) {
int val = std::stoi(in.string());
s.stack.push_back(std::make_unique<psimpl::IndexRangeNode>(val, val));
}
};
template<> struct action<val_index_range> {
template<typename Input> static void apply(const Input& in, State& s) {
std::string str = in.string();
size_t split_pos = str.find('-', 1); // Skip potential leading negative sign
int v1 = std::stoi(str.substr(0, split_pos));
int v2 = std::stoi(str.substr(split_pos + 1));
s.stack.push_back(std::make_unique<psimpl::IndexRangeNode>(v1, v2));
}
};
// --- Comparison operator actions ---
template<> struct action<op_lt> {
static void apply0(State& s) { s.current_op = CompareOp::LT; }
};
template<> struct action<op_le> {
static void apply0(State& s) { s.current_op = CompareOp::LE; }
};
template<> struct action<op_gt> {
static void apply0(State& s) { s.current_op = CompareOp::GT; }
};
template<> struct action<op_ge> {
static void apply0(State& s) { s.current_op = CompareOp::GE; }
};
template<> struct action<op_eq> {
static void apply0(State& s) { s.current_op = CompareOp::EQ; }
};
template<> struct action<op_ne> {
static void apply0(State& s) { s.current_op = CompareOp::NE; }
};
template<> struct action<val_b_compare> {
template<typename Input> static void apply(const Input& in, State& s) {
float val = std::stof(in.string());
s.stack.push_back(std::make_unique<psimpl::BfactorNode>(s.current_op, val));
}
};
template<> struct action<val_q_compare> {
template<typename Input> static void apply(const Input& in, State& s) {
float val = std::stof(in.string());
s.stack.push_back(std::make_unique<psimpl::OccupancyNode>(s.current_op, val));
}
};
// --- Stand-alone keyword actions ---
template<> struct action<rule_hetatm> {
static void apply0(State& s) {
s.stack.push_back(std::make_unique<psimpl::HetatmNode>(true));
}
};
template<> struct action<rule_polymer> {
static void apply0(State& s) {
s.stack.push_back(std::make_unique<psimpl::EntityTypeNode>(EntityType::Polymer));
}
};
template<> struct action<rule_solvent> {
static void apply0(State& s) {
// Solvent includes water
s.stack.push_back(std::make_unique<psimpl::EntityTypeNode>(EntityType::Water));
}
};
template<> struct action<rule_water> {
static void apply0(State& s) {
s.stack.push_back(std::make_unique<psimpl::EntityTypeNode>(EntityType::Water));
}
};
template<> struct action<rule_hydrogens> {
static void apply0(State& s) {
s.stack.push_back(std::make_unique<psimpl::HydrogenNode>());
}
};
template<> struct action<rule_backbone> {
static void apply0(State& s) {
s.stack.push_back(std::make_unique<psimpl::BackboneNode>());
}
};
template<> struct action<rule_sidechain> {
static void apply0(State& s) {
s.stack.push_back(std::make_unique<psimpl::SidechainNode>());
}
};
template<> struct action<rule_all> {
static void apply0(State& s) {
s.stack.push_back(std::make_unique<psimpl::AllNode>());
}
};
// --- Logic Actions ---
// NOT
template<> struct action<rule_not> {
static void apply0(State& s) {
auto child = std::move(s.stack.back());
s.stack.pop_back();
s.stack.push_back(std::make_unique<psimpl::NotNode>(std::move(child)));
}
};
// AND
template<> struct action<and_rest> {
static void apply0(State& s) {
auto rhs = std::move(s.stack.back()); s.stack.pop_back();
auto lhs = std::move(s.stack.back()); s.stack.pop_back();
auto node = std::make_unique<psimpl::AndNode>();
node->left = std::move(lhs);
node->right = std::move(rhs);
s.stack.push_back(std::move(node));
}
};
// OR
template<> struct action<or_rest> {
static void apply0(State& s) {
auto rhs = std::move(s.stack.back()); s.stack.pop_back();
auto lhs = std::move(s.stack.back()); s.stack.pop_back();
auto node = std::make_unique<psimpl::OrNode>();
node->left = std::move(lhs);
node->right = std::move(rhs);
s.stack.push_back(std::move(node));
}
};
} // namespace psimpl
// ============================================================================
// Public API
// ============================================================================
// Returns a compiled selection tree
inline std::unique_ptr<psimpl::Node> compile_pymol_selection(const std::string& selector) {
psimpl::State state;
tao::pegtl::memory_input<> in(selector, "");
try {
tao::pegtl::parse<psimpl::grammar, psimpl::action>(in, state);
if (state.stack.empty()) return nullptr;
return std::move(state.stack.back());
}
catch (const tao::pegtl::parse_error& e) {
std::cerr << "Selection Parse Error: " << e.what() << std::endl;
return nullptr;
}
}
inline std::vector<const gemmi::FlatAtom*>
select_atoms(const gemmi::FlatStructure& fs, const std::string& query) {
auto root = compile_pymol_selection(query);
std::vector<const gemmi::FlatAtom*> result;
if (root)
for (auto& atom : fs.table) {
if (root->match(atom)) {
result.push_back(&atom);
}
}
return result;
}
inline void remove_not_selected(gemmi::FlatStructure& fs, const std::string& query) {
if (auto root = compile_pymol_selection(query))
vector_remove_if(fs.table, [&](FlatAtom& atom) { return !root->match(atom); });
}
} // namespace gemmi
#endif