diff --git a/.python-version b/.python-version
new file mode 100644
index 0000000..bd28b9c
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.9
diff --git a/pyproject.toml b/pyproject.toml
index 6d7b5fe..9aff969 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -85,7 +85,7 @@ select = [
     # "I",   # isort (Ensures imports are sorted properly)
     # "B",   # flake8-bugbear (Detects likely bugs and bad practices)
     # "TID", # flake8-tidy-imports (Checks for banned or misplaced imports)
-    # "UP",  # pyupgrade (Automatically updates old Python syntax)
+    "UP",  # pyupgrade (Automatically updates old Python syntax)
     # "YTT", # flake8-2020 (Detects outdated Python 2/3 compatibility issues)
     # "FLY", # flynt (Converts old-style string formatting to f-strings)
     # "PIE", # flake8-pie
diff --git a/src/lasso/diffcrash/diffcrash_run.py b/src/lasso/diffcrash/diffcrash_run.py
index 9901e64..289bccd 100644
--- a/src/lasso/diffcrash/diffcrash_run.py
+++ b/src/lasso/diffcrash/diffcrash_run.py
@@ -10,7 +10,7 @@
 import time
 import typing
 from concurrent import futures
-from typing import List, Union
+from typing import Union
 from pathlib import Path
 import psutil
 
@@ -688,9 +688,7 @@ def run_import(self, pool: futures.ThreadPoolExecutor):
 
             if n_imports_finished != n_new_imports_finished:
                 # pylint: disable = consider-using-f-string
-                msg = "Running Imports ... [{0}/{1}] - {2:3.2f}%\r".format(
-                    n_new_imports_finished, len(return_code_futures), percentage
-                )
+                msg = f"Running Imports ... [{n_new_imports_finished}/{len(return_code_futures)}] - {percentage:3.2f}%\r"
                 print(str_running(msg), end="", flush=True)
                 self.logger.info(msg)
 
@@ -1091,7 +1089,7 @@ def is_logfile_successful(self, logfile: Path) -> bool:
         success : `bool`
         """
 
-        with open(logfile, "r", encoding="utf-8") as fp:
+        with open(logfile, encoding="utf-8") as fp:
             for line in fp:
                 if "successfully" in line:
                     return True
@@ -1181,7 +1179,7 @@ def clear_project_dir(self):
         # reinit logger
         self.logger = self._setup_logger()
 
-    def read_config_file(self, config_file: str) -> List[str]:
+    def read_config_file(self, config_file: str) -> list[str]:
         """Read a diffcrash config file
 
         Parameters
@@ -1203,7 +1201,7 @@ def read_config_file(self, config_file: str) -> List[str]:
         # pylint: disable = too-many-branches
         # pylint: disable = too-many-statements
 
-        with open(config_file, "r", encoding="utf-8") as conf:
+        with open(config_file, encoding="utf-8") as conf:
             conf_lines = conf.readlines()
         line = 0
 
@@ -1303,7 +1301,7 @@ def read_config_file(self, config_file: str) -> List[str]:
 
         return export_item_list
 
-    def check_if_logfiles_show_success(self, pattern: str) -> List[str]:
+    def check_if_logfiles_show_success(self, pattern: str) -> list[str]:
         """Check if a logfiles with given pattern show success
 
         Parameters
diff --git a/src/lasso/dimred/dimred_run.py b/src/lasso/dimred/dimred_run.py
index d822395..42631d1 100644
--- a/src/lasso/dimred/dimred_run.py
+++ b/src/lasso/dimred/dimred_run.py
@@ -7,7 +7,8 @@
 import sys
 import time
 from concurrent.futures.process import ProcessPoolExecutor
-from typing import Sequence, Tuple, Union
+from typing import Union
+from collections.abc import Sequence
 
 import h5py
 import numpy as np
@@ -529,7 +530,7 @@ def _parse_simulation_and_reference_runs(
         reference_run_pattern: Union[None, str],
         exclude_runs: Sequence[str],
         table: Table,
-    ) -> Tuple[Sequence[str], str, Sequence[str]]:
+    ) -> tuple[Sequence[str], str, Sequence[str]]:
         # pylint: disable = too-many-locals
 
         # search all denoted runs
diff --git a/src/lasso/dimred/hashing.py b/src/lasso/dimred/hashing.py
index 2186a8c..6280ff0 100644
--- a/src/lasso/dimred/hashing.py
+++ b/src/lasso/dimred/hashing.py
@@ -1,7 +1,8 @@
 import multiprocessing
 import os
 import time
-from typing import List, Tuple, Union, Sequence
+from typing import Union
+from collections.abc import Sequence
 
 import h5py
 import numpy as np
@@ -104,8 +105,8 @@ def _compute_mode_similarities(
     hashes2: np.ndarray,
     eigenvectors_sub1: np.ndarray,
     eigenvectors_sub2: np.ndarray,
-    matches: List[Tuple[int, int]],
-) -> List[float]:
+    matches: list[tuple[int, int]],
+) -> list[float]:
     """Compute the mode similarity between different meshes
 
     Parameters
@@ -217,7 +218,7 @@ def _join_hash_comparison_thread_files(
 
 def run_hash_comparison(
     comparison_filepath: str,
-    hashes_filepaths: List[str],
+    hashes_filepaths: list[str],
     n_threads: int = 1,
     print_progress: bool = False,
 ):
@@ -597,7 +598,7 @@ def curve_normalizer(x: np.ndarray, y: np.ndarray):
 
 def compute_hashes(
     eig_vecs: np.ndarray, result_field: np.ndarray, n_points: int = 100, bandwidth: float = 0.05
-) -> List[Tuple[np.ndarray, np.ndarray]]:
+) -> list[tuple[np.ndarray, np.ndarray]]:
     """Compute hashes for a result field
 
     Parameters
diff --git a/src/lasso/dimred/hashing_sphere.py b/src/lasso/dimred/hashing_sphere.py
index 44415cb..e656d15 100644
--- a/src/lasso/dimred/hashing_sphere.py
+++ b/src/lasso/dimred/hashing_sphere.py
@@ -1,5 +1,4 @@
 import os
-import typing
 import warnings
 
 import h5py
@@ -15,7 +14,7 @@
 warnings.simplefilter(action="ignore", category=FutureWarning)
 
 
-def _create_sphere_mesh(diameter: np.ndarray) -> typing.Tuple[np.ndarray, np.ndarray]:
+def _create_sphere_mesh(diameter: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
     """Compute the alpha and beta increments for a
         meshed sphere for binning the projected values
 
@@ -70,7 +69,7 @@ def _create_sphere_mesh(diameter: np.ndarray) -> typing.Tuple[np.ndarray, np.nda
 
 def _project_to_sphere(
     points: np.ndarray, centroid: np.ndarray, axis: str = "Z"
-) -> typing.Tuple[np.ndarray, np.ndarray]:
+) -> tuple[np.ndarray, np.ndarray]:
     """compute the projection vectors of centroid to each point in terms of spherical coordinates
 
     Parameters
diff --git a/src/lasso/dimred/svd/clustering_betas.py b/src/lasso/dimred/svd/clustering_betas.py
index 08938a9..75fd5b3 100644
--- a/src/lasso/dimred/svd/clustering_betas.py
+++ b/src/lasso/dimred/svd/clustering_betas.py
@@ -1,4 +1,5 @@
-from typing import Sequence, Tuple, Union
+from typing import Union
+from collections.abc import Sequence
 
 import numpy as np
 from sklearn.cluster import DBSCAN, OPTICS, KMeans, SpectralClustering
@@ -418,7 +419,7 @@ def document_algorithm(keyword):
 }
 
 
-def create_cluster_arg_dict(args: Sequence[str]) -> Union[Tuple[str, dict], str]:
+def create_cluster_arg_dict(args: Sequence[str]) -> Union[tuple[str, dict], str]:
     """Determines which cluster to use and creates a python dictionary to use as cluster_params
 
     Parameters
@@ -493,7 +494,7 @@ def create_cluster_arg_dict(args: Sequence[str]) -> Union[Tuple[str, dict], str]
     return cluster_type, cluster_arg_dict
 
 
-def create_detector_arg_dict(args: Sequence[str]) -> Union[Tuple[str, dict], str]:
+def create_detector_arg_dict(args: Sequence[str]) -> Union[tuple[str, dict], str]:
     """Determines which detector to use and creates a python dictionary to use as detector_params
 
     Parameters
@@ -579,7 +580,7 @@ def group_betas(
     detector=None,
     cluster_params=None,
     detector_params=None,
-) -> Union[Tuple[list, list], str]:
+) -> Union[tuple[list, list], str]:
     """
     Base function to to group betas into groups, detect outliers. Provides that all different
     clustering and outlier detection algorithms are implemented in an easy to access environment.
diff --git a/src/lasso/dimred/svd/keyword_types.py b/src/lasso/dimred/svd/keyword_types.py
index 0043487..ffaaccf 100644
--- a/src/lasso/dimred/svd/keyword_types.py
+++ b/src/lasso/dimred/svd/keyword_types.py
@@ -1,6 +1,3 @@
-import typing
-
-
 class ClusterType:
     """Specifies names of specific clustering algorithms
 
@@ -22,7 +19,7 @@ class ClusterType:
     SpectralClustering = "SpectralClustering"
 
     @staticmethod
-    def get_cluster_type_name() -> typing.List[str]:
+    def get_cluster_type_name() -> list[str]:
         """Get the name of the clustering algorithms"""
         return [
             ClusterType.OPTICS,
@@ -51,7 +48,7 @@ class DetectorType:
     # Experimental = "Experimental"
 
     @staticmethod
-    def get_detector_type_name() -> typing.List[str]:
+    def get_detector_type_name() -> list[str]:
         """Get the name of the detector algorithms"""
         return [
             DetectorType.IsolationForest,
diff --git a/src/lasso/dimred/svd/plot_beta_clusters.py b/src/lasso/dimred/svd/plot_beta_clusters.py
index 804d63d..fc6adcf 100644
--- a/src/lasso/dimred/svd/plot_beta_clusters.py
+++ b/src/lasso/dimred/svd/plot_beta_clusters.py
@@ -2,7 +2,8 @@
 import re
 import time
 import webbrowser
-from typing import Sequence, Union
+from typing import Union
+from collections.abc import Sequence
 
 import numpy as np
 
@@ -118,11 +119,13 @@ def plot_clusters_js(
         id_nr.append(id_group)
 
     # pylint: disable = consider-using-f-string
-    _three_min_ = '<script type="text/javascript">%s</script>' % _read_file(
-        os.path.join(
-            # move path to "~/lasso/"
-            os.path.split(os.path.split(os.path.dirname(__file__))[0])[0],
-            "plotting/resources/three_latest.min.js",
+    _three_min_ = '<script type="text/javascript">{}</script>'.format(
+        _read_file(
+            os.path.join(
+                # move path to "~/lasso/"
+                os.path.split(os.path.split(os.path.dirname(__file__))[0])[0],
+                "plotting/resources/three_latest.min.js",
+            )
         )
     )
 
@@ -136,10 +139,10 @@ def plot_clusters_js(
             name = "outliers"
             color = "black"
         else:
-            name = "cluster {i}".format(i=index)
+            name = f"cluster {index}"
             color = colorlist[(index - 1) % 10]
         formatted_trace = TRACE_STRING.format(
-            _traceNr_="trace{i}".format(i=index),
+            _traceNr_=f"trace{index}",
             _name_=name,
             _color_=color,
             _runIDs_=id_cluster[index].tolist(),
diff --git a/src/lasso/dimred/svd/pod_functions.py b/src/lasso/dimred/svd/pod_functions.py
index d367539..3c233c2 100644
--- a/src/lasso/dimred/svd/pod_functions.py
+++ b/src/lasso/dimred/svd/pod_functions.py
@@ -1,4 +1,4 @@
-from typing import Tuple, Union
+from typing import Union
 
 import numpy as np
 from scipy.sparse import csc_matrix
@@ -37,7 +37,7 @@ def calculate_v_and_betas(
     stacked_sub_displ: np.ndarray,
     progress_bar: Union[None, Progress, PlaceHolderBar] = None,
     task_id: Union[None, TaskID] = None,
-) -> Union[str, Tuple[np.ndarray, np.ndarray]]:
+) -> Union[str, tuple[np.ndarray, np.ndarray]]:
     """
     Calculates the right reduced order Basis V and up to 10 eigenvalues of the subsamples
 
diff --git a/src/lasso/dimred/svd/subsampling_methods.py b/src/lasso/dimred/svd/subsampling_methods.py
index 5a26611..eda14ed 100644
--- a/src/lasso/dimred/svd/subsampling_methods.py
+++ b/src/lasso/dimred/svd/subsampling_methods.py
@@ -1,7 +1,8 @@
 import os
 import random
 import time
-from typing import List, Sequence, Tuple, Union
+from typing import Union
+from collections.abc import Sequence
 
 import numpy as np
 from sklearn.neighbors import NearestNeighbors
@@ -45,7 +46,7 @@ def _mark_dead_eles(node_indexes: np.ndarray, alive_shells: np.ndarray) -> np.nd
 
 def _extract_shell_parts(
     part_list: Sequence[int], d3plot: D3plot
-) -> Union[Tuple[np.ndarray, np.ndarray], str]:
+) -> Union[tuple[np.ndarray, np.ndarray], str]:
     """
     Extracts a shell part defined by its part ID out of the given d3plot.
     Returns a new node index, relevant coordinates and displacement
@@ -123,7 +124,7 @@ def _extract_shell_parts(
                 return err_msg.format(part)
 
         def mask_parts(
-            part_list2: List[int], element_part_index: np.ndarray, element_node_index: np.ndarray
+            part_list2: list[int], element_part_index: np.ndarray, element_node_index: np.ndarray
         ) -> np.ndarray:
             element_part_filter = np.full(element_part_index.shape, False)
             proc_parts = []
@@ -180,7 +181,7 @@ def mask_parts(
 
 def create_reference_subsample(
     load_path: str, parts: Sequence[int], nr_samples=2000
-) -> Union[Tuple[np.ndarray, float, float], str]:
+) -> Union[tuple[np.ndarray, float, float], str]:
     """
     Loads the D3plot at load_path, extracts the node coordinates of part 13, returns
     a random subsample of these nodes
