Implementing a New Form Adapter#
This guide explains how to add support for a new molecular system format, class, or representation (referred to as a “form”) in MolSysMT.
By implementing a form adapter, your representation automatically gains the ability to:
Participate in the polymorphic conversion graph (
molsysmt.convert).Expose properties natively via the dispatch engine (
molsysmt.get).Support memory-safe chunked execution on heavy trajectories using structure iterators.
1. Directory Structure and Naming Conventions#
All form adapters are located under molsysmt/form/. The directory name and the internal form_name variable follow a strict pattern based on the form type:
Form Type |
Target Representation |
Directory Name |
Canonical |
|---|---|---|---|
|
In-memory library objects |
|
|
|
Physical file formats on disk |
|
|
|
Text inputs and identifiers |
|
|
2. Step 1: Scaffolding a New Form Adapter#
To ensure your new form adapter conforms perfectly to the repository’s QA contract, use the developer template scaffolding tool:
python devtools/scripts/scaffold_form.py --name <form_folder_name> --type <class/file/string> [--class-name <class_name>]
Example (Class Form):#
python devtools/scripts/scaffold_form.py --name openff_Topology --type class --class-name "openff.toolkit.topology.Topology"
Example (File Form):#
python devtools/scripts/scaffold_form.py --name file_xyz --type file
This command automatically templates a pristine adapter folder containing:
__init__.py(Core contract metadata)is_form.py(Form detection callable)attributes.py(Supported attribute map)has_attribute.py(Attribute check callable)iterators.py(Boilerplate for structure and topology iterators)get_topological_attributes.py(Topological property getters)get_structural_attributes.py (Structural property getters)
3. Step 2: Implementing the Core Contract#
A. Form Detection (is_form.py)#
Implement the detection logic in is_form(item). This must return True only if the incoming object matches the expected signature of this form.
def is_form(item):
"""Check if the item is of form 'rdkit.Mol'."""
output = False
class_name = str(type(item))
if 'rdkit.Chem.rdchem.Mol' in class_name:
output = True
return output
B. Attributes and Getters (attributes.py and has_attribute.py)#
The attributes dictionary in attributes.py maps supported attributes to True or False.
Rule: An attribute must be marked
Trueif and only if a corresponding getter function (e.g.get_n_atomsorget_coordinates) is fully implemented inget_topological_attributes.pyorget_structural_attributes.py.
In has_attribute.py, define the check decorated with the argument digest validation:
from molsysmt._private.argdigest import arg_digest
@arg_digest(form='rdkit.Mol')
def has_attribute(molecular_system, attribute, include_none=False, skip_digestion=False):
from .attributes import attributes
return attributes.get(attribute, False)
4. Step 3: Lazy Imports and Dependency Isolation#
[!CAUTION] Never Import Soft Dependencies Eagerly MolSysMT maintains an extremely fast cold import latency. Eager imports of libraries like
openmm,mdtraj,rdkit, orbiopythonat the module level in__init__.pyor any adapter file will leak and break the package. All soft dependencies must be imported lazily inside functions.
For example:
# INCORRECT:
import rdkit # Eager import at top level will fail the QA audit
# CORRECT:
def get_n_atoms(item, skip_digestion=False):
from rdkit import Chem # Lazy import inside the function body
return item.GetNumAtoms()
5. Step 4: Implementing Iterators (Heavy Forms)#
If your form represents a trajectory or a heavy coordinate file, you must implement iterators to support block-wise execution:
Open
iterators.py.Subclass
BaseStructuresIteratororBaseTopologyIteratorfrommolsysmt/form/iterators.py.Implement
__init__,__next__, andclose(). Context manager protocols (__enter__and__exit__) are inherited automatically.
from molsysmt.form.iterators import BaseStructuresIterator
class StructuresIterator(BaseStructuresIterator):
def __init__(self, molecular_system, atom_indices='all', start=0, interval=1, stop=None, chunk=1,
structure_indices=None, skip_digestion=True):
self.molecular_system = molecular_system
# Open coordinate file handles
def __next__(self):
# Yield coordinates chunk, or raise StopIteration when done
pass
def close(self):
# Safely release file locks or handles
pass
6. Step 5: Implementing Conversions#
Write a converter module under your folder named
to_<target_form>.py.Import and use
@arg_digestwith the source form name.Implement the conversion, returning the target form object.
Register it in the
_convert_todictionary in__init__.pyusing the string name of the function to maintain lazy loading:
# molsysmt/form/my_form/__init__.py
_convert_to = {
'molsysmt.MolSys': 'to_molsysmt_MolSys',
'molsysmt.Topology': 'to_molsysmt_Topology',
}
7. Step 6: Verifying with the QA Conformance Linter#
Before submitting any Pull Request, run the automated linter to verify your new form adapter:
python devtools/scripts/validate_form_adapters.py
The linter automatically scans all folders in molsysmt/form/ and audits them against the developer contract. If any issues are found (e.g. missing metadata, eager imports, non-conforming iterators), it will output detailed error logs and exit with code 1.