Skip to content

High-level functions

For a deeper understanding of how the algorithms work internally, see the Algorithm Flowcharts.


api

minimalsym.py — Public API for molecular symmetry analysis.

Classes:

Functions:

  • get_point_group

    Determine the point group of a molecule.

  • is_planar

    Check whether all atoms of a molecule lie in a common plane.

  • get_inequivalent

    Find symmetry-inequivalent atoms using all symmetry operations.

  • symmetrize

    Symmetrize the geometry of a molecule to exact point-group symmetry.

  • generate_symmetry_candidates

    Generate symmetry-consistent geometries compatible with a detected point group.

SymmetryResult dataclass

SymmetryResult(mol: Atoms, pg: str, rmsd: float)

Container for a found symmetry result.

Attributes:

  • mol (Atoms) –

    Symmetrized molecule.

  • pg (str) –

    Schoenflies symbol of the point group.

  • rmsd (float) –

    RMSD between the original and symmetrized structure.

get_point_group

get_point_group(mol: Atoms, geom_tol: float = 0.05, eigen_tol: float | None = None, quiet: bool = True) -> str

Determine the point group of a molecule.

Parameters:

  • mol (Atoms) –

    Molecule to find point group.

  • geom_tol (float, default: 0.05 ) –

    Geometric tolerance (default 0.05 Å).

  • eigen_tol (float, default: None ) –

    Relative tolerance for eigenvalues (default None, internal worker will determine an appropriate float).

  • quiet (bool, default: True ) –

    If True warnings and debug messages will be disabled.

Returns:

  • str

    Schoenflies symbol (e.g. "C2v", "D3h", "Oh").

Raises:

  • TypeError

    mol is not ase.Atoms.

  • ValueError

    geom_tol <= 0 or mol is empty.

  • RuntimeError

    point-group detection failed internally.

Source code in minimalsym/api.py
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def get_point_group(mol: Atoms, geom_tol: float = 0.05, eigen_tol: float|None = None, quiet: bool = True) -> str:
    """
    Determine the point group of a molecule.

    Parameters
    ----------
    mol : ase.Atoms
        Molecule to find point group.
    geom_tol : float, optional
        Geometric tolerance (default 0.05 Å).
    eigen_tol : float, optional
        Relative tolerance for eigenvalues (default None,
        internal worker will determine an appropriate float).
    quiet: bool
        If `True` warnings and debug messages will be disabled.

    Returns
    -------
    : str
        Schoenflies symbol (e.g. "C2v", "D3h", "Oh").

    Raises
    ------
    TypeError
        mol is not ase.Atoms.
    ValueError
        geom_tol <= 0 or mol is empty.
    RuntimeError
        point-group detection failed internally.
    """
    _change_global_variable(quiet)
    _validate_mol(mol, geom_tol, min_atoms=1)
    mol = mol.copy()
    mol.translate(-mol.get_center_of_mass())
    _set_tolerances(mol, geom_tol, eigen_tol)
    try:
        result = find_point_group(mol)
    except Exception as exc:
        raise RuntimeError(
            f"Point group detection failed for molecule with {len(mol)} atoms "
            f"and tolerance {geom_tol}. Original error: {exc}"
        ) from exc
    return result.pg

is_planar

is_planar(mol: Atoms, geom_tol: float = 0.05) -> bool

Check whether all atoms of a molecule lie in a common plane.

Parameters:

  • mol (Atoms) –

    Molecule to check planarity.

  • geom_tol (float, default: 0.05 ) –

    Geometric tolerance (default 0.05 Å).

Returns:

  • bool

    True if molecule is planar.

Raises:

  • TypeError

    mol is not ase.Atoms.

  • ValueError

    geom_tol <= 0 or mol has fewer than 3 atoms.

Source code in minimalsym/api.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
def is_planar(mol: Atoms, geom_tol: float = 0.05) -> bool:
    """
    Check whether all atoms of a molecule lie in a common plane.

    Parameters
    ----------
    mol : ase.Atoms
        Molecule to check planarity.
    geom_tol : float, optional
        Geometric tolerance (default 0.05 Å).

    Returns
    -------
    : bool
        True if molecule is planar.

    Raises
    ------
    TypeError
        mol is not ase.Atoms.
    ValueError
        geom_tol <= 0 or mol has fewer than 3 atoms.
    """
    _validate_mol(mol, geom_tol, min_atoms=3)
    mol = mol.copy()
    mol.translate(-mol.get_center_of_mass())

    mol.info["geom_tol"] = geom_tol
    return mol_is_planar(mol)

get_inequivalent

get_inequivalent(mol_in: Atoms, geom_tol: float = 0.3, eigen_tol: float | None = None, quiet: bool = True) -> tuple[ndarray, ndarray]

Find symmetry-inequivalent atoms using all symmetry operations.

