Repository: N720720/lindemann (language: python)

Issue:
Memory Optimization  Using OVITO Pipeline
#### Description:
When analyzing molecular dynamics trajectories to calculate the Lindemann index, it's essential to manage memory efficiently, especially for large datasets. Currently, the process of loading entire trajectories into memory can be optimized by leveraging OVITO's on-demand pipeline capabilities.

#### Problem:
- **Memory Usage** can be optimized. Loading entire trajectories into memory is not always feasible, particularly for large datasets.

#### Solution:
By using OVITO's on-demand frame loading capabilities, we can significantly reduce memory usage. OVITO's pipeline system allows for frames to be loaded as needed, rather than all at once. This way, we can efficiently calculate the Lindemann index without excessive memory consumption.

### Key Insight:
The use of Welford's online algorithm for calculating the Lindemann index is crucial in this approach. Its ability to update mean and variance incrementally allows us to take full advantage of OVITO's on-demand pipeline. This means we can process each frame individually and update our calculations without needing to store all frames in memory simultaneously.

### Implementation:
The approach involves two main components:
1. **Trajectory Loader**: Efficiently loads trajectory frames one at a time using OVITO's pipeline, thus minimizing memory usage.
2. **Lindemann Index Calculation**: Computes the Lindemann index using Welford's online algorithm to update the mean and variance of distances incrementally.

### Benefits:
1. **Reduced Memory Usage**: The on-demand loading mechanism ensures only the necessary data is in memory, making it suitable for large trajectories.
2. We can reduce the memory usage from $f * n* \frac{(n-1)}{2}\$ to $n*n$, where $f$ is the number of frames and $n$ is the number of atoms. 


### Documentation Reference:
For more details on OVITO's data import and on-demand frame loading, refer to the [OVITO documentation](https://docs.ovito.org/python/introduction/file_io.html#data-import).
Memory Optimization  Using OVITO Pipeline
#### Description:
When analyzing molecular dynamics trajectories to calculate the Lindemann index, it's essential to manage memory efficiently, especially for large datasets. Currently, the process of loading entire trajectories into memory can be optimized by leveraging OVITO's on-demand pipeline capabilities.

#### Problem:
- **Memory Usage** can be optimized. Loading entire trajectories into memory is not always feasible, particularly for large datasets.

#### Solution:
By using OVITO's on-demand frame loading capabilities, we can significantly reduce memory usage. OVITO's pipeline system allows for frames to be loaded as needed, rather than all at once. This way, we can efficiently calculate the Lindemann index without excessive memory consumption.

### Key Insight:
The use of Welford's online algorithm for calculating the Lindemann index is crucial in this approach. Its ability to update mean and variance incrementally allows us to take full advantage of OVITO's on-demand pipeline. This means we can process each frame individually and update our calculations without needing to store all frames in memory simultaneously.

### Implementation:
The approach involves two main components:
1. **Trajectory Loader**: Efficiently loads trajectory frames one at a time using OVITO's pipeline, thus minimizing memory usage.
2. **Lindemann Index Calculation**: Computes the Lindemann index using Welford's online algorithm to update the mean and variance of distances incrementally.

### Benefits:
1. **Reduced Memory Usage**: The on-demand loading mechanism ensures only the necessary data is in memory, making it suitable for large trajectories.
2. We can reduce the memory usage from $f * n* \frac{(n-1)}{2}\$ to $n*n$, where $f$ is the number of frames and $n$ is the number of atoms. 


### Documentation Reference:
For more details on OVITO's data import and on-demand frame loading, refer to the [OVITO documentation](https://docs.ovito.org/python/introduction/file_io.html#data-import).
Memory Optimization  Using OVITO Pipeline
#### Description:
When analyzing molecular dynamics trajectories to calculate the Lindemann index, it's essential to manage memory efficiently, especially for large datasets. Currently, the process of loading entire trajectories into memory can be optimized by leveraging OVITO's on-demand pipeline capabilities.

#### Problem:
- **Memory Usage** can be optimized. Loading entire trajectories into memory is not always feasible, particularly for large datasets.

#### Solution:
By using OVITO's on-demand frame loading capabilities, we can significantly reduce memory usage. OVITO's pipeline system allows for frames to be loaded as needed, rather than all at once. This way, we can efficiently calculate the Lindemann index without excessive memory consumption.

### Key Insight:
The use of Welford's online algorithm for calculating the Lindemann index is crucial in this approach. Its ability to update mean and variance incrementally allows us to take full advantage of OVITO's on-demand pipeline. This means we can process each frame individually and update our calculations without needing to store all frames in memory simultaneously.

### Implementation:
The approach involves two main components:
1. **Trajectory Loader**: Efficiently loads trajectory frames one at a time using OVITO's pipeline, thus minimizing memory usage.
2. **Lindemann Index Calculation**: Computes the Lindemann index using Welford's online algorithm to update the mean and variance of distances incrementally.

### Benefits:
1. **Reduced Memory Usage**: The on-demand loading mechanism ensures only the necessary data is in memory, making it suitable for large trajectories.
2. We can reduce the memory usage from $f * n* \frac{(n-1)}{2}\$ to $n*n$, where $f$ is the number of frames and $n$ is the number of atoms. 


### Documentation Reference:
For more details on OVITO's data import and on-demand frame loading, refer to the [OVITO documentation](https://docs.ovito.org/python/introduction/file_io.html#data-import).
Memory Optimization  Using OVITO Pipeline
#### Description:
When analyzing molecular dynamics trajectories to calculate the Lindemann index, it's essential to manage memory efficiently, especially for large datasets. Currently, the process of loading entire trajectories into memory can be optimized by leveraging OVITO's on-demand pipeline capabilities.

#### Problem:
- **Memory Usage** can be optimized. Loading entire trajectories into memory is not always feasible, particularly for large datasets.

#### Solution:
By using OVITO's on-demand frame loading capabilities, we can significantly reduce memory usage. OVITO's pipeline system allows for frames to be loaded as needed, rather than all at once. This way, we can efficiently calculate the Lindemann index without excessive memory consumption.

