From 6243cad1d66948dc5fd9551c3dfcd505f440d7e2 Mon Sep 17 00:00:00 2001 From: Rosa Bulo Date: Wed, 10 Jun 2026 18:45:12 +0200 Subject: [PATCH 1/3] Rosa Bulo (REB) SCMSUITE-- SO107: Added default settings to PDB writing - The PDB writer would by default not write any default resname, resnum, or occ/fix value That would make the PDB format unreadable to most applications. - There was also a bug in the MOL2 reader, which made it fail if there were non-integer bond-orders in the MOL2 file. --- src/scm/plams/mol/molecule.py | 11 +++++++++++ unit_tests/test_molecule.py | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/scm/plams/mol/molecule.py b/src/scm/plams/mol/molecule.py index 57522056..3617d578 100644 --- a/src/scm/plams/mol/molecule.py +++ b/src/scm/plams/mol/molecule.py @@ -3178,6 +3178,8 @@ def readmol2(self, f: IO, **other: Any) -> None: elif mode[0] == "BOND": spl = line.split() + if "." in spl[3]: + spl[3] = f"{int(float(spl[3]))}" if len(spl) < 4: raise FileError(f"readmol2: Error in {f.name} line {i+1}: not enough values in line") try: @@ -3274,8 +3276,13 @@ def writepdb(self, f: IO, **other: Any) -> None: pdb = PDBHandler() for i, at in enumerate(self.atoms): pdbatom = PDBAtom() + pdbatom.name = "%-2s" % (at.symbol.upper()) pdbatom.coords = at.coords pdbatom.element = at.symbol.upper() + pdbatom.res = "LIG" + pdbatom.resnum = "0" + pdbatom.occ = "1.00" + pdbatom.fix = "0.00" if "pdb" in at.properties: if "res" in at.properties.pdb: pdbatom.res = at.properties.pdb.res @@ -3283,6 +3290,10 @@ def writepdb(self, f: IO, **other: Any) -> None: pdbatom.resnum = at.properties.pdb.resnum if "name" in at.properties.pdb: pdbatom.name = at.properties.pdb.name + if "occ" in at.properties.pdb: + pdbatom.occ = at.properties.pdb.occ + if "fix" in at.properties.pdb: + pdbatom.occ = at.properties.pdb.fix pdb.add_atom(pdbatom) if len(self.lattice) > 0: pdb.set_lattice(self.lattice) diff --git a/unit_tests/test_molecule.py b/unit_tests/test_molecule.py index 66a3e402..45eb59a5 100644 --- a/unit_tests/test_molecule.py +++ b/unit_tests/test_molecule.py @@ -1,4 +1,5 @@ import os +from io import StringIO import pytest from abc import ABC, abstractmethod @@ -1113,6 +1114,24 @@ def test_write_multiple_molecules_to_pdb(pdb_folder, tmp_path): np.testing.assert_allclose(at.coords, at_ref.coords, atol=1e-08) +def test_simple_pdb_writing(xyz_folder, tmp_path): + """ + Test that a reasonable PDB format is written for a simple benzene molecule + + The file should contain for each atom a default resname and resnum, + as well as default fix and occ columns. + """ + benzene = Molecule(os.path.join(xyz_folder, "benzene.xyz")) + f = StringIO() + benzene.writepdb(f) + f.seek(0) + for i in range(2): + line = f.readline() + + refline = "ATOM 1 C LIG 0 1.194 -0.689 0.000 1.00 0.00 C \n" + assert line == refline + + def test_read_multiple_molecules_from_coskf(coskf_folder): """ Test for COSKF reading From 8d3ef12e25c14aec9ed427a636c13a4130f9373f Mon Sep 17 00:00:00 2001 From: Rosa Bulo Date: Fri, 12 Jun 2026 13:35:33 +0200 Subject: [PATCH 2/3] Rosa Bulo (REB) SCMSUITE-- SO107: Added test for Mol2 reading/writing --- unit_tests/test_molecule.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/unit_tests/test_molecule.py b/unit_tests/test_molecule.py index 45eb59a5..41479000 100644 --- a/unit_tests/test_molecule.py +++ b/unit_tests/test_molecule.py @@ -1132,6 +1132,24 @@ def test_simple_pdb_writing(xyz_folder, tmp_path): assert line == refline +def test_mol2_format(xyz_folder, tmp_path): + """ + Test that a Mol2 file can be written and read for a molecule with double and aromatic bonds + """ + mol = Molecule(os.path.join(xyz_folder, "reactant2.xyz")) + mol.guess_bonds() + + f = StringIO() + mol.writemol2(f) + f.seek(0) + + mol2 = Molecule() + mol2.readmol2(f) + + assert len(mol) == len(mol2) + assert len(mol.bonds) == len(mol2.bonds) + + def test_read_multiple_molecules_from_coskf(coskf_folder): """ Test for COSKF reading From b2b0c5ca56830a2eb503ccdcb78d285c6a64712d Mon Sep 17 00:00:00 2001 From: Rosa Bulo Date: Mon, 15 Jun 2026 10:52:26 +0200 Subject: [PATCH 3/3] Rosa Bulo (REB) SCMSUITE-- SO107: Added changes suggested by David --- CHANGELOG.md | 2 ++ src/scm/plams/mol/molecule.py | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 430332c5..14b16d64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ This changelog is effective from the 2025 releases. * MultiJob now supports generic Job types for the self.children attribute ### Changed +* `Molecule.readmol2` can now read non-integer bond orders +* `Molecule.writepdb` now writes default values for res, resnum, fix, and occ columns * `view` function uses stdin mode for AMSview, reducing overhead for image creation * `vasp_output_to_ams` now reads the MD time step from `POTIM` in the OUTCAR and labels molecular-dynamics runs (`IBRION = 0`) as such, instead of using a fixed default time step diff --git a/src/scm/plams/mol/molecule.py b/src/scm/plams/mol/molecule.py index 2b3fa8f6..aa22bfa2 100644 --- a/src/scm/plams/mol/molecule.py +++ b/src/scm/plams/mol/molecule.py @@ -3182,10 +3182,10 @@ def readmol2(self, f: IO, **other: Any) -> None: elif mode[0] == "BOND": spl = line.split() - if "." in spl[3]: - spl[3] = f"{int(float(spl[3]))}" if len(spl) < 4: raise FileError(f"readmol2: Error in {f.name} line {i+1}: not enough values in line") + if "." in spl[3]: + spl[3] = f"{int(float(spl[3]))}" try: atom1 = self.atoms[int(spl[1]) - 1] atom2 = self.atoms[int(spl[2]) - 1] @@ -3280,6 +3280,7 @@ def writepdb(self, f: IO, **other: Any) -> None: pdb = PDBHandler() for i, at in enumerate(self.atoms): pdbatom = PDBAtom() + pdbatom.name = f"{at.symbol.upper():<2}" pdbatom.name = "%-2s" % (at.symbol.upper()) pdbatom.coords = at.coords pdbatom.element = at.symbol.upper() @@ -3297,7 +3298,7 @@ def writepdb(self, f: IO, **other: Any) -> None: if "occ" in at.properties.pdb: pdbatom.occ = at.properties.pdb.occ if "fix" in at.properties.pdb: - pdbatom.occ = at.properties.pdb.fix + pdbatom.fix = at.properties.pdb.fix pdb.add_atom(pdbatom) if len(self.lattice) > 0: pdb.set_lattice(self.lattice)