Two atoms are in the same equivalence class if any symmetry operation (proper or improper) maps one onto the other.

Parameters:

  • mol_in (Atoms) –

    Molecule to find symmetry-inewuivalent atoms.

  • geom_tol (float, default: 0.3 ) –

    Geometric tolerance (default 0.3 Å)

  • eigen_tol (float, default: None ) –

    Relative tolerance for eigenvalues (default None, internal worker will determine an appropriate float).

  • quiet (bool, default: True ) –

    If True warnings and debug messages will be disabled.

Returns:

  • unique ( ndarray ) –

    sorted representative atom indices (one per class).

  • parent ( ndarray ) –

    parent[i] is the representative of atom i.

Raises:

  • TypeError
  • ValueError
  • RuntimeError
Source code in minimalsym/api.py
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
def get_inequivalent(mol_in: Atoms, geom_tol: float = 0.3, eigen_tol: float|None = None, quiet: bool = True) -> tuple[np.ndarray, np.ndarray]:
    """
    Find symmetry-inequivalent atoms using all symmetry operations.

    Two atoms are in the same equivalence class if any symmetry operation
    (proper or improper) maps one onto the other.

    Parameters
    ----------
    mol_in : ase.Atoms
        Molecule to find symmetry-inewuivalent atoms.
    geom_tol : float, optional
        Geometric tolerance (default 0.3 Å)
    eigen_tol : float, optional
        Relative tolerance for eigenvalues (default None,
        internal worker will determine an appropriate float).
    quiet: bool
        If `True` warnings and debug messages will be disabled.

    Returns
    -------
    unique : np.ndarray
        sorted representative atom indices (one per class).
    parent : np.ndarray
        parent[i] is the representative of atom i.

    Raises
    ------
    TypeError
    ValueError
    RuntimeError
    """
    _change_global_variable(quiet)
    _validate_mol(mol_in, geom_tol, min_atoms=1)
    mol_in = mol_in.copy()
    mol_in.translate(-mol_in.get_center_of_mass())
    _set_tolerances(mol_in, geom_tol, eigen_tol)

    try:
        asym_symtext = Symtext.from_molecule(mol_in)
    except Exception as exc:
        raise RuntimeError(f"Symtext construction failed: {exc}") from exc

    atom_map = asym_symtext.atom_map
    parent = np.arange(len(mol_in))
    _union_find_pass(atom_map, parent, range(atom_map.shape[1]))

    map_change_to = {parent[0]: 0}

    for ii, elem in enumerate(parent):
        if elem not in map_change_to.keys():
            map_change_to[elem] = ii

        parent[ii]=map_change_to[parent[ii]]

    return np.unique(parent), parent

symmetrize

symmetrize(mol_in: Atoms, geom_tol: float = 0.05, eigen_tol: float | None = None, quiet: bool = True) -> Atoms

Symmetrize the geometry of a molecule to exact point-group symmetry.

Concepts

SEA: Symmetry-equivalent atoms grouped by distance from center of mass. Symtext: Internal representation of symmetry elements and mappings.

Algorithm overview
  1. Build a Symtext at tolerance geom_tol to detect the point group and the atom permutation map.
  2. For each set of symmetry-equivalent atoms (SEA):

a. Linear molecules (C0v / D0h): project every atom onto the molecular axis (z). For D0h, inversion partners are handled explicitly.

b. Non-linear molecules: find the first non-trivial symmetry element that fixes the SEA representative (atom_i):

  - C_n axis: project position onto the axis.
  - sigma plane: project into the plane.
  - i or S_n: place at the origin.

c. Map the remaining SEA atoms from the representative using the stored matrix representation of the connecting symel.

  1. Set mol.info["geom_tol"] = 1e-12 on the result so downstream detection sees exact symmetry.

Parameters:

  • mol_in (Atoms) –

    Molecule to be symmetrized.

  • geom_tol (float, default: 0.05 ) –

    Tolerance for detecting the initial (possibly distorted) point group. Default is 0.05 Å.

  • eigen_tol (float, default: None ) –

    Relative tolerance for eigenvalues (default None, internal worker will determine an appropriate float).

  • quiet (bool, default: True ) –

    If True warnings and debug messages will be disabled.

Returns:

  • Atoms

    New Atoms object with symmetrized coordinates.

Raises:

  • RuntimeError

    If Symtext construction fails.

  • Exception

    Re-raises unexpected symmetry element types.