### Key Insight:
The use of Welford's online algorithm for calculating the Lindemann index is crucial in this approach. Its ability to update mean and variance incrementally allows us to take full advantage of OVITO's on-demand pipeline. This means we can process each frame individually and update our calculations without needing to store all frames in memory simultaneously.

### Implementation:
The approach involves two main components:
1. **Trajectory Loader**: Efficiently loads trajectory frames one at a time using OVITO's pipeline, thus minimizing memory usage.
2. **Lindemann Index Calculation**: Computes the Lindemann index using Welford's online algorithm to update the mean and variance of distances incrementally.

### Benefits:
1. **Reduced Memory Usage**: The on-demand loading mechanism ensures only the necessary data is in memory, making it suitable for large trajectories.
2. We can reduce the memory usage from $f * n* \frac{(n-1)}{2}\$ to $n*n$, where $f$ is the number of frames and $n$ is the number of atoms. 


### Documentation Reference:
For more details on OVITO's data import and on-demand frame loading, refer to the [OVITO documentation](https://docs.ovito.org/python/introduction/file_io.html#data-import).
Memory Optimization  Using OVITO Pipeline
#### Description:
When analyzing molecular dynamics trajectories to calculate the Lindemann index, it's essential to manage memory efficiently, especially for large datasets. Currently, the process of loading entire trajectories into memory can be optimized by leveraging OVITO's on-demand pipeline capabilities.

#### Problem:
- **Memory Usage** can be optimized. Loading entire trajectories into memory is not always feasible, particularly for large datasets.

#### Solution:
By using OVITO's on-demand frame loading capabilities, we can significantly reduce memory usage. OVITO's pipeline system allows for frames to be loaded as needed, rather than all at once. This way, we can efficiently calculate the Lindemann index without excessive memory consumption.

### Key Insight:
The use of Welford's online algorithm for calculating the Lindemann index is crucial in this approach. Its ability to update mean and variance incrementally allows us to take full advantage of OVITO's on-demand pipeline. This means we can process each frame individually and update our calculations without needing to store all frames in memory simultaneously.

### Implementation:
The approach involves two main components:
1. **Trajectory Loader**: Efficiently loads trajectory frames one at a time using OVITO's pipeline, thus minimizing memory usage.
2. **Lindemann Index Calculation**: Computes the Lindemann index using Welford's online algorithm to update the mean and variance of distances incrementally.

### Benefits:
1. **Reduced Memory Usage**: The on-demand loading mechanism ensures only the necessary data is in memory, making it suitable for large trajectories.
2. We can reduce the memory usage from $f * n* \frac{(n-1)}{2}\$ to $n*n$, where $f$ is the number of frames and $n$ is the number of atoms. 


### Documentation Reference:
For more details on OVITO's data import and on-demand frame loading, refer to the [OVITO documentation](https://docs.ovito.org/python/introduction/file_io.html#data-import).
Memory Optimization  Using OVITO Pipeline
#### Description:
When analyzing molecular dynamics trajectories to calculate the Lindemann index, it's essential to manage memory efficiently, especially for large datasets. Currently, the process of loading entire trajectories into memory can be optimized by leveraging OVITO's on-demand pipeline capabilities.

#### Problem:
- **Memory Usage** can be optimized. Loading entire trajectories into memory is not always feasible, particularly for large datasets.

#### Solution:
By using OVITO's on-demand frame loading capabilities, we can significantly reduce memory usage. OVITO's pipeline system allows for frames to be loaded as needed, rather than all at once. This way, we can efficiently calculate the Lindemann index without excessive memory consumption.

### Key Insight:
The use of Welford's online algorithm for calculating the Lindemann index is crucial in this approach. Its ability to update mean and variance incrementally allows us to take full advantage of OVITO's on-demand pipeline. This means we can process each frame individually and update our calculations without needing to store all frames in memory simultaneously.

### Implementation:
The approach involves two main components:
1. **Trajectory Loader**: Efficiently loads trajectory frames one at a time using OVITO's pipeline, thus minimizing memory usage.
2. **Lindemann Index Calculation**: Computes the Lindemann index using Welford's online algorithm to update the mean and variance of distances incrementally.

### Benefits:
1. **Reduced Memory Usage**: The on-demand loading mechanism ensures only the necessary data is in memory, making it suitable for large trajectories.
2. We can reduce the memory usage from $f * n* \frac{(n-1)}{2}\$ to $n*n$, where $f$ is the number of frames and $n$ is the number of atoms. 


### Documentation Reference:
For more details on OVITO's data import and on-demand frame loading, refer to the [OVITO documentation](https://docs.ovito.org/python/introduction/file_io.html#data-import).


Discussion / hints:

Fix patch:
diff --git a/lindemann/index/mem_use.py b/lindemann/index/mem_use.py
index 05ddbc3..f830ca5 100644
--- a/lindemann/index/mem_use.py
+++ b/lindemann/index/mem_use.py
@@ -23,13 +23,18 @@ def in_gb(nframes: int, natoms: int) -> str:
     num_distances = natoms * (natoms - 1) // 2
     float_size = np.float32().nbytes
     trj = nframes * natoms * 3 * float_size
-    atom_atom_array = 3 * natoms * natoms * float_size
+    atom_atom_array = 2 * natoms * natoms * float_size
     atom_array = natoms * float_size
     linde_index = nframes * natoms * float_size
     sum_bytes = trj + atom_atom_array + atom_array + linde_index
     per_trj = (
         f"\nFlag -t (per_trj) will use {np.round((trj+num_distances*2*float_size)/1024**3,4)} GB\n"
     )
+    online_per_trj = (
+        f"Flag -ot (per_trj) will use {np.round((num_distances*2*float_size)/1024**3,4)} GB\n"
+    )
     per_frames = f"Flag -f (per_frames) will use {np.round((trj+(num_distances*2*float_size)+(nframes*float_size))/1024**3,4)} GB\n"
