diff --git a/include/tadah/mlip/dataset_readers/castep_geom_reader.h b/include/tadah/mlip/dataset_readers/castep_geom_reader.h
new file mode 100644
index 0000000000000000000000000000000000000000..f44dc7231d6e6abad416b5f22ea01ab3893d94c4
--- /dev/null
+++ b/include/tadah/mlip/dataset_readers/castep_geom_reader.h
@@ -0,0 +1,66 @@
+#ifndef CASTEP_GEOM_READER_H
+#define CASTEP_GEOM_READER_H
+
+#include <tadah/mlip/structure.h>
+#include <tadah/mlip/structure_db.h>
+#include <tadah/mlip/dataset_readers/castep_md_reader.h>
+
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <vector>
+#include <stdexcept>
+
+/**
+ * @class CastepGeomReader
+ * @brief A class for reading and parsing CASTEP .geom files.
+ *
+ * Implements data extraction and processing from geometry optimisation runs.
+ * Data are converted from atomic units to common units:
+ * eV for energy, Ångström for distance, eV/Å for force, and eV/ų
+ * for pressure.
+ *
+ * Example usage:
+ * @code
+ * StructureDB my_db;
+ * // Using the basic constructor
+ * CastepGeomReader reader1(my_db);
+ * reader1.read_data("test.geom");
+ * reader1.parse_data();
+ * reader1.print_summary();
+ * 
+ * // Using the constructor with filename
+ * CastepGeomReader reader2(my_db, "test.geom");
+ * reader2.print_summary();
+ * @endcode
+ */
+class CastepGeomReader : public CastepMDReader {
+public:
+  /**
+   * @brief Constructs a CastepGeomReader with a StructureDB reference.
+   * @param db Reference to a StructureDB object for storing parsed data.
+   */
+  CastepGeomReader(StructureDB& db);
+
+  /**
+   * @brief Constructs a CastepGeomReader and reads the specified file.
+   * @param db Reference to a StructureDB object for storing parsed data.
+   * @param filename Name of the .geom file to read.
+   */
+  CastepGeomReader(StructureDB& db, const std::string& filename);
+
+private:
+
+  /**
+   * @brief Dummy ideal gas pressure.
+   * @return zero.
+   */
+  double calc_P_ideal(Structure &, double ) override;
+
+  std::string get_first_label(std::string &) override;
+  std::string get_label(std::string &) override;
+
+};
+
+#endif // CASTEP_GEOM_READER_H
+
diff --git a/include/tadah/mlip/dataset_readers/castep_md_reader.h b/include/tadah/mlip/dataset_readers/castep_md_reader.h
index 3730aa23e7ef846952039ccf16d7f984b780ffd2..147f54d366b75dd50fd8fc2ea24533a8bb06ef5e 100644
--- a/include/tadah/mlip/dataset_readers/castep_md_reader.h
+++ b/include/tadah/mlip/dataset_readers/castep_md_reader.h
@@ -17,8 +17,11 @@
  *
  * Implements data extraction and processing for molecular dynamics
  * simulations by converting data from atomic units to common units:
- * eV for energy, Ångström for distance, eV/Å for force, and eV/ų
- * for pressure.
+ * eV for energy, Ångström for distance, eV/Å for force, eV/ų
+ * for pressure and ps for time.
+ * 
+ * The virial stress tensor is extracted from full pressure tensor
+ * by removing kinetic contributions.
  *
  * Example usage:
  * @code
@@ -53,19 +56,27 @@ public:
    * @brief Reads data from a specified .md file.
    * @param filename The name of the .md file to read data from.
    */
-  void read_data(const std::string& filename) override;
+  virtual void read_data(const std::string& filename) override;
 
   /**
    * @brief Parses the data read from the .md file.
    */
-  void parse_data() override;
+  virtual void parse_data() override;
 
   /**
    * @brief Prints a summary of the parsed .md data.
    */
-  void print_summary() const override;
+  virtual void print_summary() const override;
+
+  /**
+   * @brief Checks if a string ends with a given suffix.
+   * @param str The string to check.
+   * @param suffix The suffix to check for.
+   * @return True if str ends with suffix, otherwise false.
+   */
+  bool ends_with(const std::string& str, const std::string& suffix);
 