Source code in minimalsym/api.py
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
def symmetrize(mol_in: Atoms, geom_tol: float = 0.05, eigen_tol: float|None = None, quiet: bool = True) -> Atoms:
    """
    Symmetrize the geometry of a molecule to exact point-group symmetry.

    Concepts
    --------
    SEA: Symmetry-equivalent atoms grouped by distance from center of mass.
    Symtext: Internal representation of symmetry elements and mappings.

    Algorithm overview
    ------------------
    1. Build a Symtext at tolerance *geom_tol* to detect the point group
       and the atom permutation map.
    2. For each set of symmetry-equivalent atoms (SEA):

       a. **Linear molecules** (C0v / D0h): project every atom onto the
          molecular axis (z). For D0h, inversion partners are handled
          explicitly.

       b. **Non-linear molecules**: find the first non-trivial symmetry element
          that fixes the SEA representative (atom_i):

          - C_n axis: project position onto the axis.
          - sigma plane: project into the plane.
          - i or S_n: place at the origin.

       c. Map the remaining SEA atoms from the representative using the stored
          matrix representation of the connecting symel.

    3. Set mol.info["geom_tol"] = 1e-12 on the result so downstream detection
       sees exact symmetry.

    Parameters
    ----------
    mol_in : ase.Atoms
        Molecule to be symmetrized.
    geom_tol : float, optional
        Tolerance for detecting the initial (possibly distorted) point group.
        Default is 0.05 Å.
    eigen_tol : float, optional
        Relative tolerance for eigenvalues (default None,
        internal worker will determine an appropriate float).
    quiet: bool
        If `True` warnings and debug messages will be disabled.

    Returns
    -------
    : ase.Atoms
        New Atoms object with symmetrized coordinates.

    Raises
    ------
    RuntimeError
        If Symtext construction fails.
    Exception
        Re-raises unexpected symmetry element types.
    """
    _change_global_variable(quiet)
    mol_in = mol_in.copy()

    mol_in.translate(-mol_in.get_center_of_mass())
    _set_tolerances(mol_in, geom_tol, eigen_tol)

    try:
        asym_symtext = Symtext.from_molecule(mol_in)
    except Exception as exc:
        raise RuntimeError(
            f"Symtext construction failed during symmetrize: {exc}"
        ) from exc

    mol = asym_symtext.mol

    seas = get_SEAs_from_atom_map(asym_symtext.atom_map)

    for sea in seas:
        atom_i = sea.subset[0]

        if asym_symtext.pg.is_linear:
            _project_linear(mol, sea, asym_symtext)
            continue

        _project_atom(mol, atom_i, asym_symtext)
        _force_symmetry_from_representative(mol, atom_i, asym_symtext)

    return mol

generate_symmetry_candidates

generate_symmetry_candidates(mol_in: Atoms, geom_tol: float = 0.05, eigen_tol: float | None = None, sort_by: int = 0, quiet: bool = True) -> list[SymmetryResult]

Generate symmetry-consistent geometries compatible with a detected point group.

Concepts

SEA: Symmetry-equivalent atoms grouped by distance from center of mass.

Symtext: Internal representation of symmetry elements and mappings.

Algorithm overview
  1. Build a Symtext at tolerance geom_tol to detect the approximate point group of the input structure.
  2. Decompose the detected point group into all compatible subgroups.
  3. For each candidate point group:

    a. Construct a Symtext object consistent with that subgroup.

    b. For each set of symmetry-equivalent atoms (SEA):

    - **Linear molecules** (C∞v / D∞h approximations): project atoms
    onto the molecular axis.
    
    - **Non-linear molecules**:
        - Project a representative atom onto the appropriate symmetry
        element (axis, plane, inversion center, etc.).
        - Map the remaining SEA atoms from the representative using
        the stored symmetry operations.
    

    c. Compute the RMSD between the original (aligned) geometry and the symmetrized structure.

  4. Return all generated symmetrized molecules along with their point groups and RMSD values, sorted according to sort_by parameter.

Parameters:

  • mol_in (Atoms) –

    Molecule to analyze and symmetrize into multiple candidate symmetries.

  • geom_tol (float, default: 0.05 ) –

    Tolerance for detecting the initial (possibly distorted) point group. Default is 0.05 Å.

  • eigen_tol (float, default: None ) –

    Relative tolerance for eigenvalues (default None, an internal routine determines an appropriate value).

  • sort_by (int, default: 0 ) –

    Controls sorting of the returned list:

    • 0 := sort by point group "size" (descending number of symmetry operations), then by RMSD (ascending).

    • 1 := sort by RMSD (ascending), then by point group "size" (descending).

  • quiet (bool, default: True ) –

    If True warnings and debug messages will be disabled.

Returns:

  • list[SymmetryResult]

    Symmetrized candidates with their point group and RMSD.

Raises:

  • RuntimeError

    If Symtext construction fails for the initial molecule or any candidate point group.

  • Exception

    Propagates unexpected exceptions encountered during projection or mapping.