-    per_atoms = f"Flag -a (per_atoms) will use {np.round(sum_bytes/1024**3,4)} GB"
-    return f"{per_trj}{per_frames}{per_atoms}"
+    online_per_frames = f"Flag -of (per_frames) will use {np.round(((num_distances*2*float_size)+(nframes*float_size))/1024**3,4)} GB\n"
+    per_atoms = f"Flag -a (per_atoms) will use {np.round((sum_bytes)/1024**3,4)} GB\n"
+    online_per_atoms = f"Flag -oa (per_atoms) will use {np.round((sum_bytes-trj)/1024**3,4)} GB"
+    return f"{per_trj}{online_per_trj}{per_frames}{online_per_frames}{per_atoms}{online_per_atoms}"
diff --git a/lindemann/index/online_atoms.py b/lindemann/index/online_atoms.py
new file mode 100644
index 0000000..1b49420
--- /dev/null
+++ b/lindemann/index/online_atoms.py
@@ -0,0 +1,92 @@
+from typing import Optional
+
+import numba as nb
+import numpy as np
+import numpy.typing as npt
+from ovito.data import DataCollection
+from ovito.pipeline import Pipeline
+
+
+@nb.njit(fastmath=True, parallel=False)
+def calculate_frame(
+    positions: npt.NDArray[np.float32],
+    array_mean: npt.NDArray[np.float32],
+    array_var: npt.NDArray[np.float32],
+    frame: int,
+    natoms: int,
+) -> npt.NDArray[np.float32]:
+    """
+    Calculates the contribution of the individual atomic positions to the Lindemann Index for a specific frame.
+
+    Args:
+        positions (npt.NDArray[np.float32]): Array of atomic positions for the current frame.
+        array_mean (npt.NDArray[np.float32]): Array to store the mean distances.
+        array_var (npt.NDArray[np.float32]): Array to store the variance of distances.
+        frame (int): The current frame index.
+        natoms (int): The number of atoms.
+
+    Returns:
+        npt.NDArray[np.float32]: Array of the individual atomic contributions to the Lindemann indices for the current frame.
+    """
+    frame_count = frame + 1
+    for i in range(natoms):
+        for j in range(i + 1, natoms):
+            dist = 0.0
+            for k in range(3):
+                dist += (positions[i, k] - positions[j, k]) ** 2
+            dist = np.sqrt(dist)
+            mean = array_mean[i, j]
+            var = array_var[i, j]
+            delta = dist - mean
+            update_mean = mean + delta / frame_count
+            array_mean[i, j] = update_mean
+            array_mean[j, i] = update_mean
+            delta2 = dist - array_mean[i, j]
+            update_var = var + delta * delta2
+            array_var[i, j] = update_var
+            array_var[j, i] = update_var
+
+    np.fill_diagonal(array_mean, 1.0)
+    lindemann_indices = np.divide(np.sqrt(np.divide(array_var, frame_count)), array_mean).astype(
+        np.float32
+    )
+    lindemann_indices = np.asarray(
+        [np.nanmean(lin[lin != 0]) for lin in lindemann_indices]
+    ).astype(np.float32)
+
+    return lindemann_indices
+
+
+def calculate(
+    pipeline: Pipeline, data: DataCollection, nframes: Optional[int] = None
+) -> npt.NDArray[np.float32]:
+    """
+    Calculates the contribution of the individual atomic positions to the Lindemann Index for a series of frames from an OVITO pipeline.
+
+    Args:
+        pipeline (Pipeline): The OVITO pipeline object.
+        data (DataCollection): The data collection object from OVITO.
+        nframes (Optional[int]): The number of frames to process. If None, all frames are processed.
+
+    Returns:
+        npt.NDArray[np.float32]: Array of the individual atomic contributions to the Lindemann indices for each frame.
+
+    Raises:
+        ValueError: If the requested number of frames exceeds the available frames in the pipeline.
+    """
+    num_particle = data.particles.count
+    num_frame = pipeline.source.num_frames
+    if nframes is None:
+        nframes = num_frame
+    elif nframes > num_frame:
+        raise ValueError(f"Requested {nframes} frames, but only {num_frame} frames are available.")
+
+    array_mean = np.zeros((num_particle, num_particle), dtype=np.float32)
+    array_var = np.zeros((num_particle, num_particle), dtype=np.float32)
+    lindex_array = np.zeros((nframes, num_particle), dtype=np.float32)
+    for frame in range(nframes):
+        data = pipeline.compute(frame)
+        lindex_array[frame] = calculate_frame(
+            data.particles["Position"].array, array_mean, array_var, frame, num_particle
+        )
+    return lindex_array
diff --git a/lindemann/index/online_frames.py b/lindemann/index/online_frames.py
new file mode 100644
index 0000000..19150ed
--- /dev/null
+++ b/lindemann/index/online_frames.py
@@ -0,0 +1,82 @@
+from typing import Any, Optional
+
+import numba as nb
+import numpy as np
+import numpy.typing as npt
+from ovito.data import DataCollection
+from ovito.pipeline import Pipeline
+
+
+@nb.njit(fastmath=True, parallel=False)
+def calculate_frame(
+    positions: npt.NDArray[np.float32],
+    mean_distances: npt.NDArray[np.float32],
+    m2_distances: npt.NDArray[np.float32],
+    frame: int,
+    num_atoms: int,
+) -> np.floating[Any]:
+    """
+    Calculates the Lindemann Index for a specific frame.
+
+    Args:
+        positions (npt.NDArray[np.float32]): Array of atomic positions for the current frame.
+        mean_distances (npt.NDArray[np.float32]): Array to store the mean distances between pairs of atoms.
+        m2_distances (npt.NDArray[np.float32]): Array to store the squared differences of distances between pairs of atoms.
+        frame (int): The current frame index.
+        num_atoms (int): The number of atoms.
+
+    Returns:
+        float: The Lindemann index for the current frame.
+    """
+    index = 0
+    frame_count = frame + 1
+    for i in range(num_atoms):
+        for j in range(i + 1, num_atoms):
+            dist = 0.0
+            for k in range(3):
+                dist += (positions[i, k] - positions[j, k]) ** 2
+
+            dist = np.sqrt(dist)
+            delta = dist - mean_distances[index]
+            mean_distances[index] += delta / frame_count
+            delta2 = dist - mean_distances[index]
+            m2_distances[index] += delta * delta2
+
+            index += 1
+    return np.mean(np.sqrt(m2_distances / frame_count) / mean_distances)
+
+
+def calculate(
+    pipeline: Pipeline, data: DataCollection, nframes: Optional[int] = None
+) -> npt.NDArray[np.float32]:
+    """
+    Calculates the Lindemann indices for a series of frames from an OVITO pipeline.
+
+    Args:
+        pipeline (Pipeline): The OVITO pipeline object.
+        data (DataCollection): The data collection object from OVITO.
+        nframes (Optional[int]): The number of frames to process. If None, all frames are processed.
+
+    Returns:
+        npt.NDArray[np.float32]: Array of Lindemann indices for each frame.
+
+    Raises:
+        ValueError: If the requested number of frames exceeds the available frames in the pipeline.
+    """
+    num_particle = data.particles.count
+    num_frame = pipeline.source.num_frames
+    if nframes is None:
+        nframes = num_frame
+    elif nframes > num_frame:
+        raise ValueError(f"Requested {nframes} frames, but only {num_frame} frames are available.")
+
+    num_distances = num_particle * (num_particle - 1) // 2
+    mean_distances = np.zeros(num_distances, dtype=np.float32)
+    m2_distances = np.zeros(num_distances, dtype=np.float32)
+    lindemann_index_array = np.zeros(nframes, dtype=np.float32)
+    for frame in range(nframes):
+        data = pipeline.compute(frame)
+        lindemann_index_array[frame] = calculate_frame(
+            data.particles["Position"].array, mean_distances, m2_distances, frame, num_particle
+        )
+    return lindemann_index_array
diff --git a/lindemann/index/online_trj.py b/lindemann/index/online_trj.py
new file mode 100644
index 0000000..33c7c7f
--- /dev/null
+++ b/lindemann/index/online_trj.py
@@ -0,0 +1,84 @@
+from typing import Any, Optional
+
+import numba as nb
+import numpy as np
+import numpy.typing as npt
+from ovito.data import DataCollection
+from ovito.pipeline import Pipeline
+
+from lindemann.trajectory import read
+
+
+@nb.njit(fastmath=True, parallel=False)
+def calculate_frame(
+    positions: npt.NDArray[np.float32],
+    mean_distances: npt.NDArray[np.float32],
+    m2_distances: npt.NDArray[np.float32],
+    frame: int,
+    num_atoms: int,
+) -> None:
+    """
+    Updates the mean and variance of distances between pairs of atoms for a specific frame.
+
+    Args:
+        positions (npt.NDArray[np.float32]): Array of atomic positions for the current frame.
+        mean_distances (npt.NDArray[np.float32]): Array to store the mean distances between pairs of atoms.
+        m2_distances (npt.NDArray[np.float32]): Array to store the squared differences of distances between pairs of atoms.
+        frame (int): The current frame index.
+        num_atoms (int): The number of atoms.
+
+    Returns:
+        None
+    """
+    index = 0
+    frame_count = frame + 1
+    for i in range(num_atoms):
+        for j in range(i + 1, num_atoms):
+            dist = 0.0
+            for k in range(3):
+                dist += (positions[i, k] - positions[j, k]) ** 2
+
+            dist = np.sqrt(dist)
+            delta = dist - mean_distances[index]
+            mean_distances[index] += delta / frame_count
+            delta2 = dist - mean_distances[index]
+            m2_distances[index] += delta * delta2
+
+            index += 1
+
+
+def calculate(
+    pipeline: Pipeline, data: DataCollection, nframes: Optional[int] = None
+) -> np.floating[Any]:
+    """
+    Calculates the overall Lindemann index for a series of frames from an OVITO pipeline.
+
+    Args:
+        pipeline (Pipeline): The OVITO pipeline object.
+        data (DataCollection): The data collection object from OVITO.
+        nframes (Optional[int]): The number of frames to process. If None, all frames are processed.
+
+    Returns:
+        float: The overall Lindemann index.
+
+    Raises:
+        ValueError: If the requested number of frames exceeds the available frames in the pipeline.
+    """
+    num_particle = data.particles.count
+    num_frame = pipeline.source.num_frames
+    if nframes is None:
+        nframes = num_frame
+    elif nframes > num_frame:
+        raise ValueError(f"Requested {nframes} frames, but only {num_frame} frames are available.")
+
+    num_distances = num_particle * (num_particle - 1) // 2
+    mean_distances = np.zeros(num_distances, dtype=np.float32)
+    m2_distances = np.zeros(num_distances, dtype=np.float32)
+    print(nframes, num_particle)
+    for frame in range(nframes):
+        data = pipeline.compute(frame)
+        calculate_frame(
+            data.particles["Position"].array, mean_distances, m2_distances, frame, num_particle
+        )
+
+    return np.mean(np.sqrt(m2_distances / nframes) / mean_distances)
diff --git a/lindemann/index/per_atoms.py b/lindemann/index/per_atoms.py
index 81d3f19..16c1115 100644
--- a/lindemann/index/per_atoms.py
+++ b/lindemann/index/per_atoms.py
@@ -1,19 +1,20 @@
-from typing import List
-
 import numba as nb
 import numpy as np
 import numpy.typing as npt