-private:
+protected:
   std::string raw_data_;  /**< Stores raw file data */
   std::string filename_; /**< Filename of the .md file */
 
@@ -78,13 +89,6 @@ private:
   double k_b = 8.617333262145e-5 / e_conv; /**< Boltzmann constant in atomic units (Hartree/K) */
 
   // Helper methods
-  /**
-   * @brief Checks if a string ends with a given suffix.
-   * @param str The string to check.
-   * @param suffix The suffix to check for.
-   * @return True if str ends with suffix, otherwise false.
-   */
-  bool ends_with(const std::string& str, const std::string& suffix);
 
   /**
    * @brief Calculates the ideal gas pressure.
@@ -92,7 +96,7 @@ private:
    * @param T Temperature in atomic units.
    * @return Calculated ideal gas pressure.
    */
-  double calc_P_ideal(Structure &s, double T);
+  virtual double calc_P_ideal(Structure &s, double T);
 
   /**
    * @brief Performs post-processing on the given structure.
@@ -102,6 +106,10 @@ private:
 
   double T = 0; /**< Temperature in atomic units (Hartree/k_B) */
   bool stress_tensor_bool = false; /**< Indicates presence of a stress tensor */
+
+  virtual std::string get_first_label(std::string &);
+  virtual std::string get_label(std::string &);
+
 };
 
 #endif // CASTEP_MD_READER_H
diff --git a/src/castep_geom_reader.cpp b/src/castep_geom_reader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9cd0894ea5435131b2c0c196b74eada4f36461eb
--- /dev/null
+++ b/src/castep_geom_reader.cpp
@@ -0,0 +1,20 @@
+#include <tadah/core/utils.h>
+#include <tadah/mlip/dataset_readers/castep_geom_reader.h>
+#include <tadah/mlip/dataset_readers/castep_md_reader.h>
+
+CastepGeomReader::CastepGeomReader(StructureDB& db)
+: CastepMDReader(db) {}
+
+CastepGeomReader::CastepGeomReader(StructureDB& db, const std::string& filename)
+: CastepMDReader(db, filename) {}
+
+double CastepGeomReader::calc_P_ideal(Structure &, double ) {
+  return 0; // dummy as geom stress tensor os virial
+}
+
+std::string CastepGeomReader::get_first_label(std::string &step) {
+  return "Filename: "+filename_+ " | Units: (eV, Angstrom) | Step: " + step;
+}
+std::string CastepGeomReader::get_label(std::string &step) {
+  return "Step: " + step;
+}
diff --git a/src/castep_md_reader.cpp b/src/castep_md_reader.cpp
index 38628edd790ccfb3b2184e329cfa6419ae25425d..43809c1383b441354b9e2d10f4f75b556518ca8b 100644
--- a/src/castep_md_reader.cpp
+++ b/src/castep_md_reader.cpp
@@ -57,6 +57,14 @@ void CastepMDReader::postproc_structure(Structure &s) {
   s = Structure();
 }
 
+std::string CastepMDReader::get_first_label(std::string &time) {
+  return "Filename: "+filename_+ " | Units: (eV, Angstrom) | Time: "
+  + std::to_string(std::stod(time)*t_conv) + " ps";
+}
+std::string CastepMDReader::get_label(std::string &time) {
+  return "Time: " + std::to_string(std::stod(time)*t_conv) + " ps";
+}
+
 void CastepMDReader::parse_data() {
   std::istringstream stream(raw_data_);
   std::string line;
@@ -72,8 +80,7 @@ void CastepMDReader::parse_data() {
   while (std::getline(stream, line)) {
     std::istringstream iss(line);
     if (ends_with(line,"<-- E")) {
-      s.label = "Filename: "+filename_+ " | Units: (eV, Angstrom) | Time: "
-        + std::to_string(std::stod(time)*t_conv) + " ps";
+      s.label = get_first_label(time);
       iss >> s.energy;
       s.energy *= e_conv;
       break;
@@ -136,7 +143,7 @@ void CastepMDReader::parse_data() {
         std::string time;
         std::istringstream iss(line);
         iss >> time;
-        s.label =  "Time: " + std::to_string(std::stod(time)*t_conv) + " ps";
+        s.label = get_label(time);
       }
 
       cell_idx=0;