From b6874998d66a324b1fdd3f1764191483e543609c Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Tue, 17 Sep 2024 23:32:42 -0400 Subject: [PATCH] add chgmult to Molecule repr (#340) * Update test_molecule.py * Update molecule.py * Update changelog.rst --- docs/changelog.rst | 3 ++ qcelemental/models/molecule.py | 50 +++++++++++++++++++++++++++--- qcelemental/tests/test_molecule.py | 45 +++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 6ffc02ed..201fdedf 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -19,6 +19,9 @@ Changelog .. Misc. .. +++++ +- (:pr:`340`, :issue:`330`) Add molecular charge and multiplicity to Molecule repr formula, + so neutral singlet unchanged but radical cation has '2^formula+'. + 0.29.0 / 2024-MM-DD (Unreleased) -------------------------------- diff --git a/qcelemental/models/molecule.py b/qcelemental/models/molecule.py index 50d02ad4..afd88771 100644 --- a/qcelemental/models/molecule.py +++ b/qcelemental/models/molecule.py @@ -311,7 +311,7 @@ class Config(ProtoModel.Config): serialize_skip_defaults = True repr_style = lambda self: [ ("name", self.name), - ("formula", self.get_molecular_formula()), + ("formula", self.get_molecular_formula(chgmult=True)), ("hash", self.get_hash()[:7]), ] fields = { @@ -801,7 +801,7 @@ def get_hash(self): m.update(concat.encode("utf-8")) return m.hexdigest() - def get_molecular_formula(self, order: str = "alphabetical") -> str: + def get_molecular_formula(self, order: str = "alphabetical", chgmult: bool = False) -> str: r""" Returns the molecular formula for a molecule. @@ -809,6 +809,8 @@ def get_molecular_formula(self, order: str = "alphabetical") -> str: ---------- order: str, optional Sorting order of the formula. Valid choices are "alphabetical" and "hill". + chgmult + If not neutral singlet, return formula as {mult}^{formula}{chg}. Returns ------- @@ -835,11 +837,47 @@ def get_molecular_formula(self, order: str = "alphabetical") -> str: >>> hcl.get_molecular_formula() ClH + >>> two_pentanol_radcat = qcelemental.models.Molecule(''' + ... 1 2 + ... C -4.43914 1.67538 -0.14135 + ... C -2.91385 1.70652 -0.10603 + ... H -4.82523 2.67391 -0.43607 + ... H -4.84330 1.41950 0.86129 + ... H -4.79340 0.92520 -0.88015 + ... H -2.59305 2.48187 0.62264 + ... H -2.53750 1.98573 -1.11429 + ... C -2.34173 0.34025 0.29616 + ... H -2.72306 0.06156 1.30365 + ... C -0.80326 0.34498 0.31454 + ... H -2.68994 -0.42103 -0.43686 + ... O -0.32958 1.26295 1.26740 + ... H -0.42012 0.59993 -0.70288 + ... C -0.26341 -1.04173 0.66218 + ... H -0.61130 -1.35318 1.67053 + ... H 0.84725 -1.02539 0.65807 + ... H -0.60666 -1.78872 -0.08521 + ... H -0.13614 2.11102 0.78881 + ... ''') + >>> two_pentanol_radcat.get_molecular_formula(chgmult=True) + 2^C5H12O+ + """ from ..molutil import molecular_formula_from_symbols - return molecular_formula_from_symbols(symbols=self.symbols, order=order) + formula = molecular_formula_from_symbols(symbols=self.symbols, order=order) + + c, m = self.molecular_charge, self.molecular_multiplicity + if not chgmult or (c == 0.0 and m == 1): + return formula + + if m > 1: + formula = f"{m}^{formula}" + if c < 0.0: + formula += abs(int(c)) * "-" + elif c > 0.0: + formula += int(c) * "+" + return formula ### Constructors @@ -1065,7 +1103,11 @@ def _orient_molecule_internal(self): return new_geometry def __repr_args__(self) -> "ReprArgs": - return [("name", self.name), ("formula", self.get_molecular_formula()), ("hash", self.get_hash()[:7])] + return [ + ("name", self.name), + ("formula", self.get_molecular_formula(chgmult=True)), + ("hash", self.get_hash()[:7]), + ] def _ipython_display_(self, **kwargs) -> None: try: diff --git a/qcelemental/tests/test_molecule.py b/qcelemental/tests/test_molecule.py index 1d7b82ab..faf5bd85 100644 --- a/qcelemental/tests/test_molecule.py +++ b/qcelemental/tests/test_molecule.py @@ -141,6 +141,51 @@ def test_molecule_compare(): assert water_molecule != water_molecule3 +def test_molecule_repr_chgmult(): + wat1 = water_molecule.copy() + assert "formula='H2O'," in wat1.__repr__(), "charge/mult wrongly present in Molecule repr" + + wat2 = water_dimer_minima.dict() + wat2["fragment_charges"] = [1, 0] + for field in ["molecular_charge", "molecular_multiplicity", "fragment_multiplicities", "validated"]: + wat2.pop(field) + wat2 = Molecule(**wat2) + assert "formula='2^H4O2+'," in wat2.__repr__(), "charge/mult missing from Molecule repr" + + two_pentanol_radcat = Molecule.from_data( + """ + 1 2 + C -4.43914 1.67538 -0.14135 + C -2.91385 1.70652 -0.10603 + H -4.82523 2.67391 -0.43607 + H -4.84330 1.41950 0.86129 + H -4.79340 0.92520 -0.88015 + H -2.59305 2.48187 0.62264 + H -2.53750 1.98573 -1.11429 + C -2.34173 0.34025 0.29616 + H -2.72306 0.06156 1.30365 + C -0.80326 0.34498 0.31454 + H -2.68994 -0.42103 -0.43686 + O -0.32958 1.26295 1.26740 + H -0.42012 0.59993 -0.70288 + C -0.26341 -1.04173 0.66218 + H -0.61130 -1.35318 1.67053 + H 0.84725 -1.02539 0.65807 + H -0.60666 -1.78872 -0.08521 + H -0.13614 2.11102 0.78881 + """ + ) + assert "formula='2^C5H12O+'," in two_pentanol_radcat.__repr__(), "charge/mult missing from Molecule repr" + + Oanion = Molecule.from_data( + """ + -2 1 + O 0 0 0 + """ + ) + assert "formula='O--'," in Oanion.__repr__(), "charge/mult missing from Molecule repr" + + def test_water_minima_data(): # Give it a name mol_dict = water_dimer_minima.dict()