-from numba import float32
 
 
-@nb.njit(fastmath=True, error_model="numpy")  # type: ignore # , cache=True) #(parallel=True)
+@nb.njit(fastmath=True, parallel=False)
 def calculate(frames: npt.NDArray[np.float32]) -> npt.NDArray[np.float32]:
-    """Calculate the contribution of each atom to the lindemann index over the frames
+    """
+    Calculate the contribution of each atom to the Lindemann index over the frames.
 
     Args:
-        frames: numpy array of shape(frames,atoms)
+        frames (npt.NDArray[np.float32]): A numpy array of shape (frames, atoms, 3) containing the atomic positions
+                                          over multiple frames.
+
     Returns:
-        npt.NDArray[np.float32]: Returns 1D array with the progression of the lindeman index per frame of shape(frames, atoms)
+        npt.NDArray[np.float32]: A 2D array of shape (frames, atoms) containing the progression of the Lindemann index
+                                 per frame.
     """
     len_frames, natoms, _ = frames.shape
 
diff --git a/lindemann/index/per_frames.py b/lindemann/index/per_frames.py
index 25c8175..d903ba5 100644
--- a/lindemann/index/per_frames.py
+++ b/lindemann/index/per_frames.py
@@ -1,9 +1,19 @@
 import numba as nb
 import numpy as np
+import numpy.typing as npt
 
 
 @nb.njit(fastmath=True, parallel=False)
-def calculate(positions):
+def calculate(positions: npt.NDArray[np.float32]):
+    """
+    Calculates the Lindemann index for each frame of atomic positions.
+
+    Args:
+        positions (npt.NDArray[np.float32]): Array of atomic positions with shape (num_frames, num_atoms, 3).
+
+    Returns:
+        npt.NDArray[np.float32]: Array of Lindemann indices for each frame.
+    """
     num_frames, num_atoms, _ = positions.shape
     num_distances = num_atoms * (num_atoms - 1) // 2
 
diff --git a/lindemann/index/per_trj.py b/lindemann/index/per_trj.py
index 915d2e7..433e322 100644
--- a/lindemann/index/per_trj.py
+++ b/lindemann/index/per_trj.py
@@ -1,9 +1,21 @@
+from typing import Any
+
 import numba as nb
 import numpy as np
+import numpy.typing as npt
 
 
 @nb.njit(fastmath=True)
-def calculate(positions):
+def calculate(positions: npt.NDArray[np.float32]) -> np.floating[Any]:
+    """
+    Calculates the overall Lindemann index for a series of atomic positions over multiple frames.
+
+    Args:
+        positions (npt.NDArray[np.float32]): Array of atomic positions with shape (num_frames, num_atoms, 3).
+
+    Returns:
+        np.floating[np.Any]: The overall Lindemann index.
+    """
     num_frames, num_atoms, _ = positions.shape
     num_distances = num_atoms * (num_atoms - 1) // 2
 
diff --git a/lindemann/main.py b/lindemann/main.py
index da979ba..8d0bb5e 100644
--- a/lindemann/main.py
+++ b/lindemann/main.py
@@ -1,5 +1,3 @@
-# type: ignore[attr-defined]
-# https://github.com/numba/numba/issues/4713
 import time
 from multiprocessing import Pool
 from pathlib import Path
@@ -10,7 +8,15 @@
 from rich.console import Console
 
 from lindemann import __version__
-from lindemann.index import mem_use, per_atoms, per_frames, per_trj
+from lindemann.index import (
+    mem_use,
+    online_atoms,
+    online_frames,
+    online_trj,
+    per_atoms,
+    per_frames,
+    per_trj,
+)
 from lindemann.trajectory import plt_plot, read, save
 
 app = typer.Typer(
@@ -34,22 +40,31 @@ def version_callback(value: bool):
 def main(
     trjfile: list[Path] = typer.Argument(
         ...,
-        help="The trajectory file(s). If no other option is selected, the lindemann index is calculated for the trajectory. \
-            Equivalent to the -t option. If you pass more than one trajectory they will be calculated in parallel. \
-                Only works with no flag or -t flag.",
+        help="The trajectory file(s). If no other option is selected, the Lindemann index is calculated for the trajectory. \
+              Equivalent to the -t option. If you pass more than one trajectory they will be calculated in parallel. \
+              Only works with no flag or -t flag.",
     ),
     trj: bool = typer.Option(
+        False, "-t", help="Calculates the Lindemann-Index for the Trajectory file(s)"
+    ),
+    on_trj: bool = typer.Option(
         False,
-        "-t",
-        help="Calculates the Lindemann-Index for the Trajectory file(s)",
+        "-ot",
+        help="Calculates the Lindemann-Index for the Trajectory file(s) (reduced memory usage)",
     ),
     frames: bool = typer.Option(
         False, "-f", help="Calculates the Lindemann-Index for each frame."
     ),
+    on_frames: bool = typer.Option(
+        False, "-of", help="Calculates the Lindemann-Index for each frame. (reduced memory usage)"
+    ),
     atoms: bool = typer.Option(
+        False, "-a", help="Calculates the Lindemann-Index for each atom for each frame."
+    ),
+    on_atoms: bool = typer.Option(
         False,
-        "-a",
-        help="Calculates the Lindemann-Index for each atom for each frame.",
+        "-oa",
+        help="Calculates the Lindemann-Index for each atom for each frame. (reduced memory usage)",
     ),
     plot: bool = typer.Option(False, "-p", help="Returns a plot Lindemann-Index vs. Frame."),
     lammpstrj: bool = typer.Option(
@@ -66,16 +81,13 @@ def main(
         help="Prints the version of the lindemann package.",
     ),
     timeit: bool = typer.Option(
-        False,
-        "-ti",
-        "-timeit",
-        help="Uses timeit module to show running time",
+        False, "-ti", "-timeit", help="Uses timeit module to show running time"
     ),
     mem_useage: bool = typer.Option(
         False,
         "-m",
         "-mem_use",
-        help="Calculates the memory use. Run it before you use any of the cli functionality despite the -t flag",
+        help="Calculates the memory use. Run it before you use any of the CLI functionality despite the -t flag",
     ),
 ):
     """