@@ -238,7 +239,7 @@ def create_reference_subsample(
 
 def remap_random_subsample(
     load_path: str, parts: list, reference_subsample: np.ndarray
-) -> Union[Tuple[np.ndarray, float, float], str]:
+) -> Union[tuple[np.ndarray, float, float], str]:
     """
     Remaps the specified sample onto a new mesh provided by reference subsampl, using knn matching
 
diff --git a/src/lasso/dyna/array_type.py b/src/lasso/dyna/array_type.py
index 27fecf0..7c5c535 100644
--- a/src/lasso/dyna/array_type.py
+++ b/src/lasso/dyna/array_type.py
@@ -1,6 +1,3 @@
-import typing
-
-
 class ArrayType:
     """Specifies the names for specific arrays
 
@@ -494,7 +491,7 @@ class ArrayType:
     rigid_wall_position = "rigid_wall_position"
 
     @staticmethod
-    def get_state_array_names() -> typing.List[str]:
+    def get_state_array_names() -> list[str]:
         """Get the names of all state arrays
 
         Returns:
diff --git a/src/lasso/dyna/binout.py b/src/lasso/dyna/binout.py
index be03569..d85b983 100644
--- a/src/lasso/dyna/binout.py
+++ b/src/lasso/dyna/binout.py
@@ -1,5 +1,5 @@
 import glob
-from typing import List, Union
+from typing import Union
 
 import h5py
 import numpy as np
@@ -67,13 +67,13 @@ def __init__(self, filepath: str):
 
         # check file existence
         if not self.filelist:
-            raise IOError("No file was found.")
+            raise OSError("No file was found.")
 
         # open lsda buffer
         self.lsda = Lsda(self.filelist, "r")
         self.lsda_root = self.lsda.root
 
-    def read(self, *path) -> Union[List[str], str, np.ndarray]:
+    def read(self, *path) -> Union[list[str], str, np.ndarray]:
         """Read all data from Binout (top to low level)
 
         Parameters
