Input and output

Load ligands from SDF, mol2, SMILES, or a directory.

Every loader converges on the same guarantees, because Ligand demands them: explicit hydrogens, exactly one 3D conformer, and a filesystem-safe unique name. Enforcing that here rather than downstream means a malformed input fails while the user still knows which file it came from.

rbfenetmap.io.loaders.load_fepplus_network(path)[source]

Read a Schrodinger FEP+ edge list into explicit_pairs specifications.

FEP+ writes its planned map as a text file of A >>> B lines, each name optionally prefixed by an internal hash and a colon. openfe ships the equivalent as load_fepplus_network; this is the same job in this package’s vocabulary, and it exists so that a group with an existing map can evaluate this tool against it rather than having to start from nothing.

Parameters:

path (pathlib.Path)

Returns:

"a~b" specifications, ready to hand to NetworkOptions as explicit_pairs alongside pair_strategy="explicit" and the explicit planner.

Return type:

tuple[str, …]

Raises:

ValueError – If a line is unparsable or the file holds no edges.

Notes

Only the topology is imported; FEP+’s atom mappings are not read, and this package maps every imported pair itself. That is the point rather than a limitation – the main reason to import a foreign map is to put it through this package’s own feasibility rules – but it does mean an edge FEP+ was happy with can come back rejected. The explicit planner says so loudly instead of dropping it.

rbfenetmap.io.loaders.load_ligands(paths, *, name_property='_Name')[source]

Load ligands from any mixture of files and directories.

Parameters:
  • paths (Iterable[pathlib.Path] or Sequence[str]) – Files or directories. Directories are scanned (non-recursively) for the recognised molecule suffixes.

  • name_property (str, optional) – Passed to load_sdf().

Returns:

In discovery order.

Return type:

list[Ligand]

Raises:
  • FileNotFoundError – If a named path does not exist.

  • ValueError – If two ligands end up with the same name, which would silently collapse two network vertices into one.

rbfenetmap.io.loaders.load_orion_network(path)[source]

Read an OpenEye Orion NES edge list into explicit_pairs specifications.

Orion writes A >> B lines, with # comments. Two arrows rather than three is the only structural difference from the FEP+ format, which is exactly why these are two entry points over one parser rather than one loader that sniffs: a guess that read an FEP+ line as an Orion edge would import a network nobody planned.

Parameters:

path (pathlib.Path)

Returns:

"a~b" specifications. See load_fepplus_network() for how to use them and for what is deliberately not imported.

Return type:

tuple[str, …]

Raises:

ValueError – If a line is unparsable or the file holds no edges.

rbfenetmap.io.loaders.load_sdf(path, *, name_property='_Name')[source]

Load every record from an SDF or MOL file.

Parameters:
  • path (pathlib.Path)

  • name_property (str, optional) – Molecule property to read the name from. Falls back to stem_index.

Return type:

list[Ligand]

rbfenetmap.io.loaders.load_smiles(path)[source]

Load a whitespace-delimited SMILES name file, embedding each molecule.

See the warning in _prepare(): independently embedded ligands are not co-posed.

Parameters:

path (Path)

Return type:

list[Ligand]

rbfenetmap.io.loaders.sanitize_name(raw, fallback)[source]

Return a filesystem-safe ligand name derived from raw.

Ligand names become directory and file names in exports and are parsed back out of "source~target" edge keys, so characters outside [A-Za-z0-9_.+-] are replaced with underscores rather than passed through.

Parameters:
Return type:

str

JSON serialization of a planned network.

The network file is the package’s own interchange format, and it is deliberately self-contained: molecules travel as MDL molblocks embedded in the JSON rather than as paths to the original inputs. A network that references files by path stops being reproducible the moment someone reorganises a directory, and the mapping indices in it are meaningless against a molecule that has been re-read with different atom ordering.

Rejected candidates are written too. They cost little and they are what explains a disconnected or sparse network after the fact.

Two fields are written only when they carry something – a ligand’s provenance and a network’s intermediates. Absence already means “not set”, so emitting a null or an empty list would add a difference to every file ever written in order to convey nothing, and would break the property that regenerating an all-real network reproduces it byte-for-byte.

rbfenetmap.io.networkio.SCHEMA_VERSION = 1

Bumped whenever the on-disk shape changes incompatibly.

rbfenetmap.io.networkio.dump_network(network, path, *, indent=2)[source]

Write network to path as JSON.

Parameters:
Return type:

Path

rbfenetmap.io.networkio.load_network(path)[source]

Read a network written by dump_network().

Raises:

ValueError – If the file was written by an incompatible schema version.

Parameters:

path (Path)

Return type:

Network

rbfenetmap.io.networkio.network_to_dict(network)[source]

Return the JSON-ready representation of network.

Parameters:

network (Network)

Return type:

dict[str, Any]

Amber timask / scmask generation.

A ParmEd-free port of BuildEdges._build_rbfe_mask_variables. It lives in rbfenetmap.io rather than the core because Amber masks are one output format among several, and nothing in the planning pipeline should depend on them.

Two correctness traps from the original are preserved deliberately, and both are worth understanding before touching this module.

rbfenetmap.io.amber_masks.DEFAULT_RESIDUE_NAMES = ('SRC', 'DST')

Residue names the exporter assigns to the two endpoints of an edge.

class rbfenetmap.io.amber_masks.AmberMasks(timask1, timask2, scmask1, scmask2)[source]

Bases: object

The four Amber mask strings for one transformation.

Parameters:
as_dict()[source]

Return the masks keyed by their Amber input names.

Return type:

dict[str, str]

rbfenetmap.io.amber_masks.build_amber_masks(source, target, mapping, *, residue_names=('SRC', 'DST'))[source]

Build the timask/scmask pair for one transformation.

Parameters:
Return type:

AmberMasks

Raises:

rbfenetmap.core.exceptions.ExporterError – If the mapping has no common core, if a soft-core atom name collides with a common-core atom name, or if the mapping violates Amber’s linear-scaling constraint.

Notes

Trap three: the empty core. A mapping with no common core is what a counterpoised (CBFE) edge carries, and left alone this function would happily turn it into timask1=":SRC", scmask1=":SRC&@<every atom>". That is not a malformed file – it is a valid, runnable single-topology RBFE in which both endpoints are entirely soft-core, which will converge in pmemd and answer a different question than the one asked, with nothing downstream to flag it. It is refused here rather than only in the exporter so no future caller can reopen it. Trap one: name collisions. Amber soft-core masks select atoms by name, not by index. If a soft-core atom shares its name with a common-core atom in the same residue, the mask silently selects that core atom too, and the run proceeds with a soft-core region larger than intended. Nothing downstream reports this; the free energies are simply wrong. Atom names must therefore be unique within a ligand – which is what antechamber -du y produces.

Trap two: linear scaling. Amber requires len(TI1) - len(SC1) == len(TI2) - len(SC2). Since each side of that reduces to the size of the common core, the constraint is exactly len(cc1) == len(cc2) together with injectivity of cc2 – and both are already invariants of AtomMapping. A mapping built by this package cannot violate it, so the check below can only ever fire on a hand-authored map. It is kept because it is cheap, and because failing here with a clear message beats failing inside pmemd with an opaque one.