@@ -84,108 +96,119 @@ def main(
     for phase transition analysis.
     """
 
-    n_cores = cpu_count()
-    len_trjfiles = len(trjfile)
-
-    if len_trjfiles > n_cores:
-        n_cores = n_cores
-    else:
-        n_cores = len_trjfiles
-
-    if len(trjfile) == 1:
-        single_process = True
-        tjr_frames = read.frames(str(trjfile[0]))
-    else:
-        single_process = False
-        trjfile = [str(trjf) for trjf in trjfile]
-        tjr_frames = [read.frames(tf) for tf in trjfile]
+    n_cores = min(len(trjfile), cpu_count())
+    single_process = len(trjfile) == 1
+    trjfile_str = [str(trjf) for trjf in trjfile]
 
-    if trj and single_process:
+    def calculate_single_pipeline(pipeline_func, data_func, save_filename=None, save_func=None):
+        pipeline, data = pipeline_func(trjfile_str[0])
+        results = data_func(pipeline, data)
+        if save_filename and save_func:
+            save_func(save_filename, results)
+            console.print(f"[magenta]Lindemann index saved as:[/] [bold blue]{save_filename}[/]")
+        else:
+            console.print(
+                f"[magenta]lindemann index for the Trajectory:[/] [bold blue]{results}[/]"
+            )
+        typer.Exit()
 
-        console.print(
-            f"[magenta]lindemann index for the Trajectory:[/] [bold blue]{per_trj.calculate(tjr_frames)}[/]"
-        )
+    def calculate_single(trjfile, calc_func, save_filename=None, save_func=None):
+        frames = read.frames(trjfile)
+        results = calc_func(frames)
+        if save_filename and save_func:
+            save_func(save_filename, results)
+            console.print(f"[magenta]Lindemann index saved as:[/] [bold blue]{save_filename}[/]")
+        else:
+            console.print(
+                f"[magenta]lindemann index for the Trajectory:[/] [bold blue]{results}[/]"
+            )
         typer.Exit()
-    elif trj and not single_process:
+
+    def calculate_parallel(trjfile_str, calc_func):
+        trj_frames = [read.frames(tf) for tf in trjfile_str]
         with Pool(n_cores) as p:
             console.print(f"Using {n_cores} cores")
-            res = p.map(per_trj.calculate, tjr_frames)
+            res = p.map(calc_func, trj_frames)
             console.print(res)
         typer.Exit()
 
+    if on_trj and single_process:
+        calculate_single_pipeline(read.trajectory, online_trj.calculate)
+    elif trj and single_process:
+        calculate_single(trjfile_str[0], per_trj.calculate)
+    elif trj and not single_process:
+        calculate_parallel(trjfile_str, per_trj.calculate)
     elif frames and single_process:
-        my_file_name = False
-        if my_file_name:
-            print("not implemented")
-        else:
-            filename = "lindemann_per_frame.txt"
-        np.savetxt(filename, per_frames.calculate(tjr_frames))
-        console.print(f"[magenta]lindemann index per frame saved as:[/] [bold blue]{filename}[/]")
-        typer.Exit()
+        calculate_single(
+            trjfile_str[0], per_frames.calculate, "lindemann_index_per_frame.txt", np.savetxt
+        )
     elif frames and not single_process:
-        print("multiprocessing is implemented only for the -t flag")
+        console.print("multiprocessing is implemented only for the -t flag")
         typer.Exit()
-
-    elif atoms and single_process:
-        filename = "lindemann_per_atom.txt"
-        np.savetxt(filename, per_atoms.calculate(tjr_frames))
-        console.print(f"[magenta]lindemann index per atoms saved as:[/] [bold blue]{filename}[/]")
+    elif on_frames and single_process:
+        calculate_single_pipeline(
+            read.trajectory, online_frames.calculate, "lindemann_index_per_frame.txt", np.savetxt
+        )
+    elif on_frames and not single_process:
+        console.print("multiprocessing is implemented only for the -t flag")
         typer.Exit()
+    elif atoms and single_process:
+        calculate_single(
+            trjfile_str[0], per_atoms.calculate, "lindemann_index_per_atom.txt", np.savetxt
+        )
     elif atoms and not single_process:
-        print("multiprocessing is implemented only for the -t flag")
+        console.print("multiprocessing is implemented only for the -t flag")
+        typer.Exit()
+    elif on_atoms and single_process:
+        calculate_single_pipeline(
+            read.trajectory, online_atoms.calculate, "lindemann_index_per_atoms.txt", np.savetxt
+        )
+    elif on_atoms and not single_process:
+        console.print("multiprocessing is implemented only for the -t flag")
         typer.Exit()
-
     elif plot and single_process:
+        tjr_frames = read.frames(trjfile_str[0])
         indices = per_frames.calculate(tjr_frames)
-
-        console.print(
-            f"[magenta]Saved file as:[/] [bold blue]{plt_plot.lindemann_vs_frames(indices)}[/]"
-        )
+        plot_filename = plt_plot.lindemann_vs_frames(indices)
+        console.print(f"[magenta]Saved file as:[/] [bold blue]{plot_filename}[/]")
         typer.Exit()
     elif plot and not single_process:
-        print("multiprocessing is implemented only for the -t flag")
+        console.print("multiprocessing is implemented only for the -t flag")
         typer.Exit()
-
     elif lammpstrj and single_process:
-        indices_per_atom = per_atoms.calculate(tjr_frames)
-
-        console.print(f"[magenta]{save.to_lammps(str(trjfile[0]),indices_per_atom)}[/]")
+        calculate_single(trjfile_str[0], per_atoms.calculate, trjfile_str[0], save.to_lammps)
         typer.Exit()
     elif lammpstrj and not single_process:
-        print("multiprocessing is implemented only for the -t flag")
+        console.print("multiprocessing is implemented only for the -t flag")
         typer.Exit()
-
     elif timeit and single_process:
+        tjr_frames = read.frames(trjfile_str[0])
         start = time.time()
         linde_for_time = per_trj.calculate(tjr_frames)
         time_diff = time.time() - start
-
         console.print(
-            f"[magenta]lindemann index for the Trajectory:[/] [bold blue]{linde_for_time}[/] \n[magenta]Runtime:[/] [bold green]{time_diff}[/]"
+            f"[magenta]lindemann index for the Trajectory:[/] [bold blue]{linde_for_time}[/] \n"
+            f"[magenta]Runtime:[/] [bold green]{time_diff}[/]"
         )
         typer.Exit()
     elif timeit and not single_process:
-        print("multiprocessing is implemented only for the -t flag")
+        console.print("multiprocessing is implemented only for the -t flag")
         typer.Exit()
-
     elif mem_useage and single_process:
+        tjr_frames = read.frames(trjfile_str[0])
         nframes, natoms, _ = tjr_frames.shape
         mem_use_in_gb = mem_use.in_gb(nframes, natoms)
-
-        console.print(f"[magenta]memory use:[/] [bold blue]{mem_use_in_gb}[/]")
+        console.print(f"[magenta]Memory use:[/] [bold blue]{mem_use_in_gb}[/]")
         typer.Exit()
     elif mem_useage and not single_process:
-        print("multiprocessing is implemented only for the -t flag")
+        console.print("multiprocessing is implemented only for the -t flag")
         typer.Exit()
     else:
         if single_process:
-            console.print(
-                f"[magenta]lindemann index for the Trajectory:[/] [bold blue]{per_trj.calculate(tjr_frames)}[/]"
-            )
-            typer.Exit()
-        elif not single_process:
-            with Pool(n_cores) as p:
-                console.print(f"Using {n_cores} cores")
-                res = p.map(per_trj.calculate, tjr_frames)
-                console.print(res)
-            typer.Exit()
+            calculate_single_pipeline(read.trajectory, online_trj.calculate)
+        else:
+            calculate_parallel(trjfile_str, per_trj.calculate)
+
+
+if __name__ == "__main__":
+    app()
diff --git a/lindemann/trajectory/plt_plot.py b/lindemann/trajectory/plt_plot.py
index cdd7605..b937d20 100644
--- a/lindemann/trajectory/plt_plot.py
+++ b/lindemann/trajectory/plt_plot.py
@@ -6,7 +6,7 @@
 mpl.use("Agg")
 
 
-def lindemann_vs_frames(indices: npt.NDArray[np.float64]) -> str:
+def lindemann_vs_frames(indices: npt.NDArray[np.float32]) -> str:
     plt.figure(1)
     plt.title("Lindemann index per frame")
     plt.xlabel("Frames")
diff --git a/lindemann/trajectory/read.py b/lindemann/trajectory/read.py
index 235bf0a..49a437b 100644
--- a/lindemann/trajectory/read.py
+++ b/lindemann/trajectory/read.py
@@ -1,10 +1,13 @@
 from typing import Optional
 
+import numba as nb
 import numpy as np
 import numpy.typing as npt
 from ovito.io import import_file
 from ovito.modifiers import SelectTypeModifier
 
+from lindemann.index import per_trj
+
 
 def frames(trjfile: str, nframes: Optional[int] = None) -> npt.NDArray[np.float32]:
     """
@@ -53,3 +56,13 @@ def frames(trjfile: str, nframes: Optional[int] = None) -> npt.NDArray[np.float3
     frames = position
 
     return frames
+
+
+def trajectory(trjfile: str, nframes: Optional[int] = None):
+
+    pipeline = import_file(trjfile, sort_particles=True)
+    pipeline.modifiers.append(
+        SelectTypeModifier(operate_on="particles", property="Particle Type", types={1, 2, 3})
+    )
+    data = pipeline.compute()
+    return pipeline, data
diff --git a/poetry.lock b/poetry.lock
index e05511b..be14ebb 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1273,13 +1273,13 @@ traits = ">=6.3"
 
 [[package]]
 name = "packaging"
-version = "24.0"
+version = "24.1"
 description = "Core utilities for Python packages"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 files = [
-    {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
-    {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
+    {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
+    {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
 ]
 
 [[package]]
@@ -1762,13 +1762,13 @@ six = ">=1.5"
 
 [[package]]
 name = "pyupgrade"
-version = "3.15.2"
+version = "3.16.0"
 description = "A tool to automatically upgrade syntax for newer versions."
 optional = false
 python-versions = ">=3.8.1"
 files = [
-    {file = "pyupgrade-3.15.2-py2.py3-none-any.whl", hash = "sha256:ce309e0ff8ecb73f56a45f12570be84bbbde9540d13697cacb261a7f595fb1f5"},
-    {file = "pyupgrade-3.15.2.tar.gz", hash = "sha256:c488d6896c546d25845712ef6402657123008d56c1063174e27aabe15bd6b4e5"},
+    {file = "pyupgrade-3.16.0-py2.py3-none-any.whl", hash = "sha256:7a54ee28f3024d027048d49d101e5c702e88c85edc3a1d08b636c50ebef2a97d"},
+    {file = "pyupgrade-3.16.0.tar.gz", hash = "sha256:237893a05d5b117259b31b423f23cbae4bce0b7eae57ba9a52c06098c2ddd76f"},
 ]
 
 [package.dependencies]
@@ -1974,13 +1974,13 @@ files = [
 
 [[package]]
 name = "safety"
-version = "3.2.1"
+version = "3.2.3"
 description = "Checks installed dependencies for known vulnerabilities and licenses."
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "safety-3.2.1-py3-none-any.whl", hash = "sha256:9f53646717ba052e1bf631bd54fb3da0fafa58e85d578b20a8b9affdcf81889e"},
-    {file = "safety-3.2.1.tar.gz", hash = "sha256:770aed6ff46536e5363a409e8cca411d8636d07dc33be53a8ac69bf750ab8df3"},
+    {file = "safety-3.2.3-py3-none-any.whl", hash = "sha256:cda1e91749f610337a18b7f21f78267c127e44ebbbbcbbd419c83284279a5024"},
+    {file = "safety-3.2.3.tar.gz", hash = "sha256:414154934f1727daf8a6473493944fecb380540c3f00875dc1ae377382f7d83f"},
 ]
 
 [package.dependencies]
@@ -2238,13 +2238,13 @@ typer = "0.12.3"
 
 [[package]]
 name = "typing-extensions"
-version = "4.12.1"
+version = "4.12.2"
 description = "Backported and Experimental Type Hints for Python 3.8+"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"},
-    {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"},
+    {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
+    {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
 ]
 
 [[package]]
diff --git a/pyproject.toml b/pyproject.toml
index 99dd53e..92dc492 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ build-backend = "poetry.masonry.api"
 
 [tool.poetry]
 name = "lindemann"
-version = "0.7.0"
+version = "0.9.0"
 description = "lindemann is a python package to calculate the Lindemann index  of a lammps trajectory as well as the progression of the Lindemann index per frame of temperature ramps  for phase transition analysis."
 readme = "README.md"
 authors = [


Test patch:
diff --git a/tests/test_example/test_cli.py b/tests/test_example/test_cli.py
index 3dca34f..84a5a5f 100644
--- a/tests/test_example/test_cli.py
+++ b/tests/test_example/test_cli.py
@@ -22,15 +22,18 @@ def single_process_and_multiprocess(trajectory, flag, result_str):
 def test_all_flags_multiprocess():
     trajectory = ["tests/test_example/459_02.lammpstrj", "tests/test_example/459_01.lammpstrj"]
     result_str = "multiprocessing is implemented only for the -t flag"
-    for flag in ["-f", "-a", "-p", "-ti", "-m"]:
+    for flag in ["-f", "-of", "-a", "-oa", "-p", "-ti", "-m"]:
         single_process_and_multiprocess(trajectory, flag, result_str)
 
 
 def test_a_p_flags():
     trajectory = ["tests/test_example/459_02.lammpstrj"]
-    result_str = "lindemann index per"
+    result_str = "lindemann_index_per"
     for flag in [
         "-a",
+        "-oa",
+        "-f",
+        "-of",
         "-p",
     ]:
         single_process_and_multiprocess(trajectory, flag, result_str)
@@ -45,7 +48,7 @@ def test_t_flag():
 
 def test_m_flag():
     flag = "-m"
-    res_str = "memory use: \nFlag -t (per_trj) will use 0.0034 GB\nFlag -f (per_frames) will use 0.0034 GB\nFlag -a (per_atoms) will use 0.0058 GB\n"
+    res_str = "Memory use: \nFlag -t (per_trj) will use 0.0034 GB\nFlag -ot (per_trj) will use 0.0008 GB\nFlag -f (per_frames) will use 0.0034 GB\nFlag -of (per_frames) will use 0.0008 GB\nFlag -a (per_atoms) will use 0.005 GB\nFlag -oa (per_atoms) will use 0.0024 GB\n"
     trajectory = ["tests/test_example/459_02.lammpstrj"]
     single_process_and_multiprocess(trajectory, flag, res_str)
 
diff --git a/tests/test_example/test_numeric.py b/tests/test_example/test_numeric.py
index e53e4cd..f39c44e 100644
--- a/tests/test_example/test_numeric.py
+++ b/tests/test_example/test_numeric.py
@@ -1,7 +1,7 @@
 import numpy as np
 import pytest
 
-from lindemann.index import per_atoms, per_frames, per_trj
+from lindemann.index import online_atoms, online_frames, online_trj, per_atoms, per_frames, per_trj
 from lindemann.trajectory import read
 
 "Testing the individal parts of the index module, its possible to change the test setup for individual modules"
@@ -20,15 +20,31 @@
         ),
     ],
 )
-# def test_setup(trajectory: str, lindemannindex: float) -> bool:
-
-
 def test_tra(trajectory, lindemannindex):
     """Example test with parametrization."""
     frame = read.frames(trajectory)
     assert np.isclose(per_trj.calculate(frame), lindemannindex)
 
 
+@pytest.mark.parametrize(
+    ("trajectory", "lindemannindex"),
+    [
+        (
+            "tests/test_example/459_01.lammpstrj",
+            0.025923892565654555,
+        ),
+        (
+            "tests/test_example/459_02.lammpstrj",
+            0.026426709832984754,
+        ),
+    ],
+)
+def test_online_tra(trajectory, lindemannindex):
+    """Example test with parametrization."""
+    pipeline, data = read.trajectory(trajectory)
+    assert np.isclose(online_trj.calculate(pipeline, data), lindemannindex)
+
+
 @pytest.mark.parametrize(
     ("trajectory", "lindemannindex"),
     [
@@ -65,6 +81,42 @@ def test_frames_middle(trajectory):
     assert np.isclose(linde_at_200_per_frames, linde_from_200_trj)
 
 
+@pytest.mark.parametrize(
+    ("trajectory", "lindemannindex"),
+    [
+        (
+            "tests/test_example/459_01.lammpstrj",
+            0.025923892565654555,
+        ),
+        (
+            "tests/test_example/459_02.lammpstrj",
+            0.026426709832984754,
+        ),
+    ],
+)
+def test_online_frames(trajectory, lindemannindex):
+    """Example test with parametrization."""
+    pipeline, data = read.trajectory(trajectory)
+    test_array = online_frames.calculate(pipeline, data)
+    assert np.isclose(test_array[-1], lindemannindex)
+
+
+@pytest.mark.parametrize(
+    ("trajectory"),
+    [
+        ("tests/test_example/459_01.lammpstrj"),
+        ("tests/test_example/459_02.lammpstrj"),
+    ],
+)
+def test_online_frames_middle(trajectory):
+    """Example test with parametrization."""
+    pipeline, data = read.trajectory(trajectory)
+    test_array = online_frames.calculate(pipeline, data)
+    linde_from_200_trj = online_trj.calculate(pipeline, data, 201)
+    linde_at_200_per_frames = test_array[200]
+    assert np.isclose(linde_at_200_per_frames, linde_from_200_trj)
+
+
 @pytest.mark.parametrize(
     ("trajectory", "lindemannindex"),
     [
@@ -98,3 +150,39 @@ def test_atoms_middle(trajectory):
     lindeman_at_200_frame = np.mean(per_atoms_frame_at_200)
     lindeman_from_200_trj = per_trj.calculate(frame[0:201])
     assert np.isclose(lindeman_at_200_frame, lindeman_from_200_trj)
+
+
+@pytest.mark.parametrize(
+    ("trajectory", "lindemannindex"),
+    [
+        (
+            "tests/test_example/459_01.lammpstrj",
+            0.025923892565654555,
+        ),
+        (
+            "tests/test_example/459_02.lammpstrj",
+            0.026426709832984754,
+        ),
+    ],
+)
+def test_online_atoms(trajectory, lindemannindex):
+    """Example test with parametrization."""
+    pipeline, data = read.trajectory(trajectory)
+    assert np.isclose(np.mean(online_atoms.calculate(pipeline, data)[-1]), lindemannindex)
+
+
+@pytest.mark.parametrize(
+    ("trajectory"),
+    [
+        ("tests/test_example/459_01.lammpstrj"),
+        ("tests/test_example/459_02.lammpstrj"),
+    ],
+)
+def test_online_atoms_middle(trajectory):
+    """Example test with parametrization."""
+    frame = read.frames(trajectory)
+    lindeman_from_200_trj = per_trj.calculate(frame[0:201])
+    pipeline, data = read.trajectory(trajectory)
+    per_atoms_frame_at_200 = online_atoms.calculate(pipeline, data)[200]
+    lindeman_at_200_frame = np.mean(per_atoms_frame_at_200)
+    assert np.isclose(lindeman_at_200_frame, lindeman_from_200_trj)