Source code in minimalsym/api.py
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
def generate_symmetry_candidates(mol_in: Atoms, geom_tol: float = 0.05, eigen_tol: float|None = None, sort_by:int = 0, quiet: bool = True) -> list[SymmetryResult]:
    """
    Generate symmetry-consistent geometries compatible with a detected point group.

    Concepts
    --------
    SEA: Symmetry-equivalent atoms grouped by distance from center of mass.

    Symtext: Internal representation of symmetry elements and mappings.

    Algorithm overview
    ------------------
    1. Build a Symtext at tolerance *geom_tol* to detect the approximate
       point group of the input structure.
    2. Decompose the detected point group into all compatible subgroups.
    3. For each candidate point group:

        a. Construct a Symtext object consistent with that subgroup.

        b. For each set of symmetry-equivalent atoms (SEA):

            - **Linear molecules** (C∞v / D∞h approximations): project atoms
            onto the molecular axis.

            - **Non-linear molecules**:
                - Project a representative atom onto the appropriate symmetry
                element (axis, plane, inversion center, etc.).
                - Map the remaining SEA atoms from the representative using
                the stored symmetry operations.

        c. Compute the RMSD between the original (aligned) geometry and the
          symmetrized structure.

    4. Return all generated symmetrized molecules along with their point
       groups and RMSD values, sorted according to `sort_by` parameter.

    Parameters
    ----------
    mol_in : ase.Atoms
        Molecule to analyze and symmetrize into multiple candidate symmetries.
    geom_tol : float, optional
        Tolerance for detecting the initial (possibly distorted) point group.
        Default is 0.05 Å.
    eigen_tol : float, optional
        Relative tolerance for eigenvalues (default None,
        an internal routine determines an appropriate value).
    sort_by : int, optional
        Controls sorting of the returned list:

        - 0 := sort by point group "size" (descending number of symmetry operations),
            then by RMSD (ascending).

        - 1 := sort by RMSD (ascending), then by point group "size" (descending).
    quiet: bool
        If `True` warnings and debug messages will be disabled.

    Returns
    -------
    : list[SymmetryResult]
        Symmetrized candidates with their point group and RMSD.

    Raises
    ------
    RuntimeError
        If Symtext construction fails for the initial molecule or any
        candidate point group.
    Exception
        Propagates unexpected exceptions encountered during projection
        or mapping.
    """
    _change_global_variable(quiet)
    mol_in = mol_in.copy()

    mol_in.translate(-mol_in.get_center_of_mass())
    _set_tolerances(mol_in, geom_tol, eigen_tol)

    try:
        asym_symtext = Symtext.from_molecule(mol_in)
    except Exception as exc:
        raise RuntimeError(
            f"Symtext construction failed during symmetrize: {exc}"
        ) from exc

    mol = asym_symtext.mol

    if asym_symtext.pg.is_linear:
        pass
        mol.info['geom_tol'] *= 2

    repeated_pgr = _decompose_point_group(asym_symtext.pg)

    if asym_symtext.pg.family == 'I':
        invertable = asym_symtext.pg.subfamily is not None
        extension_pgr = _check_O_point_group(mol, invertable)
        repeated_pgr.extend(extension_pgr)

    if asym_symtext.pg.family == 'I' or asym_symtext.pg.family == 'O' or asym_symtext.pg.family == 'T':
        extension_pgr = _check_general_point_group(mol)
        repeated_pgr.extend(extension_pgr)

    possible_pgr = _unique_point_group(repeated_pgr)

    sym_listmol = []

    for curr_pgr in possible_pgr:
        try:
            curr_asym_symtext = Symtext.from_PointGroupResult(mol, curr_pgr, asym_symtext.pg.is_linear)
        except Exception as exc:
            raise RuntimeError(
                f"Symtext construction failed during case {curr_pgr}: {exc}"
            ) from exc

        curr_mol = curr_asym_symtext.mol

        curr_SEAs = get_SEAs_from_atom_map(curr_asym_symtext.atom_map)

        for sea in curr_SEAs:
            atom_i = sea.subset[0]

            if curr_asym_symtext.pg.is_linear:
                _project_linear(curr_mol, sea, curr_asym_symtext)
                continue

            _project_atom(curr_mol, atom_i, curr_asym_symtext)
            _force_symmetry_from_representative(curr_mol, sea, atom_i, curr_asym_symtext)

        rmsd = _get_error(transform(mol.positions, curr_asym_symtext.rotate_to_std), curr_mol.positions)

        sym_listmol.append(SymmetryResult(
            mol=curr_mol,
            pg=curr_pgr.pg,
            rmsd=rmsd,
        ))

    if sort_by == 0:
        sym_listmol.sort(
            key=lambda x: (- _find_pg_score(x.pg), x.rmsd)
        )
    else:
        sym_listmol.sort(
            key=lambda x: (x.rmsd, - _find_pg_score(x.pg))
        )

    return sym_listmol