diff --git a/src/lasso/dyna/d3plot.py b/src/lasso/dyna/d3plot.py
index cc59317..4eb67d2 100644
--- a/src/lasso/dyna/d3plot.py
+++ b/src/lasso/dyna/d3plot.py
@@ -10,7 +10,8 @@
 import traceback
 import typing
 import webbrowser
-from typing import Any, BinaryIO, Dict, Iterable, List, Set, Tuple, Union
+from typing import Any, BinaryIO, Union
+from collections.abc import Iterable
 
 import numpy as np
 
@@ -30,7 +31,7 @@
 LOGGER = get_logger(__name__)
 
 
-def _check_ndim(d3plot, array_dim_names: Dict[str, List[str]]):
+def _check_ndim(d3plot, array_dim_names: dict[str, list[str]]):
     """Checks if the specified array is fine in terms of ndim
 
     Parameters
@@ -50,7 +51,7 @@ def _check_ndim(d3plot, array_dim_names: Dict[str, List[str]]):
 
 
 def _check_array_occurrence(
-    d3plot, array_names: List[str], required_array_names: List[str]
+    d3plot, array_names: list[str], required_array_names: list[str]
 ) -> bool:
     """Check if an array exists, if all depending on it exist too
 
@@ -80,7 +81,7 @@ def _check_array_occurrence(
     return False
 
 
-def _negative_to_positive_state_indexes(indexes: Set[int], n_entries) -> Set[int]:
+def _negative_to_positive_state_indexes(indexes: set[int], n_entries) -> set[int]:
     """Convert negative indexes of an iterable to positive ones
 
     Parameters
@@ -96,7 +97,7 @@ def _negative_to_positive_state_indexes(indexes: Set[int], n_entries) -> Set[int
         the positive indexes
     """
 
-    new_entries: Set[int] = set()
+    new_entries: set[int] = set()
     for _, index in enumerate(indexes):
         new_index = index + n_entries if index < 0 else index
         if new_index >= n_entries:
@@ -1171,8 +1172,8 @@ def pack(self, value: Any, size=None, dtype_hint=None) -> bytes:
         raise RuntimeError(msg, type(value), value)
 
     def count_array_state_var(
-        self, array_type: str, dimension_names: List[str], has_layers: bool, n_layers: int = 0
-    ) -> Tuple[int, int]:
+        self, array_type: str, dimension_names: list[str], has_layers: bool, n_layers: int = 0
+    ) -> tuple[int, int]:
         """This functions checks and updates the variable count for certain types of arrays
 
         Parameters
@@ -1432,8 +1433,8 @@ def __init__(
         filepath: str = None,
         use_femzip: Union[bool, None] = None,
         n_files_to_load_at_once: Union[int, None] = None,
-        state_array_filter: Union[List[str], None] = None,
-        state_filter: Union[None, Set[int]] = None,
+        state_array_filter: Union[list[str], None] = None,
+        state_filter: Union[None, set[int]] = None,
         buffered_reading: bool = False,
     ):
         """Constructor for a D3plot
@@ -1744,7 +1745,7 @@ def _n_rigid_walls(self) -> int:
 
     # pylint: disable = unused-argument, too-many-locals
     def _read_d3plot_file_generator(
-        self, buffered_reading: bool, state_filter: Union[None, Set[int]]
+        self, buffered_reading: bool, state_filter: Union[None, set[int]]
     ) -> typing.Any:
         """Generator function for reading bare d3plot files
 
@@ -1782,7 +1783,7 @@ def _read_d3plot_file_generator(
         # if using buffered reading, we load one state at a time
         # into memory
         if buffered_reading:
-            file_infos_tmp: List[MemoryInfo] = []
+            file_infos_tmp: list[MemoryInfo] = []
             n_previous_states = 0
             for minfo in file_infos:
                 for i_file_state in range(minfo.n_states):
@@ -1818,7 +1819,7 @@ def _read_d3plot_file_generator(
             yield buffer, n_states
 
     def _read_femzip_file_generator(
-        self, buffered_reading: bool, state_filter: Union[None, Set[int]]
+        self, buffered_reading: bool, state_filter: Union[None, set[int]]
     ) -> typing.Any:
         """Generator function for reading femzipped d3plot files
 
