diff --git a/gui/tests/env.js b/gui/tests/env.js
index eef93eb2081037b4b6eb8cb950aaa74254b496a8..1229b90c75f549bce4b06c4d23061fcd49ed45e1 100644
--- a/gui/tests/env.js
+++ b/gui/tests/env.js
@@ -2077,7 +2077,7 @@ window.nomadEnv = {
           "name": "parsers/edmft",
           "description": "NOMAD parser for EDMFT.",
           "plugin_package": "electronicparsers",
-          "level": 0,
+          "level": 2,
           "aliases": [
             "parsers/edmft"
           ],
@@ -2220,7 +2220,7 @@ window.nomadEnv = {
           "name": "parsers/magres",
           "description": "NOMAD parser for MAGRES.",
           "plugin_package": "electronicparsers",
-          "level": 0,
+          "level": 1,
           "aliases": [
             "parsers/magres"
           ],
@@ -2481,7 +2481,7 @@ window.nomadEnv = {
           "name": "parsers/tbstudio",
           "description": "NOMAD parser for TBSTUDIO.",
           "plugin_package": "electronicparsers",
-          "level": 0,
+          "level": 1,
           "aliases": [
             "parsers/tbstudio"
           ],
@@ -2533,7 +2533,7 @@ window.nomadEnv = {
           "name": "parsers/w2dynamics",
           "description": "NOMAD parser for W2DYNAMICS.",
           "plugin_package": "electronicparsers",
-          "level": 0,
+          "level": 2,
           "aliases": [
             "parsers/w2dynamics"
           ],
@@ -2555,7 +2555,7 @@ window.nomadEnv = {
           "name": "parsers/wannier90",
           "description": "NOMAD parser for WANNIER90.",
           "plugin_package": "electronicparsers",
-          "level": 0,
+          "level": 1,
           "aliases": [
             "parsers/wannier90"
           ],
diff --git a/nomad/app/v1/routers/systems.py b/nomad/app/v1/routers/systems.py
index 36748041d27430c43d74e9282662dcbcd449a1be..8c7e375aa9563d1e1a76590d6ca7576591014230 100644
--- a/nomad/app/v1/routers/systems.py
+++ b/nomad/app/v1/routers/systems.py
@@ -26,15 +26,12 @@ from fastapi import APIRouter, Depends, Path, Query, status, HTTPException
 from fastapi.responses import Response
 import ase.io
 import ase.build
-from MDAnalysis.lib.util import NamedStream
-from MDAnalysis.coordinates.PDB import PDBWriter
 
 from nomad.units import ureg
 from nomad.utils import strip, deep_get, query_list_to_dict
 from nomad.atomutils import Formula, wrap_positions, unwrap_positions
 from nomad.normalizing.common import (
     ase_atoms_from_nomad_atoms,
-    mda_universe_from_nomad_atoms,
 )
 from nomad.datamodel.metainfo.system import Atoms as NOMADAtoms
 from .entries import answer_entry_archive_request
@@ -72,36 +69,16 @@ def write_pdb(atoms: NOMADAtoms, entry_id: str = None, formula: str = None) -> s
         lines.append(
             f'REMARK 285  C: {cell[2, 0]:.3f}, {cell[2, 1]:.3f}, {cell[2, 2]:.3f}\n'
         )
-    else:
-        lines.append(
-            'REMARK 285 UNITARY VALUES FOR THE UNIT CELL SET BECAUSE UNIT CELL INFORMATION\n'
-        )
-        lines.append(
-            'REMARK 285 WAS MISSING. PROTEIN DATA BANK CONVENTIONS REQUIRE THAT CRYST1\n'
-        )
-        lines.append(
-            'REMARK 285 RECORD IS INCLUDED, BUT THE VALUES ON THIS RECORD ARE MEANINGLESS.\n'
-        )
     if pbc is not None:
         pbc = ['TRUE' if x else 'FALSE' for x in pbc]
         lines.append(f'REMARK 285 PBC (A, B, C): {pbc[0]}, {pbc[1]}, {pbc[2]}\n')
 
-    mda_string_stream = StringIO()
-    mda_named_stream = NamedStream(
-        mda_string_stream, f'temp.{format}', close=False, reset=False
-    )
-    writer = PDBWriter(mda_named_stream, remarks='')
-    universe = mda_universe_from_nomad_atoms(atoms)
-    writer.write(universe)
-    writer.close()
-
-    # We skip the title line that is written by MDA (cannot be disabled otherwise)
-    mda_string_stream.seek(0)
-    for line in mda_string_stream.readlines():
-        if not line.startswith(('REMARK', 'TITLE')):
-            lines.append(line)
-
+    stream = StringIO()
+    atoms = ase_atoms_from_nomad_atoms(atoms)
+    ase.io.write(stream, atoms, format='proteindatabank')
+    stream.seek(0)
     content = ''.join(lines)
+    content += stream.read()
     return content
 
 
diff --git a/nomad/atomutils.py b/nomad/atomutils.py
index c95aab394953d144f510fe5a461ff744d8eb9477..07e9959918b2373c75426b692d8cd765d50c5c86 100644
--- a/nomad/atomutils.py
+++ b/nomad/atomutils.py
@@ -23,16 +23,11 @@ import itertools
 import logging
 import math
 import re
-import warnings
-from array import array
-from collections import namedtuple
 from functools import reduce
-from itertools import chain
 from string import ascii_uppercase
 from typing import (
     TYPE_CHECKING,
     Any,
-    Callable,
     Dict,
     Iterable,
     List,
@@ -43,27 +38,15 @@ from typing import (
 
 import ase.data
 import ase.geometry
-import MDAnalysis
-import MDAnalysis.analysis.rdf as MDA_RDF
-import networkx
 import numpy as np
 from ase import Atoms
 from ase.formula import Formula as ASEFormula
 from ase.utils import pbc2pbc
-from MDAnalysis.core._get_readers import get_reader_for
-from MDAnalysis.core.topology import Topology
-from MDAnalysis.core.universe import Universe
-from nptyping import Int, NDArray
-from pymatgen.core import Composition
-from pymatgen.core.periodic_table import get_el_sp
-from scipy import sparse
+from nptyping import NDArray
 from scipy.spatial import Voronoi  # pylint: disable=no-name-in-module
-from scipy.stats import linregress
 
 from nomad.aflow_prototypes import aflow_prototypes
 from nomad.constants import atomic_masses
-from nomad.metainfo import MSection
-from nomad.units import ureg
 
 valid_elements = set(ase.data.chemical_symbols[1:])
 
@@ -944,6 +927,8 @@ class Formula:
         # Try if can be interpreted as fractional formula
         except Exception:
             try:
+                from pymatgen.core import Composition
+
                 self.ase_formula = ASEFormula(
                     Composition(formula).get_integer_formula_and_factor()[0]
                 )
@@ -1150,6 +1135,8 @@ class Formula:
 
     def _formula_iupac(self) -> str:
         """Returns the IUPAC formula."""
+        from pymatgen.core.periodic_table import get_el_sp
+
         count = self.count()
         counts = count.values()
         symbols = list(count.keys())
@@ -1274,1129 +1261,3 @@ class Formula:
             Chemical formula as a string.
         """
         return ''.join(symb + (str(n) if n > 1 else '') for symb, n in dct.items())
-
-
-def create_empty_universe(
-    n_atoms: int,
-    n_frames: int = 1,
-    n_residues: int = 1,
-    n_segments: int = 1,
-    atom_resindex: NDArray[Int] = None,
-    residue_segindex: NDArray[Int] = None,
-    flag_trajectory: bool = False,
-    flag_velocities: bool = False,
-    flag_forces: bool = False,
-    timestep: float = None,
-) -> MDAnalysis.Universe:
-    """Create a blank Universe
-
-    This function was adapted from the function empty() within the MDA class Universe().
-    The only difference is that the Universe() class is imported directly here, whereas in the
-    original function is is passed as a function argument, since the function there is a classfunction.
-
-    Useful for building a Universe without requiring existing files,
-    for example for system building.
-
-    If `flag_trajectory` is set to True, a
-    :class:`MDAnalysis.coordinates.memory.MemoryReader` will be
-    attached to the Universe.
-
-    Parameters
-    ----------
-    n_atoms: int
-      number of Atoms in the Universe
-    n_residues: int, default 1
-      number of Residues in the Universe, defaults to 1
-    n_segments: int, default 1
-      number of Segments in the Universe, defaults to 1
-    atom_resindex: array like, optional
-      mapping of atoms to residues, e.g. with 6 atoms,
-      `atom_resindex=[0, 0, 1, 1, 2, 2]` would put 2 atoms
-      into each of 3 residues.
-    residue_segindex: array like, optional
-      mapping of residues to segments
-    flag_trajectory: bool, optional
-      if True, attaches a :class:`MDAnalysis.coordinates.memory.MemoryReader`
-      allowing coordinates to be set and written.  Default is False
-    flag_velocities: bool, optional
-      include velocities in the :class:`MDAnalysis.coordinates.memory.MemoryReader`
-    flag_forces: bool, optional
-      include forces in the :class:`MDAnalysis.coordinates.memory.MemoryReader`
-
-    Returns
-    -------
-    MDAnalysis.Universe object
-
-    Examples
-    --------
-    For example to create a new Universe with 6 atoms in 2 residues, with
-    positions for the atoms and a mass attribute:
-
-    >>> u = mda.Universe.empty(6, 2,
-                                atom_resindex=np.array([0, 0, 0, 1, 1, 1]),
-                                flag_trajectory=True,
-            )
-    >>> u.add_TopologyAttr('masses')
-
-    .. versionadded:: 0.17.0
-    .. versionchanged:: 0.19.0
-        The attached Reader when flag_trajectory=True is now a MemoryReader
-    .. versionchanged:: 1.0.0
-        Universes can now be created with 0 atoms
-    """
-
-    if not n_atoms:
-        n_residues = 0
-        n_segments = 0
-
-    if atom_resindex is None:
-        warnings.warn(
-            'Residues specified but no atom_resindex given.  '
-            'All atoms will be placed in first Residue.',
-            UserWarning,
-        )
-
-    if residue_segindex is None:
-        warnings.warn(
-            'Segments specified but no segment_resindex given.  '
-            'All residues will be placed in first Segment',
-            UserWarning,
-        )
-
-    topology = Topology(
-        n_atoms,
-        n_residues,
-        n_segments,
-        atom_resindex=atom_resindex,
-        residue_segindex=residue_segindex,
-    )
-
-    universe = Universe(topology)
-
-    if flag_trajectory:
-        coords = np.zeros((n_frames, n_atoms, 3), dtype=np.float32)
-        vels = np.zeros_like(coords) if flag_velocities else None
-        forces = np.zeros_like(coords) if flag_forces else None
-
-        # grab and attach a MemoryReader
-        universe.trajectory = get_reader_for(coords)(
-            coords,
-            order='fac',
-            n_atoms=n_atoms,
-            velocities=vels,
-            forces=forces,
-            dt=timestep,
-        )
-
-    return universe
-
-
-def archive_to_universe(
-    archive,
-    system_index: int = 0,
-    method_index: int = -1,
-    model_index: int = -1,
-) -> MDAnalysis.Universe:
-    """Extract the topology from a provided run section of an archive entry
-
-    Input:
-
-        archive_sec_run: section run of an EntryArchive
-
-        system_index: list index of archive.run[].system to be used for topology extraction
-
-        method_index: list index of archive.run[].method to be used for atom parameter (charges and masses) extraction
-
-        model_index: list index of archive.run[].method[].force_field.model for bond list extraction
-
-    Variables:
-
-        n_frames (int):
-
-        n_atoms (int):
-
-        atom_names (str, shape=(n_atoms)):
-
-        atom_types (str, shape=(n_atoms)):
-
-        atom_resindex (str, shape=(n_atoms)):
-
-        atom_segids (str, shape=(n_atoms)):
-
-        n_segments (int): Segments correspond to a group of the same type of molecules.
-
-        n_residues (int): The number of distinct residues (nb - individual molecules are also denoted as a residue).
-
-        resnames (str, shape=(n_residues)): The name of each residue.
-
-        residue_segindex (int, shape=(n_residues)): The segment index that each residue belongs to.
-
-        residue_molnums (int, shape=(n_residues)): The molecule index that each residue belongs to.
-
-        residue_moltypes (int, shape=(n_residues)): The molecule type of each residue.
-
-        n_molecules (int):
-
-        masses (float, shape=(n_atoms)):  atom masses, units = amu
-
-        charges (float, shape=(n_atoms)): atom partial charges, units = e
-
-        positions (float, shape=(n_frames,n_atoms,3)): atom positions
-
-        velocities (float, shape=(n_frames,n_atoms,3)): atom velocities
-
-        dimensions (float, shape=(n_frames,6)): box dimensions (nb - currently assuming a cubic box!)
-
-        bonds (tuple, shape=([])): list of tuples with the atom indices of each bond
-    """
-
-    try:
-        sec_run = archive.run[-1]
-        sec_system = sec_run.system
-        sec_system_top = sec_run.system[system_index]
-        sec_atoms = sec_system_top.atoms
-        sec_atoms_group = sec_system_top.atoms_group
-        sec_calculation = sec_run.calculation
-        sec_method = (
-            sec_run.method[method_index] if sec_run.get('method') is not None else None
-        )
-    except IndexError:
-        logging.warning(
-            'Supplied indices or necessary sections do not exist in archive. Cannot build the MDA universe.'
-        )
-        return None
-
-    n_atoms = sec_atoms.get('n_atoms')
-    if n_atoms is None:
-        logging.warning('No atoms found in the archive. Cannot build the MDA universe.')
-        return None
-
-    n_frames = len(sec_system) if sec_system is not None else None
-    atom_names = sec_atoms.get('labels')
-    model_atom_parameters = sec_method.get('atom_parameters')
-    atom_types = (
-        [atom.label for atom in model_atom_parameters]
-        if model_atom_parameters
-        else atom_names
-    )
-    atom_resindex = np.arange(n_atoms)
-    atoms_segindices = np.empty(n_atoms)
-    atom_segids = np.array(range(n_atoms), dtype='object')
-    molecule_groups = sec_atoms_group
-    n_segments = len(molecule_groups)
-
-    n_residues = 0
-    n_molecules = 0
-    residue_segindex = []
-    resnames = []
-    residue_moltypes = []
-    residue_min_atom_index = []
-    residue_n_atoms = []
-    molecule_n_res = []
-    for mol_group_ind, mol_group in enumerate(molecule_groups):
-        atoms_segindices[mol_group.atom_indices] = mol_group_ind
-        atom_segids[mol_group.atom_indices] = mol_group.label
-        molecules = mol_group.atoms_group if mol_group.atoms_group is not None else []
-        for mol in molecules:
-            monomer_groups = mol.atoms_group
-            mol_res_counter = 0
-            if monomer_groups:
-                for mon_group in monomer_groups:
-                    monomers = mon_group.atoms_group
-                    for mon in monomers:
-                        resnames.append(mon.label)
-                        residue_segindex.append(mol_group_ind)
-                        residue_moltypes.append(mol.label)
-                        residue_min_atom_index.append(np.min(mon.atom_indices))
-                        residue_n_atoms.append(len(mon.atom_indices))
-                        n_residues += 1
-                        mol_res_counter += 1
-            else:  # no monomers => whole molecule is it's own residue
-                resnames.append(mol.label)
-                residue_segindex.append(mol_group_ind)
-                residue_moltypes.append(mol.label)
-                residue_min_atom_index.append(np.min(mol.atom_indices))
-                residue_n_atoms.append(len(mol.atom_indices))
-                n_residues += 1
-                mol_res_counter += 1
-            molecule_n_res.append(mol_res_counter)
-            n_molecules += 1
-
-    # reorder the residues by atom_indices
-    residue_data = np.array(
-        [
-            [
-                residue_min_atom_index[i],
-                residue_n_atoms[i],
-                residue_segindex[i],
-                residue_moltypes[i],
-                resnames[i],
-            ]
-            for i in range(len(residue_min_atom_index))
-        ],
-        dtype=object,
-    )
-    residue_data = np.array(sorted(residue_data, key=lambda x: x[0], reverse=False)).T
-    residue_n_atoms = residue_data[1].astype(int)
-    residue_segindex = residue_data[2].astype(int)
-    residue_moltypes = residue_data[3]
-    resnames = residue_data[4]
-    res_index_counter = 0
-    for i_residue, res_n_atoms in enumerate(residue_n_atoms):
-        atom_resindex[res_index_counter : res_index_counter + res_n_atoms] = i_residue  # type: ignore
-        res_index_counter += res_n_atoms
-    residue_molnums = np.array(range(n_residues))
-    mol_index_counter = 0
-    for i_molecule, n_res in enumerate(molecule_n_res):
-        residue_molnums[mol_index_counter : mol_index_counter + n_res] = i_molecule
-        mol_index_counter += n_res
-
-    # get the atom masses and charges
-
-    masses = np.empty(n_atoms)
-    charges = np.empty(n_atoms)
-    atom_parameters = (
-        sec_method.get('atom_parameters') if sec_method is not None else []
-    )
-    atom_parameters = atom_parameters if atom_parameters is not None else []
-
-    for atom_ind, atom in enumerate(atom_parameters):
-        if atom.get('mass'):
-            masses[atom_ind] = ureg.convert(
-                atom.mass.magnitude, atom.mass.units, ureg.amu
-            )
-        if atom.get('charge'):
-            charges[atom_ind] = ureg.convert(
-                atom.charge.magnitude, atom.charge.units, ureg.e
-            )
-
-    # get the atom positions, velocites, and box dimensions
-    positions = np.empty(shape=(n_frames, n_atoms, 3))
-    velocities = np.empty(shape=(n_frames, n_atoms, 3))
-    dimensions = np.empty(shape=(n_frames, 6))
-    for frame_ind, frame in enumerate(sec_system):
-        sec_atoms_fr = frame.get('atoms')
-        if sec_atoms_fr is not None:
-            positions_frame = sec_atoms_fr.positions
-            positions[frame_ind] = (
-                ureg.convert(
-                    positions_frame.magnitude, positions_frame.units, ureg.angstrom
-                )
-                if positions_frame is not None
-                else None
-            )
-            velocities_frame = sec_atoms_fr.velocities
-            velocities[frame_ind] = (
-                ureg.convert(
-                    velocities_frame.magnitude,
-                    velocities_frame.units,
-                    ureg.angstrom / ureg.picosecond,
-                )
-                if velocities_frame is not None
-                else None
-            )
-            latt_vec_tmp = sec_atoms_fr.get('lattice_vectors')
-            if latt_vec_tmp is not None:
-                length_conversion = ureg.convert(
-                    1.0, sec_atoms_fr.lattice_vectors.units, ureg.angstrom
-                )
-                dimensions[frame_ind] = [
-                    sec_atoms_fr.lattice_vectors.magnitude[0][0] * length_conversion,
-                    sec_atoms_fr.lattice_vectors.magnitude[1][1] * length_conversion,
-                    sec_atoms_fr.lattice_vectors.magnitude[2][2] * length_conversion,
-                    90,
-                    90,
-                    90,
-                ]  # TODO: extend to non-cubic boxes
-
-    # get the bonds  # TODO extend to multiple storage options for interactions
-    bonds = sec_atoms.bond_list
-    if bonds is None:
-        bonds = get_bond_list_from_model_contributions(
-            sec_run, method_index=-1, model_index=-1
-        )
-
-    # get the system times
-    system_timestep = 1.0 * ureg.picosecond
-
-    def approx(a, b, rel_tol=1e-09, abs_tol=0.0):
-        return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
-
-    system_times = [calc.time for calc in sec_calculation if calc.system_ref]
-    if system_times:
-        try:
-            method = archive.workflow2.method
-            system_timestep = (
-                method.integration_timestep * method.coordinate_save_frequency
-            )
-        except Exception:
-            logging.warning(
-                'Cannot find the system times. MDA universe will contain non-physical times and timestep.'
-            )
-    else:
-        time_steps = [
-            system_times[i_time] - system_times[i_time - 1]
-            for i_time in range(1, len(system_times))
-        ]
-        if all(approx(time_steps[0], time_step) for time_step in time_steps):
-            system_timestep = ureg.convert(
-                time_steps[0].magnitude, ureg.second, ureg.picosecond
-            )
-        else:
-            logging.warning(
-                'System times are not equally spaced. Cannot set system times in MDA universe.'
-                ' MDA universe will contain non-physical times and timestep.'
-            )
-
-    system_timestep = ureg.convert(
-        system_timestep, system_timestep._units, ureg.picoseconds
-    )
-
-    # create the Universe
-    metainfo_universe = create_empty_universe(
-        n_atoms,
-        n_frames=n_frames,
-        n_residues=n_residues,
-        n_segments=n_segments,
-        atom_resindex=atom_resindex,
-        residue_segindex=residue_segindex,
-        flag_trajectory=True,
-        flag_velocities=True,
-        timestep=system_timestep.magnitude,
-    )
-
-    # set the positions and velocities
-    for frame_ind, frame in enumerate(metainfo_universe.trajectory):
-        metainfo_universe.atoms.positions = positions[frame_ind]
-        metainfo_universe.atoms.velocities = velocities[frame_ind]
-
-    # add the atom attributes
-    metainfo_universe.add_TopologyAttr('name', atom_names)
-    metainfo_universe.add_TopologyAttr('type', atom_types)
-    metainfo_universe.add_TopologyAttr('mass', masses)
-    metainfo_universe.add_TopologyAttr('charge', charges)
-    if n_segments != 0:
-        metainfo_universe.add_TopologyAttr('segids', np.unique(atom_segids))
-    if n_residues != 0:
-        metainfo_universe.add_TopologyAttr('resnames', resnames)
-        metainfo_universe.add_TopologyAttr('resids', np.unique(atom_resindex) + 1)
-        metainfo_universe.add_TopologyAttr('resnums', np.unique(atom_resindex) + 1)
-    if len(residue_molnums) > 0:
-        metainfo_universe.add_TopologyAttr('molnums', residue_molnums)
-    if len(residue_moltypes) > 0:
-        metainfo_universe.add_TopologyAttr('moltypes', residue_moltypes)
-
-    # add the box dimensions
-    for frame_ind, frame in enumerate(metainfo_universe.trajectory):
-        metainfo_universe.atoms.dimensions = dimensions[frame_ind]
-
-    # add the bonds
-    if hasattr(metainfo_universe, 'bonds'):
-        logging.warning('archive_to_universe() failed, universe already has bonds.')
-        return None
-    metainfo_universe.add_TopologyAttr('bonds', bonds)
-
-    return metainfo_universe
-
-
-class BeadGroup(object):
-    # see https://github.com/MDAnalysis/mdanalysis/issues/1891#issuecomment-387138110
-    # by @richardjgowers with performance improvements
-    def __init__(self, atoms, compound='fragments'):
-        """Initialize with an AtomGroup instance.
-        Will split based on keyword 'compounds' (residues or fragments).
-        """
-        self._atoms = atoms
-        self.compound = compound
-        self._nbeads = len(getattr(self._atoms, self.compound))
-        # for caching
-        self._cache = {}
-        self._cache['positions'] = None
-        self.__last_frame = None
-
-    def __len__(self):
-        return self._nbeads
-
-    @property
-    def positions(self):
-        # cache positions for current frame
-        if self.universe.trajectory.frame != self.__last_frame:
-            self._cache['positions'] = self._atoms.center_of_mass(
-                unwrap=True, compound=self.compound
-            )
-            self.__last_frame = self.universe.trajectory.frame
-        return self._cache['positions']
-
-    @property  # type: ignore
-    @MDAnalysis.lib.util.cached('universe')
-    def universe(self):
-        return self._atoms.universe
-
-
-def __log_indices(first: int, last: int, num: int = 100):
-    ls = np.logspace(0, np.log10(last - first + 1), num=num)
-    return np.unique(np.int_(ls) - 1 + first)
-
-
-def __correlation(function, positions: List[float]):
-    iterator = iter(positions)
-    start_frame = next(iterator)
-    return map(lambda f: function(start_frame, f), chain([start_frame], iterator))
-
-
-def shifted_correlation_average(
-    function: Callable,
-    times: NDArray,
-    positions: NDArray,
-    index_distribution: Callable = __log_indices,
-    correlation: Callable = __correlation,
-    segments: int = 10,
-    window: float = 0.5,
-    skip: int = 0,
-) -> tuple[NDArray, NDArray]:
-    """
-    Code adapted from MDevaluate module: https://github.com/mdevaluate/mdevaluate.git
-
-    Calculate the time series for a correlation function.
-
-    The times at which the correlation is calculated are determined automatically by the
-    function given as ``index_distribution``. The default is a logarithmic distribution.
-
-    The function has been edited so that the average is always calculated, i.e., average=True below.
-
-    Args:
-        function:   The function that should be correlated
-        positions:     The coordinates of the simulation data
-        index_distribution (opt.):
-                    A function that returns the indices for which the timeseries
-                    will be calculated
-        correlation (function, opt.):
-                    The correlation function
-        segments (int, opt.):
-                    The number of segments the time window will be shifted
-        window (float, opt.):
-                    The fraction of the simulation the time series will cover
-        skip (float, opt.):
-                    The fraction of the trajectory that will be skipped at the beginning,
-                    if this is None the start index of the frames slice will be used,
-                    which defaults to 0.
-        counter (bool, opt.):
-                    If True, returns length of frames (in general number of particles specified)
-        average (bool, opt.):
-                    If True,
-    Returns:
-        tuple:
-            A list of length N that contains the indices of the frames at which
-            the time series was calculated and a numpy array of shape (segments, N)
-            that holds the (non-avaraged) correlation data
-
-            if has_counter == True: adds number of counts to output tupel.
-                                    if average is returned it will be weighted.
-
-    Example:
-        Calculating the mean square displacement of a coordinates object named ``coords``:
-
-        >>> indices, data = shifted_correlation(msd, coords)
-    """
-    if window + skip >= 1:
-        warnings.warn(
-            'Invalid parameters for shifted_correlation(), ' 'resetting to defaults.',
-            UserWarning,
-        )
-        window = 0.5
-        skip = 0
-
-    start_frames = np.unique(
-        np.linspace(
-            len(positions) * skip,
-            len(positions) * (1 - window),
-            num=segments,
-            endpoint=False,
-            dtype=int,
-        )
-    )
-    num_frames = int(len(positions) * (window))
-
-    idx = index_distribution(0, num_frames)
-
-    def correlate(start_frame):
-        shifted_idx = idx + start_frame
-        return correlation(function, map(positions.__getitem__, shifted_idx))
-
-    correlation_times = np.array([times[i] for i in idx]) - times[0]
-
-    result: NDArray
-    for i_start_frame, start_frame in enumerate(start_frames):
-        if i_start_frame == 0:
-            result = np.array(list(correlate(start_frame)))
-        else:
-            result += np.array(list(correlate(start_frame)))
-    result = np.array(result)
-    result = result / len(start_frames)
-
-    return correlation_times, result
-
-
-def _calc_diffusion_constant(
-    times: NDArray, values: NDArray, dim: int = 3
-) -> tuple[float, float]:
-    """
-    Determines the diffusion constant from a fit of the mean squared displacement
-    vs. time according to the Einstein relation.
-    """
-    linear_model = linregress(times, values)
-    slope = linear_model.slope
-    error = linear_model.rvalue
-    return slope * 1 / (2 * dim), error
-
-
-def _get_molecular_bead_groups(
-    universe: MDAnalysis.Universe, moltypes: List[str] = None
-) -> Dict[str, BeadGroup]:
-    """
-    Creates bead groups based on the molecular types as defined by the MDAnalysis universe.
-    """
-    if moltypes is None:
-        atoms_moltypes = getattr(universe.atoms, 'moltypes', [])
-        moltypes = np.unique(atoms_moltypes)
-    bead_groups = {}
-    for moltype in moltypes:
-        ags_by_moltype = universe.select_atoms('moltype ' + moltype)
-        ags_by_moltype = ags_by_moltype[
-            ags_by_moltype.masses > abs(1e-2)
-        ]  # remove any virtual/massless sites (needed for, e.g., 4-bead water models)
-        bead_groups[moltype] = BeadGroup(ags_by_moltype, compound='fragments')
-
-    return bead_groups
-
-
-def calc_molecular_rdf(
-    universe: MDAnalysis.Universe,
-    n_traj_split: int = 10,
-    n_prune: int = 1,
-    interval_indices=None,
-    max_mols: int = 5000,
-) -> Dict:
-    """
-    Calculates the radial distribution functions between for each unique pair of
-    molecule types as a function of their center of mass distance.
-
-    interval_indices: 2D array specifying the groups of the n_traj_split intervals to be averaged
-    max_mols: the maximum number of molecules per bead group for calculating the rdf, for efficiency purposes.
-    """
-    # TODO 5k default for max_mols was set after > 50k was giving problems. Should do further testing to see where the appropriate limit should be set.
-    if (
-        not universe
-        or not universe.trajectory
-        or universe.trajectory[0].dimensions is None
-    ):
-        return {}
-
-    n_frames = universe.trajectory.n_frames
-    if n_frames < n_traj_split:
-        n_traj_split = 1
-        frames_start = np.array([0])
-        frames_end = np.array([n_frames])
-        n_frames_split = np.array([n_frames])
-        interval_indices = [[0]]
-    else:
-        run_len = int(n_frames / n_traj_split)
-        frames_start = np.arange(n_traj_split) * run_len
-        frames_end = frames_start + run_len
-        frames_end[-1] = n_frames
-        n_frames_split = frames_end - frames_start
-        if np.sum(n_frames_split) != n_frames:
-            logging.error(
-                'Something went wrong with input parameters in calc_molecular_rdf().'
-                'Radial distribution functions will not be calculated.'
-            )
-            return {}
-        if not interval_indices:
-            interval_indices = [[i] for i in range(n_traj_split)]
-
-    bead_groups = _get_molecular_bead_groups(universe)
-    if not bead_groups:
-        return bead_groups
-    moltypes = [moltype for moltype in bead_groups.keys()]
-    del_list = [
-        i_moltype
-        for i_moltype, moltype in enumerate(moltypes)
-        if bead_groups[moltype]._nbeads > max_mols
-    ]
-    moltypes = np.delete(moltypes, del_list).tolist()
-
-    min_box_dimension = np.min(universe.trajectory[0].dimensions[:3])
-    max_rdf_dist = min_box_dimension / 2
-    n_bins = 200
-    n_smooth = 2
-
-    rdf_results: Dict[str, Any] = {}
-    rdf_results['n_smooth'] = n_smooth
-    rdf_results['n_prune'] = n_prune
-    rdf_results['type'] = 'molecular'
-    rdf_results['types'] = []
-    rdf_results['variables_name'] = []
-    rdf_results['bins'] = []
-    rdf_results['value'] = []
-    rdf_results['frame_start'] = []
-    rdf_results['frame_end'] = []
-    for i, moltype_i in enumerate(moltypes):
-        for j, moltype_j in enumerate(moltypes):
-            if j > i:
-                continue
-            elif (
-                i == j and bead_groups[moltype_i].positions.shape[0] == 1
-            ):  # skip if only 1 mol in group
-                continue
-
-            if i == j:
-                exclusion_block = (1, 1)  # remove self-distance
-            else:
-                exclusion_block = None
-            pair_type = f'{moltype_i}-{moltype_j}'
-            rdf_results_interval: Dict[str, Any] = {}
-            rdf_results_interval['types'] = []
-            rdf_results_interval['variables_name'] = []
-            rdf_results_interval['bins'] = []
-            rdf_results_interval['value'] = []
-            rdf_results_interval['frame_start'] = []
-            rdf_results_interval['frame_end'] = []
-            for i_interval in range(n_traj_split):
-                rdf_results_interval['types'].append(pair_type)
-                rdf_results_interval['variables_name'].append(['distance'])
-                rdf = MDA_RDF.InterRDF(
-                    bead_groups[moltype_i],
-                    bead_groups[moltype_j],
-                    range=(0, max_rdf_dist),
-                    exclusion_block=exclusion_block,
-                    nbins=n_bins,
-                ).run(frames_start[i_interval], frames_end[i_interval], n_prune)
-                rdf_results_interval['frame_start'].append(frames_start[i_interval])
-                rdf_results_interval['frame_end'].append(frames_end[i_interval])
-
-                rdf_results_interval['bins'].append(
-                    rdf.results.bins[int(n_smooth / 2) : -int(n_smooth / 2)]
-                    * ureg.angstrom
-                )
-                rdf_results_interval['value'].append(
-                    np.convolve(
-                        rdf.results.rdf, np.ones((n_smooth,)) / n_smooth, mode='same'
-                    )[int(n_smooth / 2) : -int(n_smooth / 2)]
-                )
-
-            flag_logging_error = False
-            for interval_group in interval_indices:
-                split_weights = n_frames_split[np.array(interval_group)] / np.sum(
-                    n_frames_split[np.array(interval_group)]
-                )
-                if abs(np.sum(split_weights) - 1.0) > 1e-6:
-                    flag_logging_error = True
-                    continue
-                rdf_values_avg = (
-                    split_weights[0] * rdf_results_interval['value'][interval_group[0]]
-                )
-                for i_interval, interval in enumerate(interval_group[1:]):
-                    if (
-                        rdf_results_interval['types'][interval]
-                        != rdf_results_interval['types'][interval - 1]
-                    ):
-                        flag_logging_error = True
-                        continue
-                    if (
-                        rdf_results_interval['variables_name'][interval]
-                        != rdf_results_interval['variables_name'][interval - 1]
-                    ):
-                        flag_logging_error = True
-                        continue
-                    if not (
-                        rdf_results_interval['bins'][interval]
-                        == rdf_results_interval['bins'][interval - 1]
-                    ).all():
-                        flag_logging_error = True
-                        continue
-                    rdf_values_avg += (
-                        split_weights[i_interval + 1]
-                        * rdf_results_interval['value'][interval]
-                    )
-                if flag_logging_error:
-                    logging.error(
-                        'Something went wrong in calc_molecular_rdf(). Some interval groups were skipped.'
-                    )
-                rdf_results['types'].append(
-                    rdf_results_interval['types'][interval_group[0]]
-                )
-                rdf_results['variables_name'].append(
-                    rdf_results_interval['variables_name'][interval_group[0]]
-                )
-                rdf_results['bins'].append(
-                    rdf_results_interval['bins'][interval_group[0]]
-                )
-                rdf_results['value'].append(rdf_values_avg)
-                rdf_results['frame_start'].append(
-                    int(rdf_results_interval['frame_start'][interval_group[0]])
-                )
-                rdf_results['frame_end'].append(
-                    int(rdf_results_interval['frame_end'][interval_group[-1]])
-                )
-
-    return rdf_results
-
-
-def calc_molecular_mean_squared_displacements(
-    universe: MDAnalysis.Universe, max_mols: int = 5000
-) -> Dict:
-    """
-    Calculates the mean squared displacement for the center of mass of each
-    molecule type.
-
-    max_mols: the maximum number of molecules per bead group for calculating the msd, for efficiency purposes.
-    50k was tested and is very fast and does not seem to have any memory issues.
-    """
-
-    def parse_jumps(
-        universe: MDAnalysis.Universe, selection: MDAnalysis.AtomGroup
-    ):  # TODO Add output declaration
-        """
-        See __get_nojump_positions().
-        """
-        __ = universe.trajectory[0]
-        prev = np.array(selection.positions)
-        box = universe.trajectory[0].dimensions[:3]
-        sparse_data = namedtuple('SparseData', ['data', 'row', 'col'])  # type: ignore[name-match]
-        jump_data = (
-            sparse_data(data=array('b'), row=array('l'), col=array('l')),
-            sparse_data(data=array('b'), row=array('l'), col=array('l')),
-            sparse_data(data=array('b'), row=array('l'), col=array('l')),
-        )
-
-        for i_frame, _ in enumerate(universe.trajectory[1:]):
-            curr = np.array(selection.positions)
-            delta = ((curr - prev) / box).round().astype(np.int8)
-            prev = np.array(curr)
-            for d in range(3):
-                (col,) = np.where(delta[:, d] != 0)
-                jump_data[d].col.extend(col)
-                jump_data[d].row.extend([i_frame] * len(col))
-                jump_data[d].data.extend(delta[col, d])
-
-        return jump_data
-
-    def generate_nojump_matrices(
-        universe: MDAnalysis.Universe, selection: MDAnalysis.AtomGroup
-    ):  # TODO Add output declaration
-        """
-        See __get_nojump_positions().
-        """
-        jump_data = parse_jumps(universe, selection)
-        n_frames = len(universe.trajectory)
-        n_atoms = selection.positions.shape[0]
-
-        nojump_matrices = tuple(
-            sparse.csr_matrix(
-                (np.array(m.data), (m.row, m.col)), shape=(n_frames, n_atoms)
-            )
-            for m in jump_data
-        )
-        return nojump_matrices
-
-    def get_nojump_positions(
-        universe: MDAnalysis.Universe, selection: MDAnalysis.AtomGroup
-    ) -> NDArray:
-        """
-        Unwraps the positions to create a continuous trajectory without jumps across periodic boundaries.
-        """
-        nojump_matrices = generate_nojump_matrices(universe, selection)
-        box = universe.trajectory[0].dimensions[:3]
-
-        nojump_positions = []
-        for i_frame, __ in enumerate(universe.trajectory):
-            delta = (
-                np.array(
-                    np.vstack([m[:i_frame, :].sum(axis=0) for m in nojump_matrices]).T
-                )
-                * box
-            )
-            nojump_positions.append(selection.positions - delta)
-
-        return np.array(nojump_positions)
-
-    def mean_squared_displacement(start: NDArray, current: NDArray):
-        """
-        Calculates mean square displacement between current and initial (start) coordinates.
-        """
-        vec = start - current
-        return (vec**2).sum(axis=1).mean()
-
-    if (
-        not universe
-        or not universe.trajectory
-        or universe.trajectory[0].dimensions is None
-    ):
-        return {}
-
-    n_frames = universe.trajectory.n_frames
-    if n_frames < 50:
-        warnings.warn(
-            'At least 50 frames required to calculate molecular'
-            ' mean squared displacements, skipping.',
-            UserWarning,
-        )
-        return {}
-
-    dt = getattr(universe.trajectory, 'dt')
-    if dt is None:
-        warnings.warn(
-            'Universe is missing time step, cannot calculate molecular'
-            ' mean squared displacements, skipping.',
-            UserWarning,
-        )
-        return {}
-    times = np.arange(n_frames) * dt
-
-    bead_groups = _get_molecular_bead_groups(universe)
-    if bead_groups is {}:
-        return bead_groups
-
-    moltypes = [moltype for moltype in bead_groups.keys()]
-    del_list = []
-    for i_moltype, moltype in enumerate(moltypes):
-        if bead_groups[moltype]._nbeads > max_mols:
-            if max_mols > 50000:
-                warnings.warn(
-                    'Calculating mean squared displacements for more than 50k molecules.'
-                    ' Expect long processing times!',
-                    UserWarning,
-                )
-            try:
-                # select max_mols nr. of rnd molecules from this moltype
-                moltype_indices = np.array(
-                    [atom._ix for atom in bead_groups[moltype]._atoms]
-                )
-                molnums = universe.atoms.molnums[moltype_indices]
-                molnum_types = np.unique(molnums)
-                molnum_types_rnd = np.sort(
-                    np.random.choice(molnum_types, size=max_mols)
-                )
-                atom_indices_rnd = np.concatenate(
-                    [np.where(molnums == molnum)[0] for molnum in molnum_types_rnd]
-                )
-                selection = ' '.join([str(i) for i in atom_indices_rnd])
-                selection = f'index {selection}'
-                ags_moltype_rnd = universe.select_atoms(selection)
-                bead_groups[moltype] = BeadGroup(ags_moltype_rnd, compound='fragments')
-                warnings.warn(
-                    'Maximum number of molecules for calculating the msd has been reached.'
-                    ' Will make a random selection for calculation.'
-                )
-            except Exception:
-                warnings.warn(
-                    'Error in selecting random molecules for large group when calculating msd. Skipping this molecule type.'
-                )
-                del_list.append(i_moltype)
-
-    for index in sorted(del_list, reverse=True):
-        del moltypes[index]
-
-    msd_results: Dict[str, Any] = {}
-    msd_results['type'] = 'molecular'
-    msd_results['direction'] = 'xyz'
-    msd_results['value'] = []
-    msd_results['times'] = []
-    msd_results['diffusion_constant'] = []
-    msd_results['error_diffusion_constant'] = []
-    for moltype in moltypes:
-        positions = get_nojump_positions(universe, bead_groups[moltype])
-        results = shifted_correlation_average(
-            mean_squared_displacement, times, positions
-        )
-        if results:
-            msd_results['value'].append(results[1])
-            msd_results['times'].append(results[0])
-            diffusion_constant, error = _calc_diffusion_constant(*results)
-            msd_results['diffusion_constant'].append(diffusion_constant)
-            msd_results['error_diffusion_constant'].append(error)
-
-    msd_results['types'] = moltypes
-    msd_results['times'] = np.array(msd_results['times']) * ureg.picosecond
-    msd_results['value'] = np.array(msd_results['value']) * ureg.angstrom**2
-    msd_results['diffusion_constant'] = (
-        np.array(msd_results['diffusion_constant']) * ureg.angstrom**2 / ureg.picosecond
-    )
-    msd_results['error_diffusion_constant'] = np.array(
-        msd_results['error_diffusion_constant']
-    )
-
-    return msd_results
-
-
-def calc_radius_of_gyration(
-    universe: MDAnalysis.Universe, molecule_atom_indices: NDArray
-) -> Dict:
-    """
-    Calculates the radius of gyration as a function of time for the atoms 'molecule_atom_indices'.
-    """
-    if (
-        not universe
-        or not universe.trajectory
-        or universe.trajectory[0].dimensions is None
-    ):
-        return {}
-    selection = ' '.join([str(i) for i in molecule_atom_indices])
-    selection = f'index {selection}'
-    molecule = universe.select_atoms(selection)
-    rg_results: Dict[str, Any] = {}
-    rg_results['type'] = 'molecular'
-    rg_results['times'] = []
-    rg_results['value'] = []
-    time_unit = hasattr(universe.trajectory.time, 'units')
-    for __ in universe.trajectory:
-        rg_results['times'].append(
-            universe.trajectory.time.magnitude
-            if time_unit
-            else universe.trajectory.time
-        )
-        rg_results['value'].append(molecule.radius_of_gyration())
-    rg_results['n_frames'] = len(rg_results['times'])
-    rg_results['times'] = (
-        np.array(rg_results['times']) * time_unit
-        if time_unit
-        else np.array(rg_results['times'])
-    )
-    rg_results['value'] = np.array(rg_results['value']) * ureg.angstrom
-
-    return rg_results
-
-
-def calc_molecular_radius_of_gyration(
-    universe: MDAnalysis.Universe, system_topology: MSection
-) -> List[Dict]:
-    """
-    Calculates the radius of gyration as a function of time for each polymer in the system.
-    """
-    if not system_topology:
-        return []
-
-    rg_results = []
-    for molgroup in system_topology:
-        for molecule in molgroup.get('atoms_group'):
-            sec_monomer_groups = molecule.get('atoms_group')
-            group_type = sec_monomer_groups[0].type if sec_monomer_groups else None
-            if group_type != 'monomer_group':
-                continue
-            rg_result = calc_radius_of_gyration(universe, molecule.atom_indices)
-            rg_result['label'] = molecule.label + '-index_' + str(molecule.index)
-            rg_result['atomsgroup_ref'] = molecule
-            rg_results.append(rg_result)
-
-    return rg_results
-
-
-def get_molecules_from_bond_list(
-    n_particles: int,
-    bond_list: List[tuple],
-    particle_types: List[str] = None,
-    particles_typeid: array = None,
-) -> List[Dict]:
-    """
-    Returns a list of dictionaries with molecule info from each instance in the list of bonds.
-    """
-    system_graph = networkx.empty_graph(n_particles)
-    system_graph.add_edges_from([(i[0], i[1]) for i in bond_list])
-    molecules = [
-        system_graph.subgraph(c).copy()
-        for c in networkx.connected_components(system_graph)
-    ]
-    molecule_info: List[Dict] = []
-    molecule_dict: Dict = {}
-    for mol in molecules:
-        molecule_dict = {}
-        molecule_dict['indices'] = np.array(mol.nodes())
-        molecule_dict['bonds'] = np.array(mol.edges())
-        molecule_dict['type'] = 'molecule'
-        molecule_dict['is_molecule'] = True
-        if particles_typeid is None and len(particle_types) == n_particles:
-            molecule_dict['names'] = [
-                particle_types[int(x)]
-                for x in sorted(np.array(molecule_dict['indices']))
-            ]
-        if particle_types is not None and particles_typeid is not None:
-            molecule_dict['names'] = [
-                particle_types[particles_typeid[int(x)]]
-                for x in sorted(np.array(molecule_dict['indices']))
-            ]
-        molecule_info.append(molecule_dict)
-    return molecule_info
-
-
-def is_same_molecule(mol_1: dict, mol_2: dict) -> bool:
-    """
-    Checks whether the 2 input molecule dictionary (see "get_molecules_from_bond_list()" above)
-    represent the same molecule type, i.e., same particle types and corresponding bond connections.
-    """
-
-    def get_bond_list_dict(mol):
-        mol_shift = np.min(mol['indices'])
-        mol_bonds_shift = mol['bonds'] - mol_shift
-        bond_list = [
-            sorted((mol['names'][i], mol['names'][j])) for i, j in mol_bonds_shift
-        ]
-        bond_list_names, bond_list_counts = np.unique(
-            bond_list, axis=0, return_counts=True
-        )
-
-        return {
-            bond[0] + '-' + bond[1]: bond_list_counts[i_bond]
-            for i_bond, bond in enumerate(bond_list_names)
-        }
-
-    if sorted(mol_1['names']) != sorted(mol_2['names']):
-        return False
-
-    bond_list_dict_1 = get_bond_list_dict(mol_1)
-    bond_list_dict_2 = get_bond_list_dict(mol_2)
-
-    if bond_list_dict_1 == bond_list_dict_2:
-        return True
-
-    return False
-
-
-def get_composition(children_names: List[str]) -> str:
-    """
-    Generates a generalized "chemical formula" based on the provided list `children_names`,
-    with the format X(m)Y(n) for children_names X and Y of quantities m and n, respectively.
-    """
-    children_count_tup = np.unique(children_names, return_counts=True)
-    formula = ''.join([f'{name}({count})' for name, count in zip(*children_count_tup)])
-    return formula
-
-
-def get_bond_list_from_model_contributions(
-    sec_run: MSection, method_index: int = -1, model_index: int = -1
-) -> List[tuple]:
-    """
-    Generates bond list of tuples using the list of bonded force field interactions stored under run[].method[].force_field.model[].
-
-    bond_list: List[tuple]
-    """
-    contributions = []
-    if sec_run.m_xpath(
-        f'method[{method_index}].force_field.model[{model_index}].contributions'
-    ):
-        contributions = (
-            sec_run.method[method_index].force_field.model[model_index].contributions
-        )
-    bond_list = []
-    for contribution in contributions:
-        if contribution.type != 'bond':
-            continue
-
-        atom_indices = contribution.atom_indices
-        if (
-            contribution.n_interactions
-        ):  # all bonds have been grouped into one contribution
-            bond_list = [tuple(indices) for indices in atom_indices]
-        else:
-            bond_list.append(tuple(contribution.atom_indices))
-
-    return bond_list
diff --git a/nomad/datamodel/metainfo/simulation/workflow.py b/nomad/datamodel/metainfo/simulation/workflow.py
index 8d9f2e172623c956f7d32e7d72bf59788cc038d5..a53db33ecc43112b05ad8fe2fbcce57ee243b732 100644
--- a/nomad/datamodel/metainfo/simulation/workflow.py
+++ b/nomad/datamodel/metainfo/simulation/workflow.py
@@ -66,12 +66,6 @@ from nomad.datamodel.metainfo.simulation.calculation import (
     RadiusOfGyrationValues as RadiusOfGyrationValuesCalculation,
     EnergyEntry,
 )
-from nomad.atomutils import archive_to_universe
-from nomad.atomutils import (
-    calc_molecular_rdf,
-    calc_molecular_mean_squared_displacements,
-    calc_molecular_radius_of_gyration,
-)
 
 
 # TODO remove this after reprocessing with the new schema defined in
@@ -2208,7 +2202,18 @@ class MolecularDynamicsResults(ThermodynamicsResults):
     def normalize(self, archive, logger):
         super().normalize(archive, logger)
 
-        universe = archive_to_universe(archive)
+        try:
+            from simulationworkflowschema.molecular_dynamics import archive_to_universe
+            from simulationworkflowschema.molecular_dynamics import (
+                calc_molecular_rdf,
+                calc_molecular_mean_squared_displacements,
+                calc_molecular_radius_of_gyration,
+            )
+
+            universe = archive_to_universe(archive)
+        except Exception:
+            universe = None
+
         if universe is None:
             return
 
diff --git a/nomad/normalizing/common.py b/nomad/normalizing/common.py
index 60c2e62696919ccd65b01386b4427dc951a6f45c..7f85a845aeab5a1937e80d9af0fddb55e52ccda7 100644
--- a/nomad/normalizing/common.py
+++ b/nomad/normalizing/common.py
@@ -20,7 +20,6 @@ from math import isnan
 from ase import Atoms
 from typing import List, Set, Any, Optional, Dict, Union
 from nptyping import NDArray
-import MDAnalysis as mda
 from matid import SymmetryAnalyzer  # pylint: disable=import-error
 from matid.symmetry.wyckoffset import WyckoffSet as WyckoffSetMatID  # pylint: disable=import-error
 import matid.geometry  # pylint: disable=import-error
@@ -273,46 +272,6 @@ def ase_atoms_from_structure(system: Structure) -> Atoms:
     )
 
 
-def mda_universe_from_nomad_atoms(system: Atoms, logger=None) -> mda.Universe:
-    """Returns an instance of mda.Universe from a NOMAD Atoms-section.
-
-    Args:
-        system: The atoms to transform
-
-    Returns:
-        A new mda.Universe created from the given data.
-    """
-    n_atoms = len(system.positions)
-    n_residues = 1
-    atom_resindex = [0] * n_atoms
-    residue_segindex = [0]
-
-    universe = mda.Universe.empty(
-        n_atoms,
-        n_residues=n_residues,
-        atom_resindex=atom_resindex,
-        residue_segindex=residue_segindex,
-        trajectory=True,
-    )
-
-    # Add positions
-    universe.atoms.positions = system.positions.to(ureg.angstrom).magnitude
-
-    # Add atom attributes
-    atom_names = system.labels
-    universe.add_TopologyAttr('name', atom_names)
-    universe.add_TopologyAttr('type', atom_names)
-    universe.add_TopologyAttr('element', atom_names)
-
-    # Add the box dimensions
-    if system.lattice_vectors is not None:
-        universe.atoms.dimensions = atomutils.cell_to_cellpar(
-            system.lattice_vectors.to(ureg.angstrom).magnitude, degrees=True
-        )
-
-    return universe
-
-
 def structures_2d(original_atoms, logger=None):
     conv_atoms = None
     prim_atoms = None
diff --git a/pyproject.toml b/pyproject.toml
index 28fe31cf809d4ab2801b977022ed6f607dd155e6..e371a58c268f02b9efc1d20ed7eb35b0ba29c085 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,8 +29,6 @@ dependencies = [
     'lxml>=5.2',
     'lxml-html-clean>=0.1.0',
     'matid>=2.1.2',
-    'mdanalysis==2.7.0',
-    'networkx>=2.6.3',
     'nptyping~=1.4.4',
     'numpy>=1.22.4,<2.0.0',
     'openpyxl>=3.0.0',
diff --git a/requirements-dev.txt b/requirements-dev.txt
index c5effa20ce226adcc984cd78be40d51d8304015b..30a9c097c109beaefd5b7cc22dad291f6c72505a 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -22,12 +22,12 @@ basicauth==0.4.1          # via -r requirements.txt, nomad-lab (pyproject.toml)
 beautifulsoup4==4.12.3    # via -r requirements.txt, nomad-lab (pyproject.toml)
 billiard==4.2.0           # via celery, -r requirements.txt
 bitarray==2.9.2           # via -r requirements.txt, nomad-lab (pyproject.toml)
-build==1.2.1              # via nomad-lab (pyproject.toml)
+build==1.2.2              # via nomad-lab (pyproject.toml)
 cachetools==5.5.0         # via -r requirements.txt, nomad-lab (pyproject.toml)
 celery==5.4.0             # via -r requirements.txt, nomad-lab (pyproject.toml)
 certifi==2024.8.30        # via elasticsearch, httpcore, httpx, netcdf4, requests, -r requirements.txt
 certipy==0.1.3            # via jupyterhub, -r requirements.txt
-cffi==1.17.0 ; platform_python_implementation != 'PyPy'  # via cryptography, -r requirements.txt
+cffi==1.17.1 ; platform_python_implementation != 'PyPy'  # via cryptography, -r requirements.txt
 cftime==1.6.4             # via netcdf4, -r requirements.txt
 charset-normalizer==3.3.2  # via requests, -r requirements.txt
 click==8.1.7              # via celery, click-didyoumean, click-plugins, click-repl, mkdocs, mkdocs-click, uvicorn, -r requirements.txt, nomad-lab (pyproject.toml)
@@ -57,7 +57,6 @@ et-xmlfile==1.1.0         # via openpyxl, -r requirements.txt
 execnet==2.1.1            # via pytest-xdist
 executing==2.1.0          # via devtools
 fastapi==0.99.1           # via h5grove, -r requirements.txt, nomad-lab (pyproject.toml)
-fasteners==0.19           # via mdanalysis, -r requirements.txt
 filelock==3.3.1           # via -r requirements.txt, nomad-lab (pyproject.toml)
 fonttools==4.53.1         # via matplotlib, -r requirements.txt
 fqdn==1.5.1               # via jsonschema, -r requirements.txt
@@ -65,7 +64,6 @@ ghp-import==2.1.0         # via mkdocs
 gitdb==4.0.11             # via gitpython, -r requirements.txt
 gitpython==3.1.43         # via mkdocs-git-revision-date-localized-plugin, -r requirements.txt, nomad-lab (pyproject.toml)
 greenlet==3.0.3 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')  # via sqlalchemy, -r requirements.txt
-griddataformats==1.0.2    # via mdanalysis, -r requirements.txt
 gunicorn==21.2.0          # via -r requirements.txt, nomad-lab (pyproject.toml)
 h11==0.14.0               # via httpcore, uvicorn, -r requirements.txt
 h5grove==1.3.0            # via -r requirements.txt, nomad-lab (pyproject.toml)
@@ -89,7 +87,7 @@ jaraco-functools==4.0.2   # via keyring
 jeepney==0.8.0 ; sys_platform == 'linux'  # via keyring, secretstorage
 jinja2==3.1.4             # via jupyterhub, mkdocs, mkdocs-macros-plugin, mkdocs-material, sphinx, -r requirements.txt
 jmespath==1.0.1           # via -r requirements.txt, nomad-lab (pyproject.toml)
-joblib==1.4.2             # via mdanalysis, pymatgen, scikit-learn, -r requirements.txt
+joblib==1.4.2             # via pymatgen, scikit-learn, -r requirements.txt
 jsonpointer==3.0.0        # via jsonschema, -r requirements.txt
 jsonschema==4.17.3        # via jupyter-telemetry, oauthenticator, -r requirements.txt, nomad-lab (pyproject.toml)
 jupyter-telemetry==0.1.0  # via jupyterhub, -r requirements.txt
@@ -108,43 +106,40 @@ mako==1.3.5               # via alembic, -r requirements.txt
 markdown==3.7             # via mkdocs, mkdocs-click, mkdocs-material, pymdown-extensions
 markupsafe==2.1.5         # via jinja2, mako, mkdocs, -r requirements.txt, nomad-lab (pyproject.toml)
 matid==2.1.2              # via -r requirements.txt, nomad-lab (pyproject.toml)
-matplotlib==3.9.2         # via ase, mdanalysis, pymatgen, -r requirements.txt
-mda-xdrlib==0.2.0         # via mdanalysis, pyedr, -r requirements.txt
-mdanalysis==2.7.0         # via -r requirements.txt, nomad-lab (pyproject.toml)
+matplotlib==3.9.2         # via ase, pymatgen, -r requirements.txt
+mda-xdrlib==0.2.0         # via pyedr, -r requirements.txt
 mergedeep==1.3.4          # via mkdocs, mkdocs-get-deps
 mistune==3.0.2            # via m2r, -r requirements.txt
 mkdocs==1.6.1             # via mkdocs-git-revision-date-localized-plugin, mkdocs-macros-plugin, mkdocs-material, mkdocs-redirects, nomad-lab (pyproject.toml)
 mkdocs-click==0.8.1       # via nomad-lab (pyproject.toml)
 mkdocs-get-deps==0.2.0    # via mkdocs
-mkdocs-git-revision-date-localized-plugin==1.2.7  # via nomad-lab (pyproject.toml)
+mkdocs-git-revision-date-localized-plugin==1.2.8  # via nomad-lab (pyproject.toml)
 mkdocs-glightbox==0.4.0   # via nomad-lab (pyproject.toml)
 mkdocs-macros-plugin==1.0.5  # via nomad-lab (pyproject.toml)
 mkdocs-material==9.5.34   # via nomad-lab (pyproject.toml)
 mkdocs-material-extensions==1.3.1  # via mkdocs-material, nomad-lab (pyproject.toml)
 mkdocs-redirects==1.2.1   # via nomad-lab (pyproject.toml)
-mmtf-python==1.1.3        # via mdanalysis, -r requirements.txt
 mongoengine==0.29.0       # via -r requirements.txt, nomad-lab (pyproject.toml)
 mongomock==4.1.2          # via optimade, -r requirements.txt
 monty==2024.7.30          # via pymatgen, -r requirements.txt
-more-itertools==10.4.0    # via jaraco-classes, jaraco-functools
+more-itertools==10.5.0    # via jaraco-classes, jaraco-functools
 mpmath==1.3.0             # via sympy, -r requirements.txt
-mrcfile==1.5.3            # via griddataformats, -r requirements.txt
-msgpack==1.0.8            # via mmtf-python, -r requirements.txt, nomad-lab (pyproject.toml)
+msgpack==1.1.0            # via -r requirements.txt, nomad-lab (pyproject.toml)
 mypy==1.0.1               # via nomad-lab (pyproject.toml)
 mypy-extensions==1.0.0    # via mypy
 names==0.3.0              # via nomad-lab (pyproject.toml)
 netcdf4==1.6.5            # via -r requirements.txt, nomad-lab (pyproject.toml)
-networkx==3.3             # via matid, pymatgen, -r requirements.txt, nomad-lab (pyproject.toml)
+networkx==3.3             # via matid, pymatgen, -r requirements.txt
 nh3==0.2.18               # via readme-renderer
 nomad-openbis==1.0.0      # via -r requirements.txt, nomad-lab (pyproject.toml)
 nptyping==1.4.4           # via -r requirements.txt, nomad-lab (pyproject.toml)
-numpy==1.26.4             # via ase, cftime, contourpy, griddataformats, h5grove, h5py, matid, matplotlib, mdanalysis, mrcfile, netcdf4, nptyping, pandas, pyedr, pymatgen, rdkit, scikit-learn, scipy, spglib, tifffile, xarray, -r requirements.txt, nomad-lab (pyproject.toml)
+numpy==1.26.4             # via ase, cftime, contourpy, h5grove, h5py, matid, matplotlib, netcdf4, nptyping, pandas, pyedr, pymatgen, rdkit, scikit-learn, scipy, spglib, tifffile, xarray, -r requirements.txt, nomad-lab (pyproject.toml)
 oauthenticator==15.1.0    # via -r requirements.txt, nomad-lab (pyproject.toml)
 oauthlib==3.2.2           # via jupyterhub, -r requirements.txt
 openpyxl==3.1.5           # via -r requirements.txt, nomad-lab (pyproject.toml)
 optimade==0.22.1          # via -r requirements.txt, nomad-lab (pyproject.toml)
 orjson==3.10.7            # via h5grove, -r requirements.txt, nomad-lab (pyproject.toml)
-packaging==24.1           # via build, deprecation, gunicorn, jupyterhub, matplotlib, mdanalysis, mkdocs, mongomock, pint, plotly, pytest, sphinx, xarray, -r requirements.txt
+packaging==24.1           # via build, deprecation, gunicorn, jupyterhub, matplotlib, mkdocs, mongomock, pint, plotly, pytest, sphinx, xarray, -r requirements.txt
 paginate==0.5.7           # via mkdocs-material
 palettable==3.3.3         # via pymatgen, -r requirements.txt
 pamela==1.2.0 ; sys_platform != 'win32'  # via jupyterhub, -r requirements.txt
@@ -155,7 +150,7 @@ pathspec==0.12.1          # via mkdocs
 pillow==10.4.0            # via matplotlib, rdkit, -r requirements.txt
 pint==0.17                # via -r requirements.txt, nomad-lab (pyproject.toml)
 pkginfo==1.11.1           # via twine
-platformdirs==4.2.2       # via mkdocs-get-deps
+platformdirs==4.3.2       # via mkdocs-get-deps
 plotly==5.24.0            # via pymatgen, -r requirements.txt
 pluggy==1.5.0             # via pytest
 prometheus-client==0.20.0  # via jupyterhub, -r requirements.txt
@@ -211,10 +206,10 @@ rfc3987==1.3.8            # via jsonschema, -r requirements.txt
 rope==0.21.0              # via nomad-lab (pyproject.toml)
 ruamel-yaml==0.18.6       # via jupyter-telemetry, oauthenticator, pymatgen, -r requirements.txt, nomad-lab (pyproject.toml)
 ruamel-yaml-clib==0.2.8 ; python_full_version < '3.13' and platform_python_implementation == 'CPython'  # via ruamel-yaml, -r requirements.txt
-ruff==0.6.3               # via nomad-lab (pyproject.toml)
+ruff==0.6.4               # via nomad-lab (pyproject.toml)
 runstats==2.0.0           # via -r requirements.txt, nomad-lab (pyproject.toml)
 scikit-learn==1.5.1       # via matid, -r requirements.txt, nomad-lab (pyproject.toml)
-scipy==1.14.1             # via ase, griddataformats, mdanalysis, pymatgen, scikit-learn, -r requirements.txt, nomad-lab (pyproject.toml)
+scipy==1.14.1             # via ase, pymatgen, scikit-learn, -r requirements.txt, nomad-lab (pyproject.toml)
 secretstorage==3.3.3 ; sys_platform == 'linux'  # via keyring
 sentinels==1.0.0          # via mongomock, -r requirements.txt
 six==1.16.0               # via asttokens, basicauth, elasticsearch-dsl, html5lib, isodate, pybtex, python-dateutil, rdflib, rfc3339-validator, validators, -r requirements.txt
@@ -238,16 +233,16 @@ tabulate==0.8.9           # via nomad-openbis, pymatgen, -r requirements.txt, no
 tenacity==9.0.0           # via plotly, -r requirements.txt
 termcolor==2.4.0          # via mkdocs-macros-plugin
 texttable==1.7.0          # via nomad-openbis, -r requirements.txt
-threadpoolctl==3.5.0      # via mdanalysis, scikit-learn, -r requirements.txt
+threadpoolctl==3.5.0      # via scikit-learn, -r requirements.txt
 tifffile==2024.8.30       # via h5grove, -r requirements.txt
-tomli==2.0.1 ; python_full_version == '3.11'  # via coverage
+tomli==2.0.1 ; python_full_version <= '3.11'  # via coverage
 toposort==1.10            # via -r requirements.txt, nomad-lab (pyproject.toml)
 tornado==6.4.1            # via jupyterhub, -r requirements.txt
-tqdm==4.66.5              # via mdanalysis, pyedr, pymatgen, twine, -r requirements.txt
+tqdm==4.66.5              # via pyedr, pymatgen, twine, -r requirements.txt
 traitlets==5.14.3         # via jupyter-telemetry, jupyterhub, -r requirements.txt
 twine==3.4.2              # via nomad-lab (pyproject.toml)
 typed-ast==1.5.5          # via nomad-lab (pyproject.toml)
-types-python-dateutil==2.9.0.20240821  # via arrow, -r requirements.txt
+types-python-dateutil==2.9.0.20240906  # via arrow, -r requirements.txt
 typing-extensions==4.12.2  # via alembic, fastapi, jwcrypto, mypy, pydantic, sqlalchemy, -r requirements.txt
 typish==1.9.3             # via nptyping, -r requirements.txt
 tzdata==2024.1            # via celery, pandas, -r requirements.txt
@@ -255,7 +250,7 @@ uncertainties==3.2.2      # via pymatgen, -r requirements.txt
 unidecode==1.3.2          # via -r requirements.txt, nomad-lab (pyproject.toml)
 uri-template==1.3.0       # via jsonschema, -r requirements.txt
 urllib3==1.26.20          # via docker, elasticsearch, nomad-openbis, requests, -r requirements.txt
-uv==0.4.4                 # via nomad-lab (pyproject.toml)
+uv==0.4.8                 # via nomad-lab (pyproject.toml)
 uvicorn==0.30.6           # via h5grove, -r requirements.txt, nomad-lab (pyproject.toml)
 uvloop==0.20.0 ; platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'  # via uvicorn, -r requirements.txt
 validators==0.18.2        # via -r requirements.txt, nomad-lab (pyproject.toml)
diff --git a/requirements-plugins.txt b/requirements-plugins.txt
index 3b54d480d53d1df6c21e14bee3a8b4a0a0da5918..aaed4c6dabc56b77ae2c657898e95fdbe47455df 100644
--- a/requirements-plugins.txt
+++ b/requirements-plugins.txt
@@ -14,7 +14,7 @@ asteval==1.0.2            # via lmfit
 asttokens==2.4.1          # via stack-data, -c requirements-dev.txt
 async-lru==2.0.4          # via jupyterlab
 async-property==0.2.2     # via python-keycloak, -c requirements-dev.txt
-atomisticparsers @ git+https://github.com/nomad-coe/atomistic-parsers.git@cedaf4191abe315001f6b55a9690dcd21116e9b7  # via -r default_plugins.txt
+atomisticparsers @ git+https://github.com/nomad-coe/atomistic-parsers.git@eebca28ebe83b3c945e922910b18a16ec36e8122  # via -r default_plugins.txt
 attrs==24.2.0             # via jsonschema, -c requirements-dev.txt
 babel==2.16.0             # via jupyterlab-server, -c requirements-dev.txt
 beautifulsoup4==4.12.3    # via nbconvert, -c requirements-dev.txt
@@ -24,7 +24,8 @@ blinker==1.8.2            # via flask
 blosc2==2.7.1             # via tables
 cachetools==5.5.0         # via nomad-lab, -c requirements-dev.txt
 certifi==2024.8.30        # via elasticsearch, httpcore, httpx, netcdf4, requests, -c requirements-dev.txt
-cffi==1.17.0              # via argon2-cffi-bindings, cryptography, pyzmq, -c requirements-dev.txt
+cffi==1.17.0 ; platform_python_implementation == 'PyPy'  # via argon2-cffi-bindings, pyzmq, -c requirements-dev.txt
+cffi==1.17.1 ; platform_python_implementation != 'PyPy'  # via argon2-cffi-bindings, cryptography, pyzmq, -c requirements-dev.txt
 cftime==1.6.4             # via netcdf4, -c requirements-dev.txt
 charset-normalizer==3.3.2  # via requests, -c requirements-dev.txt
 click==8.1.7              # via asr, click-default-group, dask, flask, nomad-lab, pynxtools, -c requirements-dev.txt
@@ -48,13 +49,13 @@ docstring-parser==0.16    # via nomad-lab, -c requirements-dev.txt
 eelsdbconverter @ git+https://github.com/nomad-coe/nomad-parser-eelsdb.git@788eb03dc71ef9b164e2a5ccb9c0209b546f5c38  # via -r default_plugins.txt
 elasticsearch==7.17.1     # via elasticsearch-dsl, -c requirements-dev.txt
 elasticsearch-dsl==7.4.0  # via nomad-lab, -c requirements-dev.txt
-electronicparsers @ git+https://github.com/nomad-coe/electronic-parsers.git@637a5d67fe84f8e4f1b18fdb2dd2df7ba199d1a9  # via -r default_plugins.txt
+electronicparsers @ git+https://github.com/nomad-coe/electronic-parsers.git@8d4ba60e1893afad1ad43df1bf0a0911f17bffc0  # via -r default_plugins.txt
 entrypoints==0.4          # via ipyparallel
 et-xmlfile==1.1.0         # via openpyxl, -c requirements-dev.txt
 executing==2.1.0          # via stack-data, -c requirements-dev.txt
 fabio==2024.4.0           # via pyfai, silx
 fairmat-readers-xrd==0.0.6  # via pynxtools-xrd
-fasteners==0.19           # via mdanalysis, zarr, -c requirements-dev.txt
+fasteners==0.19           # via mdanalysis, zarr
 fastjsonschema==2.20.0    # via nbformat
 findiff==0.10.0           # via pynxtools-stm
 flask==3.0.3              # via asr
@@ -63,7 +64,7 @@ fonttools==4.53.1         # via matplotlib, -c requirements-dev.txt
 fqdn==1.5.1               # via jsonschema, -c requirements-dev.txt
 fsspec==2024.6.1          # via dask, hyperspy
 greenlet==3.0.3 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')  # via sqlalchemy, -c requirements-dev.txt
-griddataformats==1.0.2    # via mdanalysis, -c requirements-dev.txt
+griddataformats==1.0.2    # via mdanalysis
 h11==0.14.0               # via httpcore, -c requirements-dev.txt
 h5grove==1.3.0            # via jupyterlab-h5web, -c requirements-dev.txt
 h5py==3.11.0              # via electronicparsers, fabio, h5grove, hdf5plugin, hyperspy, ifes-apt-tc-data-modeling, jupyterlab-h5web, kikuchipy, nionswift, nomad-lab, orix, phonopy, pyfai, pynxtools, pynxtools-mpes, pynxtools-xps, pyxem, silx, workflowparsers, -c requirements-dev.txt
@@ -120,14 +121,14 @@ matplotlib==3.9.2         # via ase, asr, diffsims, hyperspy, kikuchipy, matplot
 matplotlib-inline==0.1.7  # via ipykernel, ipython
 matplotlib-scalebar==0.8.1  # via orix
 mda-xdrlib==0.2.0         # via mdanalysis, pyedr, -c requirements-dev.txt
-mdanalysis==2.7.0         # via atomisticparsers, nomad-lab, -c requirements-dev.txt
+mdanalysis==2.7.0         # via atomisticparsers, nomad-lab, nomad-schema-plugin-simulation-workflow
 mergedeep==1.3.4          # via pynxtools, -c requirements-dev.txt
 mistune==3.0.2            # via nbconvert, -c requirements-dev.txt
-mmtf-python==1.1.3        # via mdanalysis, -c requirements-dev.txt
+mmtf-python==1.1.3        # via mdanalysis
 monty==2024.7.30          # via pymatgen, -c requirements-dev.txt
 mpmath==1.3.0             # via sympy, -c requirements-dev.txt
-mrcfile==1.5.3            # via griddataformats, -c requirements-dev.txt
-msgpack==1.0.8            # via blosc2, mmtf-python, -c requirements-dev.txt
+mrcfile==1.5.3            # via griddataformats
+msgpack==1.1.0            # via blosc2, mmtf-python, -c requirements-dev.txt
 natsort==8.4.0            # via hyperspy
 nbclient==0.10.0          # via nbconvert
 nbconvert==7.16.4         # via jupyter, jupyter-server
@@ -135,7 +136,7 @@ nbformat==5.10.4          # via jupyter-server, nbclient, nbconvert
 ndindex==1.8              # via blosc2
 nest-asyncio==1.6.0       # via ipykernel
 netcdf4==1.6.5            # via electronicparsers, -c requirements-dev.txt
-networkx==3.3             # via matid, nomad-lab, pymatgen, radioactivedecay, scikit-image, -c requirements-dev.txt
+networkx==3.3             # via matid, nomad-lab, nomad-schema-plugin-simulation-workflow, pymatgen, radioactivedecay, scikit-image, -c requirements-dev.txt
 niondata==15.6.3          # via nionswift, nionswift-io
 nionswift==16.11.0        # via pynxtools-em
 nionswift-io==15.2.1      # via nionswift
@@ -151,7 +152,7 @@ nomad-normalizer-plugin-spectra @ git+https://github.com/nomad-coe/nomad-normali
 nomad-normalizer-plugin-system @ git+https://github.com/nomad-coe/nomad-normalizer-plugin-system.git@01523ddbc85676a40af40f4747f5f13676f34c0f  # via -r default_plugins.txt
 nomad-porous-materials @ git+https://github.com/FAIRmat-NFDI/nomad-porous-materials.git@522f4a3208077f534f1c5e886527ee2104283d0b  # via -r default_plugins.txt
 nomad-schema-plugin-run @ git+https://github.com/nomad-coe/nomad-schema-plugin-run.git@92d120dc4c5f7f4bcd94990ed009f8ac0019acec  # via atomisticparsers, databaseparsers, electronicparsers, nomad-schema-plugin-simulation-workflow, simulationparsers, workflowparsers, -r default_plugins.txt
-nomad-schema-plugin-simulation-workflow @ git+https://github.com/nomad-coe/nomad-schema-plugin-simulation-workflow.git@c9bd20c0447aaf22a6477cb39bdf03dea4c597f8  # via atomisticparsers, databaseparsers, electronicparsers, nomad-normalizer-plugin-simulation-workflow, workflowparsers, -r default_plugins.txt
+nomad-schema-plugin-simulation-workflow @ git+https://github.com/nomad-coe/nomad-schema-plugin-simulation-workflow.git@5631278b983072a6e7cb6fea40c4b3ca49b7b804  # via atomisticparsers, databaseparsers, electronicparsers, nomad-normalizer-plugin-simulation-workflow, workflowparsers, -r default_plugins.txt
 nomad-simulations==0.0.1  # via -r default_plugins.txt
 notebook==7.1.3           # via jupyter
 notebook-shim==0.2.4      # via jupyterlab, notebook
@@ -177,7 +178,7 @@ pexpect==4.9.0 ; sys_platform != 'emscripten' and sys_platform != 'win32'  # via
 phonopy==2.11.0           # via asr, workflowparsers
 pillow==10.4.0            # via fabio, imageio, matplotlib, nionswift, rdkit, scikit-image, -c requirements-dev.txt
 pint==0.17                # via fairmat-readers-xrd, hyperspy, nomad-lab, pynxtools-xps, pynxtools-xrd, rosettasciio, -c requirements-dev.txt
-platformdirs==4.2.2       # via jupyter-core, pooch, xraydb, -c requirements-dev.txt
+platformdirs==4.3.2       # via jupyter-core, pooch, xraydb, -c requirements-dev.txt
 plotly==5.24.0            # via asr, pymatgen, -c requirements-dev.txt
 pooch==1.8.2              # via kikuchipy, orix
 prettytable==3.11.0       # via hyperspy
@@ -233,7 +234,7 @@ ruamel-yaml==0.18.6       # via pymatgen, -c requirements-dev.txt
 ruamel-yaml-clib==0.2.8 ; python_full_version < '3.13' and platform_python_implementation == 'CPython'  # via ruamel-yaml, -c requirements-dev.txt
 scikit-image==0.22.0      # via hyperspy, kikuchipy, pyxem
 scikit-learn==1.5.1       # via kikuchipy, matid, nomad-lab, pyxem, -c requirements-dev.txt
-scipy==1.14.1             # via ase, atomisticparsers, diffsims, findiff, griddataformats, hyperspy, kikuchipy, lmfit, mdanalysis, niondata, nionswift, nomad-lab, orix, pyfai, pymatgen, pyxem, radioactivedecay, scikit-image, scikit-learn, sparse, xraydb, -c requirements-dev.txt
+scipy==1.14.1             # via ase, atomisticparsers, diffsims, findiff, griddataformats, hyperspy, kikuchipy, lmfit, mdanalysis, niondata, nionswift, nomad-lab, nomad-schema-plugin-simulation-workflow, orix, pyfai, pymatgen, pyxem, radioactivedecay, scikit-image, scikit-learn, sparse, xraydb, -c requirements-dev.txt
 send2trash==1.8.3         # via jupyter-server
 setuptools==73.0.1        # via pynxtools-xps, radioactivedecay
 silx==2.1.1               # via pyfai
@@ -260,7 +261,7 @@ tqdm==4.66.5              # via diffsims, hyperspy, ipyparallel, kikuchipy, mdan
 traitlets==5.14.3         # via comm, ipykernel, ipyparallel, ipython, ipywidgets, jupyter-client, jupyter-console, jupyter-core, jupyter-events, jupyter-server, jupyterlab, matplotlib-inline, nbclient, nbconvert, nbformat, qtconsole, -c requirements-dev.txt
 traits==6.4.3             # via hyperspy, pyxem
 transforms3d==0.4.2       # via diffsims, pyxem
-types-python-dateutil==2.9.0.20240821  # via arrow, -c requirements-dev.txt
+types-python-dateutil==2.9.0.20240906  # via arrow, -c requirements-dev.txt
 typing-extensions==4.12.2  # via ipython, jwcrypto, pydantic, sqlalchemy, tables, -c requirements-dev.txt
 typish==1.9.3             # via nptyping, -c requirements-dev.txt
 tzdata==2024.1            # via pandas, tzlocal, -c requirements-dev.txt
diff --git a/requirements.txt b/requirements.txt
index 49a969c73627e8b2777802781340c7a3a9e1127b..54ecbb99060d235c7569b45d1fcebe9dc9a6467f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -21,7 +21,7 @@ cachetools==5.5.0         # via nomad-lab (pyproject.toml)
 celery==5.4.0             # via nomad-lab (pyproject.toml)
 certifi==2024.8.30        # via elasticsearch, httpcore, httpx, netcdf4, requests
 certipy==0.1.3            # via jupyterhub
-cffi==1.17.0 ; platform_python_implementation != 'PyPy'  # via cryptography
+cffi==1.17.1 ; platform_python_implementation != 'PyPy'  # via cryptography
 cftime==1.6.4             # via netcdf4
 charset-normalizer==3.3.2  # via requests
 click==8.1.7              # via celery, click-didyoumean, click-plugins, click-repl, uvicorn, nomad-lab (pyproject.toml)
@@ -46,14 +46,12 @@ email-validator==1.3.1    # via optimade
 escapism==1.0.1           # via dockerspawner
 et-xmlfile==1.1.0         # via openpyxl
 fastapi==0.99.1           # via h5grove, nomad-lab (pyproject.toml)
-fasteners==0.19           # via mdanalysis
 filelock==3.3.1           # via nomad-lab (pyproject.toml)
 fonttools==4.53.1         # via matplotlib
 fqdn==1.5.1               # via jsonschema
 gitdb==4.0.11             # via gitpython
 gitpython==3.1.43         # via nomad-lab (pyproject.toml)
 greenlet==3.0.3 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')  # via sqlalchemy
-griddataformats==1.0.2    # via mdanalysis
 gunicorn==21.2.0          # via nomad-lab (pyproject.toml)
 h11==0.14.0               # via httpcore, uvicorn
 h5grove==1.3.0            # via nomad-lab (pyproject.toml)
@@ -72,7 +70,7 @@ isoduration==20.11.0      # via jsonschema
 itsdangerous==2.2.0       # via nomad-lab (pyproject.toml)
 jinja2==3.1.4             # via jupyterhub, sphinx
 jmespath==1.0.1           # via nomad-lab (pyproject.toml)
-joblib==1.4.2             # via mdanalysis, pymatgen, scikit-learn
+joblib==1.4.2             # via pymatgen, scikit-learn
 jsonpointer==3.0.0        # via jsonschema
 jsonschema==4.17.3        # via jupyter-telemetry, oauthenticator, nomad-lab (pyproject.toml)
 jupyter-telemetry==0.1.0  # via jupyterhub
@@ -89,28 +87,25 @@ m2r==0.2.1                # via nomad-lab (pyproject.toml)
 mako==1.3.5               # via alembic
 markupsafe==2.1.5         # via jinja2, mako
 matid==2.1.2              # via nomad-lab (pyproject.toml)
-matplotlib==3.9.2         # via ase, mdanalysis, pymatgen
-mda-xdrlib==0.2.0         # via mdanalysis, pyedr
-mdanalysis==2.7.0         # via nomad-lab (pyproject.toml)
+matplotlib==3.9.2         # via ase, pymatgen
+mda-xdrlib==0.2.0         # via pyedr
 mistune==3.0.2            # via m2r
-mmtf-python==1.1.3        # via mdanalysis
 mongoengine==0.29.0       # via nomad-lab (pyproject.toml)
 mongomock==4.1.2          # via optimade
 monty==2024.7.30          # via pymatgen
 mpmath==1.3.0             # via sympy
-mrcfile==1.5.3            # via griddataformats
-msgpack==1.0.8            # via mmtf-python, nomad-lab (pyproject.toml)
+msgpack==1.1.0            # via nomad-lab (pyproject.toml)
 netcdf4==1.6.5            # via nomad-lab (pyproject.toml)
-networkx==3.3             # via matid, pymatgen, nomad-lab (pyproject.toml)
+networkx==3.3             # via matid, pymatgen
 nomad-openbis==1.0.0      # via nomad-lab (pyproject.toml)
 nptyping==1.4.4           # via nomad-lab (pyproject.toml)
-numpy==1.26.4             # via ase, cftime, contourpy, griddataformats, h5grove, h5py, matid, matplotlib, mdanalysis, mrcfile, netcdf4, nptyping, pandas, pyedr, pymatgen, rdkit, scikit-learn, scipy, spglib, tifffile, xarray, nomad-lab (pyproject.toml)
+numpy==1.26.4             # via ase, cftime, contourpy, h5grove, h5py, matid, matplotlib, netcdf4, nptyping, pandas, pyedr, pymatgen, rdkit, scikit-learn, scipy, spglib, tifffile, xarray, nomad-lab (pyproject.toml)
 oauthenticator==15.1.0    # via nomad-lab (pyproject.toml)
 oauthlib==3.2.2           # via jupyterhub
 openpyxl==3.1.5           # via nomad-lab (pyproject.toml)
 optimade==0.22.1          # via nomad-lab (pyproject.toml)
 orjson==3.10.7            # via h5grove, nomad-lab (pyproject.toml)
-packaging==24.1           # via deprecation, gunicorn, jupyterhub, matplotlib, mdanalysis, mongomock, pint, plotly, sphinx, xarray
+packaging==24.1           # via deprecation, gunicorn, jupyterhub, matplotlib, mongomock, pint, plotly, sphinx, xarray
 palettable==3.3.3         # via pymatgen
 pamela==1.2.0 ; sys_platform != 'win32'  # via jupyterhub
 pandas==2.2.2             # via nomad-openbis, panedr, pymatgen, xarray, nomad-lab (pyproject.toml)
@@ -158,7 +153,7 @@ ruamel-yaml==0.18.6       # via jupyter-telemetry, oauthenticator, pymatgen
 ruamel-yaml-clib==0.2.8 ; python_full_version < '3.13' and platform_python_implementation == 'CPython'  # via ruamel-yaml
 runstats==2.0.0           # via nomad-lab (pyproject.toml)
 scikit-learn==1.5.1       # via matid, nomad-lab (pyproject.toml)
-scipy==1.14.1             # via ase, griddataformats, mdanalysis, pymatgen, scikit-learn, nomad-lab (pyproject.toml)
+scipy==1.14.1             # via ase, pymatgen, scikit-learn, nomad-lab (pyproject.toml)
 sentinels==1.0.0          # via mongomock
 six==1.16.0               # via basicauth, elasticsearch-dsl, html5lib, isodate, pybtex, python-dateutil, rdflib, rfc3339-validator, validators
 smmap==5.0.1              # via gitdb
@@ -180,13 +175,13 @@ sympy==1.13.2             # via pymatgen
 tabulate==0.8.9           # via nomad-openbis, pymatgen, nomad-lab (pyproject.toml)
 tenacity==9.0.0           # via plotly
 texttable==1.7.0          # via nomad-openbis
-threadpoolctl==3.5.0      # via mdanalysis, scikit-learn
+threadpoolctl==3.5.0      # via scikit-learn
 tifffile==2024.8.30       # via h5grove
 toposort==1.10            # via nomad-lab (pyproject.toml)
 tornado==6.4.1            # via jupyterhub
-tqdm==4.66.5              # via mdanalysis, pyedr, pymatgen
+tqdm==4.66.5              # via pyedr, pymatgen
 traitlets==5.14.3         # via jupyter-telemetry, jupyterhub
-types-python-dateutil==2.9.0.20240821  # via arrow
+types-python-dateutil==2.9.0.20240906  # via arrow
 typing-extensions==4.12.2  # via alembic, fastapi, jwcrypto, pydantic, sqlalchemy
 typish==1.9.3             # via nptyping
 tzdata==2024.1            # via celery, pandas
diff --git a/tests/app/v1/routers/test_systems.py b/tests/app/v1/routers/test_systems.py
index fe261c95348a121c3ec87207d9c0dea6aefc88a5..debb8bdef085c2f6a3950386e9cbb53c7a161309 100644
--- a/tests/app/v1/routers/test_systems.py
+++ b/tests/app/v1/routers/test_systems.py
@@ -225,10 +225,11 @@ REMARK 285  A: 5.000, 0.000, 0.000
 REMARK 285  B: 0.000, 5.000, 0.000
 REMARK 285  C: 0.000, 0.000, 5.000
 REMARK 285 PBC (A, B, C): TRUE, TRUE, TRUE
-CRYST1    5.000    5.000    5.000  90.00  90.00  90.00 P 1           1
-ATOM      1  C   UNK X   1       0.000   0.000   0.000  1.00  0.00           C
-ATOM      2  H   UNK X   1       1.000   1.000   1.000  1.00  0.00           H
-END
+CRYST1    5.000    5.000    5.000  90.00  90.00  90.00 P 1
+MODEL     1
+ATOM      1    C MOL     1       0.000   0.000   0.000  1.00  0.00           C
+ATOM      2    H MOL     1       1.000   1.000   1.000  1.00  0.00           H
+ENDMDL
 """,
             'CH.pdb',
             id='pdb',
@@ -308,13 +309,10 @@ def test_formats_with_cell(
         pytest.param(
             'pdb',
             """TITLE     NOMAD ENTRY ID: systems_entry_1
-REMARK 285 UNITARY VALUES FOR THE UNIT CELL SET BECAUSE UNIT CELL INFORMATION
-REMARK 285 WAS MISSING. PROTEIN DATA BANK CONVENTIONS REQUIRE THAT CRYST1
-REMARK 285 RECORD IS INCLUDED, BUT THE VALUES ON THIS RECORD ARE MEANINGLESS.
-CRYST1    1.000    1.000    1.000  90.00  90.00  90.00 P 1           1
-ATOM      1  N   UNK X   1       0.000   0.000   0.000  1.00  0.00           N
-ATOM      2  O   UNK X   1       1.000   1.000   1.000  1.00  0.00           O
-END
+MODEL     1
+ATOM      1    N MOL     1       0.000   0.000   0.000  1.00  0.00           N
+ATOM      2    O MOL     1       1.000   1.000   1.000  1.00  0.00           O
+ENDMDL
 """,
             'NO.pdb',
             id='pdb',