Source code for bbprep._internal.ensemble.ensemble
"""Module for ensemble class."""
from collections import abc
from dataclasses import dataclass
import stk
from .calculators import EnergyCalculator, Optimiser
[docs]
@dataclass
class Conformer:
molecule: stk.BuildingBlock
conformer_id: int
source: str | None = None
permutation: dict[tuple[int], float] | None = None
score: int | float | None = None
[docs]
class Ensemble:
def __init__(
self,
base_molecule: stk.BuildingBlock,
) -> None:
self._base_molecule = base_molecule
self._molecule_num_atoms = base_molecule.get_num_atoms()
self._conformers: dict[int, Conformer] = {}
[docs]
def add_conformer(self, conformer: Conformer) -> None:
conf_id = conformer.conformer_id
if conf_id in self._conformers:
msg = f"{conf_id} already in conformer list"
raise RuntimeError(msg)
self._conformers[conf_id] = conformer
[docs]
def yield_conformers(self) -> abc.Iterator[Conformer]:
for conf_id in self._conformers:
yield self._conformers[conf_id]
[docs]
def get_conformer(self, idx: int) -> Conformer:
try:
return self._conformers[idx]
except KeyError as e:
msg = f"Conformer with ID: {idx} not in ensemble."
raise KeyError(msg) from e
[docs]
def optimise_conformers(self, optimiser: Optimiser) -> "Ensemble":
"""Get a new ensemble with optimised conformers."""
new_ensemble = Ensemble(base_molecule=self._base_molecule)
for conformer in self.yield_conformers():
new_ensemble.add_conformer(
conformer=Conformer(
molecule=optimiser.function(conformer.molecule),
conformer_id=conformer.conformer_id,
source=f"{conformer.source}:{optimiser.name}",
permutation=None,
)
)
return new_ensemble
[docs]
def get_lowest_energy_conformer(
self,
calculator: EnergyCalculator,
) -> Conformer:
"""Get the lowest energy conformer based on a calculator.
The calculator must be a callable that returns a value.
"""
lowest_energy_conformer: Conformer
lowest_energy = float("inf")
for conformer in self.yield_conformers():
energy = calculator.function(conformer.molecule)
if energy < lowest_energy:
lowest_energy = energy
lowest_energy_conformer = Conformer(
molecule=conformer.molecule,
conformer_id=conformer.conformer_id,
source=conformer.source,
permutation=None,
score=energy,
)
return lowest_energy_conformer
def __str__(self) -> str:
return (
f"{self.__class__.__name__}(num_confs={self.get_num_conformers()})"
)
def __repr__(self) -> str:
return str(self)