@@ -1845,7 +1846,7 @@ def _read_femzip_file_generator(
         n_timesteps: int = buffer_info.n_timesteps
 
         # convert negative filter indexes
-        state_filter_parsed: Set[int] = set()
+        state_filter_parsed: set[int] = set()
         if state_filter is not None:
             state_filter_parsed = _negative_to_positive_state_indexes(state_filter, n_timesteps)
             n_states_to_load = len(state_filter)
@@ -2657,7 +2658,7 @@ def _read_rigid_body_description(self):
         info = self._rigid_body_info
         info.n_rigid_bodies = rigid_body_description_header["nrigid"]
 
-        rigid_bodies: List[RigidBodyMetadata] = []
+        rigid_bodies: list[RigidBodyMetadata] = []
         for _ in range(info.n_rigid_bodies):
             rigid_body_info = {
                 # rigid body part internal number
@@ -5661,7 +5662,7 @@ def _read_states_rigid_body_motion(
 
         return var_index
 
-    def _collect_file_infos(self, size_per_state: int) -> List[MemoryInfo]:
+    def _collect_file_infos(self, size_per_state: int) -> list[MemoryInfo]:
         """This routine collects the memory and file info for the d3plot files
 
         Parameters
@@ -5828,8 +5829,8 @@ def _collect_file_infos(self, size_per_state: int) -> List[MemoryInfo]:
 
     @staticmethod
     def _read_file_from_memory_info(
-        memory_infos: Union[MemoryInfo, List[MemoryInfo]],
-    ) -> Tuple[BinaryBuffer, int]:
+        memory_infos: Union[MemoryInfo, list[MemoryInfo]],
+    ) -> tuple[BinaryBuffer, int]:
         """Read files from a single or multiple memory infos
 
         Parameters
@@ -6051,7 +6052,7 @@ def plot(
         i_timestep: int = 0,
         field: Union[np.ndarray, None] = None,
         is_element_field: bool = True,
-        fringe_limits: Union[Tuple[float, float], None] = None,
+        fringe_limits: Union[tuple[float, float], None] = None,
         export_filepath: str = "",
     ):
         """Plot the d3plot geometry
@@ -7604,7 +7605,7 @@ def _write_states_globals(
 
         n_parts = settings.header["nmmat"]
 
-        def _write_part_field(array_type: str, default_shape: Union[int, Tuple], dtype: np.dtype):
+        def _write_part_field(array_type: str, default_shape: Union[int, tuple], dtype: np.dtype):
             array = (
                 self.arrays[array_type][i_timestep]
                 if array_type in self.arrays
@@ -9354,7 +9355,7 @@ def _write_states_rigid_bodies(
         return n_bytes_written
 
     def check_array_dims(
-        self, array_dimensions: Dict[str, int], dimension_name: str, dimension_size: int = -1
+        self, array_dimensions: dict[str, int], dimension_name: str, dimension_size: int = -1
     ):
         """This function checks if multiple arrays share an array dimensions
         with the same size.
diff --git a/src/lasso/dyna/d3plot_header.py b/src/lasso/dyna/d3plot_header.py
index 6b62414..b7334bc 100644
--- a/src/lasso/dyna/d3plot_header.py
+++ b/src/lasso/dyna/d3plot_header.py
@@ -1,5 +1,5 @@
 import enum
-from typing import Any, Dict, Tuple, Union
+from typing import Any, Union
 
 import numpy as np
 import rich
@@ -317,7 +317,7 @@ class D3plotHeader:
     itype: np.dtype = np.int32
     ftype: np.dtype = np.float32
     wordsize: int = 4
-    raw_header: Dict[str, Any] = {}
+    raw_header: dict[str, Any] = {}
     external_numbers_dtype = np.int32
     n_header_bytes: int = 0
 
@@ -345,7 +345,7 @@ class D3plotHeader:
     quadratic_elems_has_full_connectivity: bool = False
     quadratic_elems_has_data_at_integration_points: bool = False
     n_post_branches: int = 0
-    n_types: Tuple[int, ...] = ()
+    n_types: tuple[int, ...] = ()
 
     # parts
     n_parts: int = 0
@@ -1099,7 +1099,7 @@ def read_words(self, bb: BinaryBuffer, words_to_read: dict, storage_dict: dict =
     @staticmethod
     def _determine_file_settings(
         bb: Union[BinaryBuffer, None] = None,
-    ) -> Tuple[int, Union[np.int32, np.int64], Union[np.float32, np.float64]]:
+    ) -> tuple[int, Union[np.int32, np.int64], Union[np.float32, np.float64]]:
         """Determine the precision of the file
 
         Parameters
@@ -1169,7 +1169,7 @@ def _determine_file_settings(
 
         return word_size, itype, ftype
 
-    def compare(self, other: "D3plotHeader") -> Dict[str, Tuple[Any, Any]]:
+    def compare(self, other: "D3plotHeader") -> dict[str, tuple[Any, Any]]:
         """Compare two headers and get the differences
 
         Parameters
diff --git a/src/lasso/dyna/femzip_mapper.py b/src/lasso/dyna/femzip_mapper.py
index 2324385..0e858d8 100644
--- a/src/lasso/dyna/femzip_mapper.py
+++ b/src/lasso/dyna/femzip_mapper.py
@@ -1,7 +1,7 @@
 import logging
 import re
 import traceback
-from typing import Dict, List, Set, Tuple, Union
+from typing import Union
 
 import numpy as np
 from lasso.dyna.array_type import ArrayType
@@ -9,8 +9,8 @@
 from lasso.femzip.fz_config import FemzipArrayType, FemzipVariableCategory, get_last_int_of_line
 
 
-TRANSL_FEMZIP_ARRATYPE_TO_D3PLOT_ARRAYTYPE: Dict[
-    Tuple[FemzipArrayType, FemzipVariableCategory], Set[str]
+TRANSL_FEMZIP_ARRATYPE_TO_D3PLOT_ARRAYTYPE: dict[
+    tuple[FemzipArrayType, FemzipVariableCategory], set[str]
 ] = {
     # GLOBAL
     (FemzipArrayType.GLOBAL_DATA, FemzipVariableCategory.GLOBAL): {
@@ -336,8 +336,8 @@
 
 
 def femzip_to_d3plot(
-    result_arrays: Dict[Tuple[int, str, FemzipVariableCategory], np.ndarray],
-) -> Dict[str, np.ndarray]:
+    result_arrays: dict[tuple[int, str, FemzipVariableCategory], np.ndarray],
+) -> dict[str, np.ndarray]:
     """Map femzip arrays to d3plot arrays
 
     Parameters
@@ -407,7 +407,7 @@ def set_n_timesteps(self, n_timesteps: Union[int, None]) -> None:
         """
         self._set_attr("n_timesteps", n_timesteps)
 
-    def to_shape(self) -> Tuple[int, ...]:
+    def to_shape(self) -> tuple[int, ...]:
         """Set the number of variables
 
         Returns
@@ -437,14 +437,14 @@ class D3plotArrayMapping:
 
     just_assign: bool = False
 
-    def to_slice(self) -> Tuple[Union[int, slice], ...]:
+    def to_slice(self) -> tuple[Union[int, slice], ...]:
         """Get the slices mapping a femzip array to a d3plot array
 
         Returns
         -------
         slices: Tuple[Union[int, slice], ...]
         """
-        slices: List[Union[slice, int]] = [slice(None), slice(None)]
+        slices: list[Union[slice, int]] = [slice(None), slice(None)]
         if self.d3_layer_slice is not None:
             slices.append(self.d3_layer_slice)
         if self.d3_var_slice is not None:
@@ -467,7 +467,7 @@ class FemzipArrayInfo:
     i_layer: Union[int, None] = None
     i_var: Union[int, None] = None
 
-    mappings: List[D3plotArrayMapping]
+    mappings: list[D3plotArrayMapping]
 
     def __init__(self):
         self.mappings = []
@@ -491,13 +491,13 @@ class FemzipMapper:
 
     FORTRAN_OFFSET: int = 1
 
-    _d3plot_arrays: Dict[str, np.ndarray] = {}
+    _d3plot_arrays: dict[str, np.ndarray] = {}
     _fz_array_slices = {}
 
     def __init__(self):
         pass
 
-    def map(self, result_arrays: Dict[Tuple[int, str, FemzipVariableCategory], np.ndarray]):
+    def map(self, result_arrays: dict[tuple[int, str, FemzipVariableCategory], np.ndarray]):
         """Map femzip data to d3plot arrays.
 
         Parameters
@@ -521,8 +521,8 @@ def map(self, result_arrays: Dict[Tuple[int, str, FemzipVariableCategory], np.nd
         self._map_arrays(array_infos, self._d3plot_arrays)
 
     def _convert(
-        self, result_arrays: Dict[Tuple[int, str, FemzipVariableCategory], np.ndarray]
-    ) -> List[FemzipArrayInfo]:
+        self, result_arrays: dict[tuple[int, str, FemzipVariableCategory], np.ndarray]
+    ) -> list[FemzipArrayInfo]:
         """Convert femzip result arrays into array infos
 
         Parameters
@@ -558,7 +558,7 @@ def _convert(
         return array_infos
 
     @staticmethod
-    def _build(fz_arrays: List[FemzipArrayInfo]) -> Dict[str, Tuple[int, ...]]:
+    def _build(fz_arrays: list[FemzipArrayInfo]) -> dict[str, tuple[int, ...]]:
         """Counts the occurrence of all variables in the result array such as the
         number of layers and stresses.
 
@@ -577,8 +577,8 @@ def _build(fz_arrays: List[FemzipArrayInfo]) -> Dict[str, Tuple[int, ...]]:
         Some variables only have partial stress results written for Sigma-x and Sigma-y
         and layers one to three for example.
         """
-        shape_infos: Dict[str, ArrayShapeInfo] = {}
-        name_count: Dict[Tuple[str, FemzipVariableCategory], int] = {}
+        shape_infos: dict[str, ArrayShapeInfo] = {}
+        name_count: dict[tuple[str, FemzipVariableCategory], int] = {}
 
         for arr_info in fz_arrays:
             # print(arr_info)
@@ -661,7 +661,7 @@ def _build(fz_arrays: List[FemzipArrayInfo]) -> Dict[str, Tuple[int, ...]]:
 
         return {name: info.to_shape() for name, info in shape_infos.items()}
 
-    def _map_arrays(self, array_infos: List[FemzipArrayInfo], d3plot_arrays: Dict[str, np.ndarray]):
+    def _map_arrays(self, array_infos: list[FemzipArrayInfo], d3plot_arrays: dict[str, np.ndarray]):
         """Allocate a femzip variable to its correct position in
         the d3plot array dictionary.
 
@@ -699,8 +699,8 @@ def _map_arrays(self, array_infos: List[FemzipArrayInfo], d3plot_arrays: Dict[st
                     d3plot_array[slices] = arr_info.array
 
     def _allocate_d3plot_arrays(
-        self, array_shapes: Dict[str, Tuple[int, ...]]
-    ) -> Dict[str, np.ndarray]:
+        self, array_shapes: dict[str, tuple[int, ...]]
+    ) -> dict[str, np.ndarray]:
         """Initialize all the d3plot arrays.
 
         Parameters
@@ -725,7 +725,7 @@ def d3plot_arrays(self):
 
     def _parse_femzip_name(
         self, fz_name: str, var_type: FemzipVariableCategory
-    ) -> Tuple[str, Union[int, None], Union[int, None], Union[int, None]]:
+    ) -> tuple[str, Union[int, None], Union[int, None], Union[int, None]]:
         """Parses the femzip variable names.
 
         Parameters
@@ -789,7 +789,7 @@ def _parse_femzip_name(
 
 
 def filter_femzip_variables(
-    file_metadata: FemzipFileMetadata, d3plot_array_filter: Union[Set[str], None]
+    file_metadata: FemzipFileMetadata, d3plot_array_filter: Union[set[str], None]
 ) -> FemzipFileMetadata:
     """Filters variable infos regarding d3plot array types
 
@@ -809,7 +809,7 @@ def filter_femzip_variables(
     # pylint: disable = too-many-locals
 
     # find out which arrays we need and
-    vars_to_copy: List[int] = []
+    vars_to_copy: list[int] = []
 
     for i_var in range(file_metadata.number_of_variables):
         try:
diff --git a/src/lasso/dyna/lsda_py3.py b/src/lasso/dyna/lsda_py3.py
index 9870172..3f2b811 100644
--- a/src/lasso/dyna/lsda_py3.py
+++ b/src/lasso/dyna/lsda_py3.py
@@ -140,7 +140,7 @@ def writedata(self, sym, data):
         self.fp.write(struct.pack(self.lcunpack, length, Lsda.DATA))
         self.fp.write(struct.pack("bb", sym.type, nlen) + bytes(sym.name, "utf-8"))
         #    fmt=self.ordercode+self.packtype[sym.type]*sym.length
-        fmt = "%c%d%c" % (self.ordercode, sym.length, self.packtype[sym.type])
+        fmt = f"{self.ordercode}{sym.length}{self.packtype[sym.type]}"
         self.fp.write(struct.pack(fmt, *data))
         sym.file = self
 
@@ -239,7 +239,7 @@ def lread(self, start=0, end=2000000000):
         self.file.ateof = 0
         #    format = self.file.ordercode + _Diskfile.packtype[self.type]*(end-start)
         #    return struct.unpack(format,self.file.fp.read(size*(end-start)))
-        format = "%c%d%c" % (self.file.ordercode, (end - start), _Diskfile.packtype[self.type])
+        format = f"{(self.file.ordercode)}{end - start}{_Diskfile.packtype[self.type]}"
         if self.type == Lsda.LINK:
             return struct.unpack(format, self.file.fp.read(size * (end - start)))[0]
         else:
@@ -622,7 +622,7 @@ def nextfile(self):  # Open next file in sequence
             newname = parts[0] + "%001"
         else:
             ret = int(parts[1]) + 1
-            newname = "%s%%%3.3d" % (parts[0], ret)
+            newname = f"{parts[0]}%{ret:03d}"
         if self.mode == "w":
             self.fw = _Diskfile(newname, "w")
         else:
@@ -639,5 +639,5 @@ def nextfile(self):  # Open next file in sequence
     s = struct.pack(a, x)
     if len(s) != b:
         print("LSDA: initialization error")
-        print("Data type %s has length %d instead of %d" % (a, len(s), b))
+        print(f"Data type {a} has length {len(s)} instead of {b}")
         types_ok = 0
diff --git a/src/lasso/femzip/femzip_api.py b/src/lasso/femzip/femzip_api.py
index f955826..1b3a2d9 100644
--- a/src/lasso/femzip/femzip_api.py
+++ b/src/lasso/femzip/femzip_api.py
@@ -18,7 +18,7 @@
     c_uint64,
     sizeof,
 )
-from typing import Any, Dict, List, Set, Tuple, Union
+from typing import Any, Union
 
 import numpy as np
 
@@ -355,7 +355,7 @@ def api(self) -> CDLL:
         return self._api
 
     @staticmethod
-    def _parse_state_filter(state_filter: Union[Set[int], None], n_timesteps: int) -> Set[int]:
+    def _parse_state_filter(state_filter: Union[set[int], None], n_timesteps: int) -> set[int]:
         # convert negative indexes
         state_filter_parsed = (
             {entry if entry >= 0 else entry + n_timesteps for entry in state_filter}
@@ -394,7 +394,7 @@ def _check_femzip_error(err: FemzipError) -> None:
             raise FemzipException(err_msg.format(err.ier, fz_error_msg))
 
     @staticmethod
-    def struct_to_dict(struct: Structure) -> Dict[str, Any]:
+    def struct_to_dict(struct: Structure) -> dict[str, Any]:
         """Converts a ctypes struct into a dict
 
         Parameters
@@ -481,7 +481,7 @@ def get_part_titles(
         return memoryview(buffer).cast("B")
 
     def read_state_deletion_info(
-        self, buffer_info: FemzipBufferInfo, state_filter: Union[Set[int], None] = None
+        self, buffer_info: FemzipBufferInfo, state_filter: Union[set[int], None] = None
     ) -> np.ndarray:
         """Get information which elements are alive
 
@@ -824,7 +824,7 @@ def read_states(
         self,
         filepath: str,
         buffer_info: Union[FemzipBufferInfo, None] = None,
-        state_filter: Union[Set[int], None] = None,
+        state_filter: Union[set[int], None] = None,
     ) -> np.ndarray:
         """Reads all femzip state information
 
@@ -945,7 +945,7 @@ def _get_variables_state_buffer_size(
         # pylint: disable=too-many-statements
 
         buffer_size_state = 0
-        var_indexes_to_remove: Set[int] = set()
+        var_indexes_to_remove: set[int] = set()
         for i_var in range(file_metadata.number_of_variables):
             var_info = file_metadata.variable_infos[i_var]
             variable_name = var_info.name.decode("utf-8")
@@ -1023,14 +1023,14 @@ def _decompose_read_variables_array(
         all_vars_array: np.ndarray,
         n_timesteps_read: int,
         file_metadata: FemzipFileMetadata,
-    ) -> Dict[Tuple[int, str, FemzipVariableCategory], np.ndarray]:
+    ) -> dict[tuple[int, str, FemzipVariableCategory], np.ndarray]:
         # pylint: disable=too-many-arguments
         # pylint: disable=too-many-locals
         # pylint: disable=too-many-branches
         # pylint: disable=too-many-statements
 
         # decompose array
-        result_arrays: Dict[Tuple[int, str, FemzipVariableCategory], np.ndarray] = {}
+        result_arrays: dict[tuple[int, str, FemzipVariableCategory], np.ndarray] = {}
         var_pos = 0
         for i_var in range(file_metadata.number_of_variables):
             var_info: VariableInfo = file_metadata.variable_infos[i_var]
@@ -1139,8 +1139,8 @@ def read_variables(
         n_rigid_wall_vars: int,
         n_airbag_particles: int,
         n_airbags: int,
-        state_filter: Union[Set[int], None] = None,
-    ) -> Dict[Tuple[int, str, FemzipVariableCategory], np.ndarray]:
+        state_filter: Union[set[int], None] = None,
+    ) -> dict[tuple[int, str, FemzipVariableCategory], np.ndarray]:
         """Read specific variables from Femzip
 
         Parameters
@@ -1291,12 +1291,12 @@ class FemzipD3plotArrayMapping:
     i_integration_point: Union[int, None]
     i_var_index: Union[int, None]
 
-    fz_array_slices = Tuple[slice]
+    fz_array_slices = tuple[slice]
 
     def __init__(
         self,
         d3plot_array_type: str,
-        fz_array_slices: Tuple[slice] = (slice(None),),
+        fz_array_slices: tuple[slice] = (slice(None),),
         i_integration_point: Union[int, None] = None,
         i_var_index: Union[int, None] = None,
     ):
@@ -1311,7 +1311,7 @@ class FemzipArrayMetadata:
 
     array_type: FemzipArrayType
     category: FemzipVariableCategory
-    d3plot_mappings: List[FemzipD3plotArrayMapping]
+    d3plot_mappings: list[FemzipD3plotArrayMapping]
     # set when parsed
     fz_var_index: Union[int, None] = None
 
@@ -1319,7 +1319,7 @@ def __init__(
         self,
         array_type: FemzipArrayType,
         category: FemzipVariableCategory,
-        d3plot_mappings: List[FemzipD3plotArrayMapping],
+        d3plot_mappings: list[FemzipD3plotArrayMapping],
     ):
         self.array_type = array_type
         self.category = category
diff --git a/src/lasso/femzip/fz_config.py b/src/lasso/femzip/fz_config.py
index 7dd9a71..d565e11 100644
--- a/src/lasso/femzip/fz_config.py
+++ b/src/lasso/femzip/fz_config.py
@@ -1,9 +1,9 @@
-from typing import Dict, Union, Tuple
+from typing import Union
 
 import enum
 
 
-def get_last_int_of_line(line: str) -> Tuple[str, Union[None, int]]:
+def get_last_int_of_line(line: str) -> tuple[str, Union[None, int]]:
     """Searches an integer in the line
 
     Parameters
@@ -117,7 +117,7 @@ def from_int(number: int) -> "FemzipVariableCategory":
         return FEMZIP_CATEGORY_TRANSL_DICT[number]
 
 
-FEMZIP_CATEGORY_TRANSL_DICT: Dict[int, FemzipVariableCategory] = {
+FEMZIP_CATEGORY_TRANSL_DICT: dict[int, FemzipVariableCategory] = {
     entry.value: entry for entry in FemzipVariableCategory.__members__.values()
 }
 
diff --git a/src/lasso/io/binary_buffer.py b/src/lasso/io/binary_buffer.py
index 9dfa185..1a12d68 100644
--- a/src/lasso/io/binary_buffer.py
+++ b/src/lasso/io/binary_buffer.py
@@ -1,6 +1,6 @@
 import mmap
 import os
-from typing import Any, List, Union
+from typing import Any, Union
 
 import numpy as np
 
@@ -225,7 +225,7 @@ def save(self, filepath: Union[str, None] = None):
 
         self.filepath_ = filepath_parsed
 
-    def load(self, filepath: Union[List[str], str, None] = None, n_bytes: int = 0):
+    def load(self, filepath: Union[list[str], str, None] = None, n_bytes: int = 0):
         """load a file
 
         Parameters
diff --git a/src/lasso/io/files.py b/src/lasso/io/files.py
index 1e112dd..3773848 100644
--- a/src/lasso/io/files.py
+++ b/src/lasso/io/files.py
@@ -2,7 +2,8 @@
 import glob
 import os
 import typing
-from typing import Iterator, List, Union
+from typing import Union
+from collections.abc import Iterator
 
 
 @contextlib.contextmanager
@@ -37,7 +38,7 @@ def open_file_or_filepath(
 
 
 def collect_files(
-    dirpath: Union[str, List[str]], patterns: Union[str, List[str]], recursive: bool = False
+    dirpath: Union[str, list[str]], patterns: Union[str, list[str]], recursive: bool = False
 ):
     """Collect files from directories
 
diff --git a/src/lasso/plotting/plot_shell_mesh.py b/src/lasso/plotting/plot_shell_mesh.py
index 66ec7a7..3f71fe5 100644
--- a/src/lasso/plotting/plot_shell_mesh.py
+++ b/src/lasso/plotting/plot_shell_mesh.py
@@ -4,7 +4,7 @@
 import json
 from base64 import b64encode
 from zipfile import ZipFile, ZIP_DEFLATED
-from typing import Union, Tuple
+from typing import Union
 import numpy as np
 
 
@@ -21,7 +21,7 @@ def _read_file(filepath: str):
     file_content : str
     """
 
-    with open(filepath, "r", encoding="utf-8") as fp_filepath:
+    with open(filepath, encoding="utf-8") as fp_filepath:
         return fp_filepath.read()
 
 
@@ -30,7 +30,7 @@ def plot_shell_mesh(
     shell_node_indexes: np.ndarray,
     field: Union[np.ndarray, None] = None,
     is_element_field: bool = True,
-    fringe_limits: Union[Tuple[float, float], None] = None,
+    fringe_limits: Union[tuple[float, float], None] = None,
 ):
     """Plot a mesh
 
@@ -190,23 +190,17 @@ def plot_shell_mesh(
     _html_jquery_js = script_string_js.format(jszip_jquery_format)
 
     # pylint: disable = consider-using-f-string
-    return """
+    return f"""
 <!DOCTYPE html>
 <html lang="en">
     <head>
     <meta charset="utf-8" />
-        {_jquery_js}
-        {_jszip_js}
-        {_three_js}
-        {_chroma_js}
+        {_html_jquery_js}
+        {_html_jszip_js}
+        {_html_three_js}
+        {_html_chroma_js}
     </head>
     <body>
         {_html_div}
     </body>
-</html>""".format(
-        _html_div=_html_div,
-        _jszip_js=_html_jszip_js,
-        _three_js=_html_three_js,
-        _chroma_js=_html_chroma_js,
-        _jquery_js=_html_jquery_js,
-    )
+</html>"""
diff --git a/test/unit_tests/dimred/svd/test_clustering_betas.py b/test/unit_tests/dimred/svd/test_clustering_betas.py
index 850fd87..32de5bc 100644
--- a/test/unit_tests/dimred/svd/test_clustering_betas.py
+++ b/test/unit_tests/dimred/svd/test_clustering_betas.py
@@ -9,7 +9,7 @@ def test_group_betas(self):
         """tests correct function of the group_betas function
         in clustering_betas.py"""
 
-        fake_names = np.array(["betas_{i}".format(i=i) for i in range(25)])
+        fake_names = np.array([f"betas_{i}" for i in range(25)])
         fake_cluster_0 = np.random.rand(12, 3) + 5
         fake_cluster_1 = np.random.rand(12, 3) - 5
         fake_betas = np.stack([*fake_cluster_0, *fake_cluster_1, np.array([0, 0, 0])])
diff --git a/test/unit_tests/dimred/svd/test_pod_functions.py b/test/unit_tests/dimred/svd/test_pod_functions.py
index 5f0a544..d0e7779 100644
--- a/test/unit_tests/dimred/svd/test_pod_functions.py
+++ b/test/unit_tests/dimred/svd/test_pod_functions.py
@@ -1,7 +1,6 @@
 from unittest import TestCase
 from lasso.dimred.svd.pod_functions import calculate_v_and_betas
 import numpy as np
-from typing import Tuple
 
 
 class PodFunctionsTest(TestCase):
@@ -25,7 +24,7 @@ def test_calculate_v_and_betas(self):
         result = calculate_v_and_betas(rand_samples)
 
         # returns Tuple containing v_rob and betas
-        self.assertTrue(isinstance(result, Tuple))
+        self.assertTrue(isinstance(result, tuple))
 
         v_rob, betas = result
 
diff --git a/test/unit_tests/dimred/svd/test_subsampling_methods.py b/test/unit_tests/dimred/svd/test_subsampling_methods.py
index cf100ee..9307925 100644
--- a/test/unit_tests/dimred/svd/test_subsampling_methods.py
+++ b/test/unit_tests/dimred/svd/test_subsampling_methods.py
@@ -1,6 +1,5 @@
 import os
 import tempfile
-from typing import Tuple
 from unittest import TestCase
 
 import numpy as np
@@ -21,7 +20,7 @@ def test_create_reference_sample(self):
             result = create_reference_subsample(load_path, parts=[], nr_samples=n_nodes)
 
             # result should be tuple containing subsample, load time and total process time
-            self.assertTrue(isinstance(result, Tuple))
+            self.assertTrue(isinstance(result, tuple))
 
             ref_sample, t_total, t_load = result
 
@@ -68,7 +67,7 @@ def test_remap_random_subsample(self):
 
             # sub_result should be Tuple containing subsample, total process time,
             # and plot load time
-            self.assertTrue(isinstance(sub_result, Tuple))
+            self.assertTrue(isinstance(sub_result, tuple))
 
             subsample, t_total, t_load = sub_result
 
diff --git a/test/unit_tests/dyna/test_d3plot.py b/test/unit_tests/dyna/test_d3plot.py
index 2cd1ba9..713965b 100644
--- a/test/unit_tests/dyna/test_d3plot.py
+++ b/test/unit_tests/dyna/test_d3plot.py
@@ -154,7 +154,7 @@ def test_header(self):
         header = d3plot.header
 
         for name, value in test_header_data.items():
-            self.assertEqual(header.raw_header[name], value, "Invalid var %s" % name)
+            self.assertEqual(header.raw_header[name], value, f"Invalid var {name}")
 
     def test_beam_integration_points(self):
         self.maxDiff = None
@@ -173,14 +173,10 @@ def test_beam_integration_points(self):
         for array_name, minmax in maxmin_test_values.items():
             array = d3plot.arrays[array_name]
             self.assertAlmostEqual(
-                array.min(),
-                minmax[0],
-                msg="{0}: {1} != {2}".format(array_name, array.min(), minmax[0]),
+                array.min(), minmax[0], msg=f"{array_name}: {array.min()} != {minmax[0]}"
             )
             self.assertAlmostEqual(
-                array.max(),
-                minmax[1],
-                msg="{0}: {1} != {2}".format(array_name, array.max(), minmax[1]),
+                array.max(), minmax[1], msg=f"{array_name}: {array.max()} != {minmax[1]}"
             )
 
     def test_correct_sort_of_more_than_100_state_files(self):
diff --git a/test/unit_tests/dyna/test_mapper.py b/test/unit_tests/dyna/test_mapper.py
index 1b59266..2cdd3eb 100644
--- a/test/unit_tests/dyna/test_mapper.py
+++ b/test/unit_tests/dyna/test_mapper.py
@@ -1,4 +1,4 @@
-from typing import Dict, Set, Tuple, Union
+from typing import Union
 from unittest import TestCase
 
 import numpy as np
@@ -7,7 +7,7 @@
 from lasso.dyna.array_type import ArrayType
 from lasso.dyna.femzip_mapper import FemzipMapper
 
-part_global_femzip_translations: Dict[Tuple[FemzipArrayType, FemzipVariableCategory], Set[str]] = {
+part_global_femzip_translations: dict[tuple[FemzipArrayType, FemzipVariableCategory], set[str]] = {
     # GLOBAL
     (FemzipArrayType.GLOBAL_DATA, FemzipVariableCategory.GLOBAL): {
         # ArrayType.global_timesteps,
@@ -26,7 +26,7 @@
     },
 }
 
-element_nope_femzip_translations: Dict[Tuple[str, FemzipVariableCategory], str] = {
+element_nope_femzip_translations: dict[tuple[str, FemzipVariableCategory], str] = {
     # NODE
     (
         FemzipArrayType.NODE_DISPLACEMENT.value,
@@ -254,7 +254,7 @@
 class MapperTest(TestCase):
     def validate(
         self,
-        fz: Dict[Tuple[str, FemzipVariableCategory], np.ndarray],
+        fz: dict[tuple[str, FemzipVariableCategory], np.ndarray],
         d3plot_shape: tuple,
         data_index_positions: Union[tuple, slice] = slice(None),
     ):
@@ -334,7 +334,7 @@ def test_dependent_variable(self):
         d1 = np.random.randn(2, 1200)
         d2 = np.random.randn(2, 1200)
 
-        fz: Dict[Tuple[int, str, FemzipVariableCategory], np.ndarray] = {
+        fz: dict[tuple[int, str, FemzipVariableCategory], np.ndarray] = {
             (1, "element_dependent_variable_2", FemzipVariableCategory.SHELL): d2,
             (2, "element_dependent_variable_1", FemzipVariableCategory.SHELL): d1,
         }
@@ -355,7 +355,7 @@ def test_effective_p_strain(self):
         d2 = np.random.randn(2, 20000)
         d3 = np.random.randn(2, 20000)
 
-        fz: Dict[Tuple[int, str, FemzipVariableCategory], np.ndarray] = {
+        fz: dict[tuple[int, str, FemzipVariableCategory], np.ndarray] = {
             (1, "Effective plastic strain (   1)", FemzipVariableCategory.SHELL): d1,
             (2, "Effective plastic strain (   2)", FemzipVariableCategory.SHELL): d2,
             (3, "Effective plastic strain (   3)", FemzipVariableCategory.SHELL): d3,
@@ -390,7 +390,7 @@ def test_others(self):
         history_vars1 = np.random.randn(3, 2)
         history_vars2 = np.random.randn(3, 2)
 
-        fz: Dict[Tuple[int, str, FemzipVariableCategory], np.ndarray] = {
+        fz: dict[tuple[int, str, FemzipVariableCategory], np.ndarray] = {
             # stress
             (1, "Sigma-x (IP 6)", FemzipVariableCategory.SOLID): stress_1,
             (2, "Sigma-y (IP 3)", FemzipVariableCategory.SOLID): stress_2,
@@ -439,7 +439,7 @@ def test_beam(self):
         bending = np.random.randn(3, 123)
         torsion = np.random.rand(2, 5)
 
-        fz: Dict[Tuple[int, str, FemzipVariableCategory], np.ndarray] = {
+        fz: dict[tuple[int, str, FemzipVariableCategory], np.ndarray] = {
             (1, "axial_force", FemzipVariableCategory.BEAM): axial_force,
             (2, "s_shear_resultant", FemzipVariableCategory.BEAM): shear,
             (3, "t_bending_moment", FemzipVariableCategory.BEAM): bending,