Repository: silvxlabs/quicfire-tools (language: python)

Issue:
Netcdf for v0.7.0

Fix Ignition serialization

Add example and test data



Discussion / hints:

Fix patch:
diff --git a/docs/how-to-guides.md b/docs/how-to-guides.md
index 44afc36..3a53be5 100644
--- a/docs/how-to-guides.md
+++ b/docs/how-to-guides.md
@@ -179,14 +179,14 @@ from quicfire_tools.topography import GaussianHillTopo
 
 # First, create an TopoType class
 topo = GaussianHillTopo(
-    x_hilltop = 100,
-    y_hilltop = 150,
-    elevation_max = 50,
-    elevation_std = 15
+    x_hilltop=100,
+    y_hilltop=150,
+    elevation_max=50,
+    elevation_std=15
 )
 
-# Next, assign it to the topo_type attribute of the qu_topoinputs InputFile
-simulation.qu_topoinputs.topo_type = topo
+# Next, assign it to the topography attribute of the qu_topoinputs InputFile
+simulation.qu_topoinputs.topography = topo
 ```
 
 Please see [topopgraphy](reference.md#quicfire_tools.topography) for a full list of topography types.
@@ -207,8 +207,8 @@ ignition = CircularRingIgnition(
     ring_width = 10
 )
 
-# Next, assign it to the ignition_type attribute of the quic_fire InputFile
-simulation.quic_fire.ignition_type
+# Next, assign it to the ignition attribute of the quic_fire InputFile
+simulation.quic_fire.ignition
 ```
 
 Please see [igntions](reference.md#quicfire_tools.ignitions) for a full list of ignition patterns.
diff --git a/notebooks/demo.ipynb b/notebooks/demo.ipynb
index c7d5f91..c450bf2 100644
--- a/notebooks/demo.ipynb
+++ b/notebooks/demo.ipynb
@@ -86,7 +86,7 @@
    "outputs": [
     {
      "data": {
-      "text/plain": "QUIC_fire(name='QUIC_fire', fire_flag=1, random_seed=-1, time_now=1705940333, sim_time=600, fire_time_step=1, quic_time_step=1, out_time_fire=30, out_time_wind=30, out_time_emis_rad=30, out_time_wind_avg=30, nz=1, stretch_grid_flag=0, dz=1, dz_array=[], fuel_density_flag=1, fuel_density=0.5, fuel_moisture_flag=1, fuel_moisture=0.1, fuel_height_flag=1, fuel_height=1.0, size_scale_flag=0, size_scale=0.0005, patch_and_gap_flag=0, patch_size=0, gap_size=0, ignition_type=RectangleIgnition(ignition_flag=<IgnitionSources.rectangle: 1>, x_min=20.0, y_min=20.0, x_length=10.0, y_length=160.0), ignitions_per_cell=2, firebrand_flag=0, auto_kill=1, eng_to_atm_out=0, react_rate_out=0, fuel_dens_out=1, qf_wind_out=0, qu_wind_inst_out=1, qu_wind_avg_out=0, fuel_moist_out=0, mass_burnt_out=0, firebrand_out=0, emissions_out=0, radiation_out=0, surf_eng_out=0)"
+      "text/plain": "QUIC_fire(name='QUIC_fire', fire_flag=1, random_seed=-1, time_now=1705940333, sim_time=600, fire_time_step=1, quic_time_step=1, out_time_fire=30, out_time_wind=30, out_time_emis_rad=30, out_time_wind_avg=30, nz=1, stretch_grid_flag=0, dz=1, dz_array=[], fuel_density_flag=1, fuel_density=0.5, fuel_moisture_flag=1, fuel_moisture=0.1, fuel_height_flag=1, fuel_height=1.0, size_scale_flag=0, size_scale=0.0005, patch_and_gap_flag=0, patch_size=0, gap_size=0, ignition=RectangleIgnition(ignition_flag=<IgnitionSources.rectangle: 1>, x_min=20.0, y_min=20.0, x_length=10.0, y_length=160.0), ignitions_per_cell=2, firebrand_flag=0, auto_kill=1, eng_to_atm_out=0, react_rate_out=0, fuel_dens_out=1, qf_wind_out=0, qu_wind_inst_out=1, qu_wind_avg_out=0, fuel_moist_out=0, mass_burnt_out=0, firebrand_out=0, emissions_out=0, radiation_out=0, surf_eng_out=0)"
      },
      "execution_count": 30,
      "metadata": {},
@@ -358,7 +358,7 @@
    "outputs": [
     {
      "data": {
-      "text/plain": "TopoType(topo_flag=<TopoSources.flat: 0>)"
+      "text/plain": "TopoType(topo_flag=<TopoFlags.flat: 0>)"
      },
      "execution_count": 42,
      "metadata": {},
@@ -366,7 +366,7 @@
     }
    ],
    "source": [
-    "custom_simulation_flat_topo.qu_topoinputs.topo_type"
+    "custom_simulation_flat_topo.qu_topoinputs.topography"
    ]
   },
   {
diff --git a/quicfire_tools/data/outputs/outputs.json b/quicfire_tools/data/outputs/outputs.json
new file mode 100644
index 0000000..a7362c1
--- /dev/null
+++ b/quicfire_tools/data/outputs/outputs.json
@@ -0,0 +1,265 @@
+{
+  "fire-energy_to_atmos": {
+    "file_format": "gridded",
+    "dimensions": [
+      "z",
+      "y",
+      "x"
+    ],
+    "grid": "en2atmos",
+    "output_times": "fire",
+    "delimiter": "-",
+    "extension": ".bin",
+    "description": "Energy released to the atmosphere that generates buoyant plumes",
+    "units": "kW"
+  },
+  "fire-reaction_rate": {
+    "file_format": "compressed",
+    "dimensions": [
+      "z",
+      "y",
+      "x"
+    ],
+    "grid": "fire",
+    "output_times": "fire",
+    "delimiter": "-",
+    "extension": ".bin",
+    "description": "Rate of reaction of fuel",
+    "units": "kg/m^3/s"
+  },
+  "fuels-dens": {
+    "file_format": "compressed",
+    "dimensions": [
+      "z",
+      "y",
+      "x"
+    ],
+    "grid": "fire",
+    "output_times": "fire",
+    "delimiter": "-",
+    "extension": ".bin",
+    "description": "Fuel density",
+    "units": "kg/m^3"
+  },
+  "fuels-moist": {
+    "file_format": "compressed",
+    "dimensions": [
+      "z",
+      "y",
+      "x"
+    ],
+    "grid": "fire",
+    "output_times": "fire",
+    "delimiter": "-",
+    "extension": ".bin",
+    "description": "Fuel moisture content",
+    "units": "g water / g air"
+  },
+  "groundfuelheight": {
+    "file_format": "gridded",
+    "dimensions": [
+      "y",
+      "x"
+    ],
+    "grid": "fire",
+    "output_times": null,
+    "delimiter": null,
+    "extension": ".bin",
+    "description": "2D array with initial fuel height in the ground layer",
+    "units": "m"
+  },
+  "mburnt_integ": {
+    "file_format": "gridded",
+    "dimensions": [
+      "y",
+      "x"
+    ],
+    "grid": "fire",
+    "output_times": "fire",
+    "delimiter": "-",
+    "extension": ".bin",
+    "description": "2D file containing the percentage of mass burnt for each (i,j) location on the fire grid (vertically integrated)",
+    "units": "%"
+  },
+  "surfEnergy": {
+    "file_format": "gridded",
+    "dimensions": [
+      "y",
+      "x"
+    ],
+    "grid": "fire",
+    "output_times": "surf_eng",
+    "delimiter": "",
+    "extension": ".bin",
+    "description": "Array contains total energy output by each surface cell on a per second interval.",
+    "units": "kW/m^2"
+  },
+  "thermaldose": {
+    "file_format": "compressed",
+    "dimensions": [
+      "z",
+      "y",
+      "x"
+    ],
+    "grid": "fire",
+    "output_times": "emis_rad",
+    "delimiter": "-",
+    "extension": ".bin",
+    "description": "The output shows the thermal flux to skin of a person collocated with fuel (for health effects).",
+    "units": "(kW/m^2)^(4/3)s"
+  },
+  "thermalradiation": {
+    "file_format": "compressed",
+    "dimensions": [
+      "z",
+      "y",
+      "x"
+    ],
+    "grid": "fire",
+    "output_times": "emis_rad",
+    "delimiter": "-",
+    "extension": ".bin",
+    "description": "The output shows the thermal flux to skin of a person collocated with fuel (for health effects).",
+    "units": "kW/m^2"
+  },
+  "windu": {
+        "file_format": "gridded",
+        "dimensions": ["z", "y", "x"],
+        "grid": "fire",
+        "output_times": "fire",
+        "delimiter": "",
+        "extension": ".bin",
+        "description": "Fire u-components, cell centered ",
+        "units": "m/s"
+    },
+    "windv": {
+        "file_format": "gridded",
+        "dimensions": ["z", "y", "x"],
+        "grid": "fire",
+        "output_times": "fire",
+        "delimiter": "",
+        "extension": ".bin",
+        "description": "Fire v-components, cell centered ",
+        "units": "m/s"
+    },
+    "windw": {
+        "file_format": "gridded",
+        "dimensions": ["z", "y", "x"],
+        "grid": "fire",
+        "output_times": "fire",
+        "delimiter": "",
+        "extension": ".bin",
+        "description": "Fire w-components, cell centered ",
+        "units": "m/s"
+    },
+      "qu_windu": {
+        "file_format": "gridded",
+        "dimensions": ["z", "y", "x"],
+        "grid": "quic",
+        "output_times": "wind",
+        "delimiter": "",
+        "extension": ".bin",
+        "description": "Instantaneous wind component in the x-direction, cell centered ",
+        "units": "m/s"
+    },
+    "qu_windv": {
+        "file_format": "gridded",
+        "dimensions": ["z", "y", "x"],
+        "grid": "quic",
+        "output_times": "wind",
+        "delimiter": "",
+        "extension": ".bin",
+        "description": "Instantaneous wind component in the y-direction, cell centered ",
+        "units": "m/s"
+    },
+    "qu_windw": {
+        "file_format": "gridded",
+        "dimensions": ["z", "y", "x"],
+        "grid": "quic",
+        "output_times": "wind",
+        "delimiter": "",
+        "extension": ".bin",
+        "description": "Instantaneous wind component in the x-direction, cell centered ",
+        "units": "m/s"
+    },
+    "qu_windu_ave": {
+        "file_format": "gridded",
+        "dimensions": ["z", "y", "x"],
+        "grid": "quic",
+        "output_times": "wind_ave",
+        "delimiter": "",
+        "extension": ".bin",
+        "description": "Averaged wind component in the x-direction, cell centered ",
+        "units": "m/s"
+    },
+    "qu_windv_ave": {
+        "file_format": "gridded",
+        "dimensions": ["z", "y", "x"],
+        "grid": "quic",
+        "output_times": "wind_ave",
+        "delimiter": "",
+        "extension": ".bin",
+        "description": "Averaged wind component in the y-direction, cell centered ",
+        "units": "m/s"
+    },
+    "qu_windw_ave": {
+        "file_format": "gridded",
+        "dimensions": ["z", "y", "x"],
+        "grid": "quic",
+        "output_times": "wind_ave",
+        "delimiter": "",
+        "extension": ".bin",
+        "description": "Averaged wind component in the z-direction, cell centered ",
+        "units": "m/s"
+    },
+    "qu_wplume": {
+        "file_format": "gridded",
+        "dimensions": ["z", "y", "x"],
+        "grid": "quic",
+        "output_times": "wind",
+        "delimiter": "",
+        "extension": ".bin",
+        "description": "Instantaneous wind component in the z-direction generated by the fire, cell centered ",
+        "units": "m/s"
+    },
+      "co_emissions": {
+        "file_format": "compressed",
+        "dimensions": ["z", "y", "x"],
+        "grid": "fire",
+        "output_times": "emis_rad",
+        "delimiter": "-",
+        "extension": ".bin",
+        "description": "Mass of CO emitted between two emission file output times in grams",
+        "units": "g"
+    },
+    "pm_emissions": {
+        "file_format": "compressed",
+        "dimensions": ["z", "y", "x"],
+        "grid": "fire",
+        "output_times": "emis_rad",
+        "delimiter": "-",
+        "extension": ".bin",
+        "description": "Mass of PM2.5 emitted between two emission file output times in grams",
+        "units": "g"
+    },
+    "water_emissions": {
+        "file_format": "compressed",
+        "dimensions": ["z", "y", "x"],
+        "grid": "fire",
+        "output_times": "emis_rad",
+        "delimiter": "-",
+        "extension": ".bin",
+        "description": "Mass of water emitted between two emission file output times (fuel moisture + water from combustion) in grams",
+        "units": "g"
+    },
+    "emissions_distribution": {
+        "file_format": "compressed",
+        "dimensions": ["z", "y", "x"],
+        "grid": "fire",
+        "output_times": "emis_rad",
+        "delimiter": "-",
+        "extension": ".bin",
+        "description": "Size of PM2.5 emitted between two emission file output times in microns",
+        "units": "g"
+    }
+}
diff --git a/quicfire_tools/examples.py b/quicfire_tools/examples.py
deleted file mode 100644
index 03cb9ae..0000000
--- a/quicfire_tools/examples.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from __future__ import annotations
-
-from quicfire_tools.inputs import SimulationInputs
-
-
-def create_line_fire() -> SimulationInputs:
-    # Set up the basic simulation data
-    sim_inputs = SimulationInputs.create_simulation(
-        nx=200,
-        ny=200,
-        fire_nz=1,
-        simulation_time=600,
-        wind_speed=1.7,
-        wind_direction=270,
-    )
-
-    # Set uniform fuel values
-    sim_inputs.set_uniform_fuels(
-        fuel_density=0.7,
-        fuel_moisture=0.10,
-        fuel_height=1.0,
-    )
-
-    # Set a line ignitions
-    sim_inputs.set_rectangle_ignition(
-        x_min=10,
-        y_min=50,
-        x_length=10,
-        y_length=100,
-    )
-
-    # Select which binary files to output
-    sim_inputs.set_output_files(
-        fuel_dens=True,
-        mass_burnt=True,
-        qf_wind=True,
-    )
-
-    return sim_inputs
-
-
-# TODO: Implement a FastFuels custom .dat file example
-def create_custom_fuels_simulation() -> SimulationInputs:
-    return None
-
-
-line_fire_simulation = create_line_fire()
-custom_fuels_simulation = create_custom_fuels_simulation()
diff --git a/quicfire_tools/ignitions.py b/quicfire_tools/ignitions.py
index 6966b1c..e2af13e 100644
--- a/quicfire_tools/ignitions.py
+++ b/quicfire_tools/ignitions.py
@@ -1,6 +1,7 @@
 """
 QUIC-Fire Tools Ignitions Module
 """
+
 from __future__ import annotations
 
 # Core Imports
@@ -10,7 +11,7 @@
 from pydantic import BaseModel
 
 
-class IgnitionSources(int, Enum):
+class IgnitionFlags(int, Enum):
     """
     Enum class for all valid ignition source options in QUIC-Fire.
     """
@@ -21,13 +22,13 @@ class IgnitionSources(int, Enum):
     ignite_dat_file = 7
 
 
-class IgnitionType(BaseModel):
+class Ignition(BaseModel):
     """
     Base class for all ignition types in QUIC-Fire. This class is used to
     provide a common string representation for all ignition types.
     """
 
-    ignition_flag: IgnitionSources
+    ignition_flag: IgnitionFlags
 
     def __str__(self):
         return (
@@ -39,7 +40,7 @@ def __str__(self):
         )
 
 
-class RectangleIgnition(IgnitionType):
+class RectangleIgnition(Ignition):
     """
     Represents a rectangle ignition source in QUIC-Fire.
 
@@ -55,7 +56,7 @@ class RectangleIgnition(IgnitionType):
         Length in the y-direction [m]
     """
 
-    ignition_flag: IgnitionSources = IgnitionSources(1)
+    ignition_flag: IgnitionFlags = IgnitionFlags(1)
     x_min: float
     y_min: float
     x_length: float
@@ -72,7 +73,7 @@ def __str__(self):
         return flag_line + locations
 
 
-class SquareRingIgnition(IgnitionType):
+class SquareRingIgnition(Ignition):
     """
     Represents a square ring ignition source in QUIC-Fire.
 
@@ -92,7 +93,7 @@ class SquareRingIgnition(IgnitionType):
         Width in the y-direction [m]
     """
 
-    ignition_flag: IgnitionSources = IgnitionSources(2)
+    ignition_flag: IgnitionFlags = IgnitionFlags(2)
     x_min: float
     y_min: float
     x_length: float
@@ -113,7 +114,7 @@ def __str__(self):
         return flag_line + locations
 
 
-class CircularRingIgnition(IgnitionType):
+class CircularRingIgnition(Ignition):
     """
     Represents a circular ring ignition source in QUIC-Fire.
 
@@ -131,7 +132,7 @@ class CircularRingIgnition(IgnitionType):
         Width of the ring [m]
     """
 
-    ignition_flag: IgnitionSources = IgnitionSources(3)
+    ignition_flag: IgnitionFlags = IgnitionFlags(3)
     x_min: float
     y_min: float
     x_length: float
@@ -176,3 +177,15 @@ def default_line_ignition(nx, ny, wind_direction):
     return RectangleIgnition(
         x_min=x_min, y_min=y_min, x_length=x_length, y_length=y_length
     )
+
+
+def serialize_ignition(ignition_data: dict):
+    ignition_flag = ignition_data.get("ignition_flag")
+    if ignition_flag == IgnitionFlags(1):
+        return RectangleIgnition(**ignition_data)
+    elif ignition_flag == IgnitionFlags(2):
+        return SquareRingIgnition(**ignition_data)
+    elif ignition_flag == IgnitionFlags(3):
+        return CircularRingIgnition(**ignition_data)
+    else:
+        return Ignition(**ignition_data)
diff --git a/quicfire_tools/inputs.py b/quicfire_tools/inputs.py
index 0092425..734e710 100644
--- a/quicfire_tools/inputs.py
+++ b/quicfire_tools/inputs.py
@@ -36,15 +36,16 @@
 
 # Internal imports
 from quicfire_tools.ignitions import (
-    IgnitionSources,
+    IgnitionFlags,
     CircularRingIgnition,
-    IgnitionType,
+    Ignition,
     RectangleIgnition,
     SquareRingIgnition,
     default_line_ignition,
+    serialize_ignition,
 )
 from quicfire_tools.topography import (
-    TopoSources,
+    TopoFlags,
     CanyonTopo,
     CosHillTopo,
     GaussianHillTopo,
@@ -52,7 +53,8 @@
     HillPassTopo,
     SinusoidTopo,
     SlopeMesaTopo,
-    TopoType,
+    Topography,
+    serialize_topography,
 )
 from quicfire_tools.utils import compute_parabolic_stretched_grid
 
@@ -221,12 +223,12 @@ def create_simulation(
 
         # Initialize input files with required parameters
         start_time = int(time.time())
-        ignition_type = default_line_ignition(nx, ny, wind_direction)
+        ignition = default_line_ignition(nx, ny, wind_direction)
         quic_fire = QUIC_fire(
             nz=fire_nz,
             time_now=start_time,
             sim_time=simulation_time,
-            ignition_type=ignition_type,
+            ignition=ignition,
         )
         gridlist = Gridlist(n=nx, m=ny, l=fire_nz)
         windsensors = WindSensorArray(
@@ -297,26 +299,59 @@ def from_directory(
 
         version = _validate_and_return_version(version)
 
+        # Read the required input files
+        qu_fileoptions = QU_Fileoptions.from_file(directory)
+        qfire_advanced_user_inputs = QFire_Advanced_User_Inputs.from_file(directory)
+        qfire_bldg_advanced_user_inputs = QFire_Bldg_Advanced_User_Inputs.from_file(
+            directory
+        )
+        qfire_plume_advanced_user_inputs = QFire_Plume_Advanced_User_Inputs.from_file(
+            directory
+        )
+        runtime_advanced_user_inputs = RuntimeAdvancedUserInputs.from_file(directory)
+        qu_metparams = QU_metparams.from_file(directory)
+        quic_fire = QUIC_fire.from_file(directory, version=version)
+        windsensors = WindSensorArray.from_file(directory)
+        qu_topoinputs = QU_TopoInputs.from_file(directory)
+        qu_simparams = QU_Simparams.from_file(directory)
+
+        # Try and read optional input files. Return defaults if not found.
+        try:
+            raster_origin = RasterOrigin.from_file(directory)
+        except FileNotFoundError:
+            raster_origin = RasterOrigin()
+
+        # Instantiate file objects that are all default values
+        qu_buildings = QU_Buildings()
+        qp_buildout = QP_buildout()
+        qu_movingcoords = QU_movingcoords()
+
+        # Instantiate derivative files. Eventually get rid of these...
+        gridlist = Gridlist(
+            n=qu_simparams.nx,
+            m=qu_simparams.ny,
+            l=quic_fire.nz,
+            dx=qu_simparams.dx,
+            dy=qu_simparams.dy,
+            dz=quic_fire.dz,
+        )
+
         return cls(
-            rasterorigin=RasterOrigin.from_file(directory),
-            qu_buildings=QU_Buildings.from_file(directory),
-            qu_fileoptions=QU_Fileoptions.from_file(directory),
-            qfire_advanced_user_inputs=QFire_Advanced_User_Inputs.from_file(directory),
-            qfire_bldg_advanced_user_inputs=QFire_Bldg_Advanced_User_Inputs.from_file(
-                directory
-            ),
-            qfire_plume_advanced_user_inputs=QFire_Plume_Advanced_User_Inputs.from_file(
-                directory
-            ),
-            runtime_advanced_user_inputs=RuntimeAdvancedUserInputs.from_file(directory),
-            qu_movingcoords=QU_movingcoords.from_file(directory),
-            qp_buildout=QP_buildout.from_file(directory),
-            qu_metparams=QU_metparams.from_file(directory),
-            quic_fire=QUIC_fire.from_file(directory, version=version),
-            gridlist=Gridlist.from_file(directory),
-            windsensors=WindSensorArray.from_file(directory),
-            qu_topoinputs=QU_TopoInputs.from_file(directory),
-            qu_simparams=QU_Simparams.from_file(directory),
+            rasterorigin=raster_origin,
+            qu_buildings=qu_buildings,
+            qu_fileoptions=qu_fileoptions,
+            qfire_advanced_user_inputs=qfire_advanced_user_inputs,
+            qfire_bldg_advanced_user_inputs=qfire_bldg_advanced_user_inputs,
+            qfire_plume_advanced_user_inputs=qfire_plume_advanced_user_inputs,
+            runtime_advanced_user_inputs=runtime_advanced_user_inputs,
+            qu_movingcoords=qu_movingcoords,
+            qp_buildout=qp_buildout,
+            qu_metparams=qu_metparams,
+            quic_fire=quic_fire,
+            gridlist=gridlist,
+            windsensors=windsensors,
+            qu_topoinputs=qu_topoinputs,
+            qu_simparams=qu_simparams,
         )
 
     @classmethod
@@ -537,11 +572,9 @@ def set_custom_simulation(
         if patch_and_gap:
             self.quic_fire.patch_and_gap_flag = 2
         if ignition:
-            self.quic_fire.ignition_type = IgnitionType(
-                ignition_flag=IgnitionSources(7)
-            )
+            self.quic_fire.ignition = Ignition(ignition_flag=IgnitionFlags(7))
         if topo:
-            self.qu_topoinputs.topo_type = TopoType(topo_flag=TopoSources(5))
+            self.qu_topoinputs.topography = Topography(topo_flag=TopoFlags(5))
 
     def set_uniform_fuels(
         self,
@@ -629,7 +662,7 @@ def set_rectangle_ignition(
         ignition = RectangleIgnition(
             x_min=x_min, y_min=y_min, x_length=x_length, y_length=y_length
         )
-        self.quic_fire.ignition_type = ignition
+        self.quic_fire.ignition = ignition
 
     def set_output_files(
         self,
@@ -698,6 +731,28 @@ def set_output_files(
         self.quic_fire.surf_eng_out = int(surf_eng)
         self.quic_fire.emissions_out = 2 if emissions else 0
 
+    def set_output_interval(self, interval: int):
+        """
+        Sets the interval, in seconds, at which the simulation will write .bin
+        files to disk. This function sets the same interval for all output
+        files.
+
+        Parameters
+        ----------
+        interval: int
+            Interval in seconds at which to write .bin files to disk.
+
+        Examples
+        --------
+        >>> from quicfire_tools import SimulationInputs
+        >>> sim_inputs = SimulationInputs.create_simulation(nx=100, ny=100, fire_nz=26, wind_speed=1.8, wind_direction=90, simulation_time=600)
+        >>> sim_inputs.set_output_interval(60)
+        """
+        self.quic_fire.out_time_fire = interval
+        self.quic_fire.out_time_wind = interval
+        self.quic_fire.out_time_wind_avg = interval
+        self.quic_fire.out_time_emis_rad = interval
+
     def new_wind_sensor(
         self,
         update: str = None,
@@ -1022,15 +1077,21 @@ def from_file(cls, directory: str | Path, **kwargs):
         with open(directory / "gridlist", "r") as f:
             lines = f.read()
 
-        return cls(
-            n=int(lines.split("n=")[1].split()[0]),
-            m=int(lines.split("m=")[1].split()[0]),
-            l=int(lines.split("l=")[1].split()[0]),
-            dx=float(lines.split("dx=")[1].split()[0]),
-            dy=float(lines.split("dy=")[1].split()[0]),
-            dz=float(lines.split("dz=")[1].split()[0]),
-            aa1=float(lines.split("aa1=")[1].split()[0]),
-        )
+        try:
+            return cls(
+                n=int(lines.split("n=")[1].split()[0]),
+                m=int(lines.split("m=")[1].split()[0]),
+                l=int(lines.split("l=")[1].split()[0]),
+                dx=float(lines.split("dx=")[1].split()[0]),
+                dy=float(lines.split("dy=")[1].split()[0]),
+                dz=float(lines.split("dz=")[1].split()[0]),
+                aa1=float(lines.split("aa1=")[1].split()[0]),
+            )
+        except IndexError:
+            raise ValueError(
+                f"Error parsing {cls.__name__} from file: gridlist"
+                f"\nPlease check the file for correctness."
+            )
 
 
 class RasterOrigin(InputFile):
@@ -1061,7 +1122,13 @@ def from_file(cls, directory: str | Path, **kwargs):
             directory = Path(directory)
         with open(directory / "rasterorigin.txt", "r") as f:
             lines = f.readlines()
-        return cls(utm_x=float(lines[0].split()[0]), utm_y=float(lines[1].split()[0]))
+        try:
+            return cls(
+                utm_x=float(lines[0].split()[0]), utm_y=float(lines[1].split()[0])
+            )
+        except IndexError:
+            # Optional file, return default values if parsing fails
+            return cls()
 
 
 class QU_Buildings(InputFile):
@@ -1097,11 +1164,15 @@ def from_file(cls, directory: str | Path, **kwargs):
             directory = Path(directory)
         with open(directory / "QU_buildings.inp", "r") as f:
             lines = f.readlines()
-        return cls(
-            wall_roughness_length=float(lines[1].split()[0]),
-            number_of_buildings=int(lines[2].split()[0]),
-            number_of_polygon_nodes=int(lines[3].split()[0]),
-        )
+        try:
+            return cls(
+                wall_roughness_length=float(lines[1].split()[0]),
+                number_of_buildings=int(lines[2].split()[0]),
+                number_of_polygon_nodes=int(lines[3].split()[0]),
+            )
+        except IndexError:
+            # Optional file, return default values if parsing fails
+            return cls()
 
 
 class QU_Fileoptions(InputFile):
@@ -1146,13 +1217,19 @@ def from_file(cls, directory: str | Path, **kwargs):
             directory = Path(directory)
         with open(directory / "QU_fileoptions.inp", "r") as f:
             lines = f.readlines()
-        return cls(
-            output_data_file_format_flag=int(lines[1].split()[0]),
-            non_mass_conserved_initial_field_flag=int(lines[2].split()[0]),
-            initial_sensor_velocity_field_flag=int(lines[3].split()[0]),
-            qu_staggered_velocity_file_flag=int(lines[4].split()[0]),
-            generate_wind_startup_files_flag=int(lines[5].split()[0]),
-        )
+        try:
+            return cls(
+                output_data_file_format_flag=int(lines[1].split()[0]),
+                non_mass_conserved_initial_field_flag=int(lines[2].split()[0]),
+                initial_sensor_velocity_field_flag=int(lines[3].split()[0]),
+                qu_staggered_velocity_file_flag=int(lines[4].split()[0]),
+                generate_wind_startup_files_flag=int(lines[5].split()[0]),
+            )
+        except IndexError:
+            raise ValueError(
+                f"Error parsing {cls.__name__} from file: QU_fileoptions.inp"
+                f"\nPlease check the file for correctness."
+            )
 
 
 class QU_Simparams(InputFile):
@@ -1252,13 +1329,12 @@ class QU_Simparams(InputFile):
     quic_cfd_flag: Literal[0, 1] = 0
     explosive_bldg_flag: Literal[0, 1] = 0
     bldg_array_flag: Literal[0, 1] = 0
-    _from_file: bool = False
-    _from_file_dz_array: list[PositiveFloat] = []
+    _from_file_dz_array: Optional[list[PositiveFloat]] = None
 
     @computed_field
     @property
     def _dz_array(self) -> list[float]:
-        if self._from_file:
+        if self._from_file_dz_array:
             return self._from_file_dz_array
         elif self.stretch_grid_flag == 0:
             return [self.surface_vertical_cell_size] * self.nz
@@ -1432,69 +1508,77 @@ def from_file(cls, directory: str | Path, **kwargs):
         with open(directory / "QU_simparams.inp", "r") as f:
             lines = f.readlines()
 
-        # Read QU grid parameters
-        nx = int(lines[1].strip().split("!")[0])
-        ny = int(lines[2].strip().split("!")[0])
-        nz = int(lines[3].strip().split("!")[0])
-        dx = float(lines[4].strip().split("!")[0])
-        dy = float(lines[5].strip().split("!")[0])
-
-        # Read stretch grid flag
-        stretch_grid_flag = int(lines[6].strip().split("!")[0])
-
-        # Read vertical grid lines as function of stretch grid flag
-        _from_file_dz_array = []
-        custom_dz_array = []
-        if stretch_grid_flag == 0:
-            surface_vertical_cell_size = float(lines[7].strip().split("!")[0])
-            number_surface_cells = int(lines[8].strip().split("!")[0])
-            quic_domain_height = surface_vertical_cell_size * number_surface_cells
-            current_line = 9
-        elif stretch_grid_flag == 1:
-            surface_vertical_cell_size = float(lines[7].strip().split("!")[0])
-            number_surface_cells = 5
-            for i in range(9, 9 + nz):
-                custom_dz_array.append(float(lines[i].strip().split("!")[0]))
-            quic_domain_height = round(sum(custom_dz_array), 2)
-            current_line = 9 + nz
-        elif stretch_grid_flag == 3:
-            surface_vertical_cell_size = float(lines[7].strip().split("!")[0])
-            number_surface_cells = int(lines[8].strip().split("!")[0])
-            _ = lines[9].strip().split("!")[0]
-            for i in range(10, 10 + nz):
-                _from_file_dz_array.append(float(lines[i].strip().split("!")[0]))
-            quic_domain_height = round(sum(_from_file_dz_array), 2)
-            current_line = 10 + nz
-        else:
-            raise ValueError("stretch_grid_flag must be 0, 1, or 3.")
-
-        # Read QU wind parameters
-        number_wind_steps = int(lines[current_line].strip().split("!")[0])
-        utc_offset = int(lines[current_line + 1].strip().split("!")[0])
-        _ = lines[current_line + 2].strip().split("!")[0]
-        wind_times = []
-        for i in range(current_line + 3, current_line + 3 + number_wind_steps):
-            wind_times.append(int(lines[i].strip()))
-        current_line = current_line + 3 + number_wind_steps
-
-        # Skip not used parameters
-        current_line += 9
-
-        # Read remaining QU parameters
-        sor_iter_max = int(lines[current_line].strip().split("!")[0])
-        sor_residual_reduction = int(lines[current_line + 1].strip().split("!")[0])
-        use_diffusion_flag = int(lines[current_line + 2].strip().split("!")[0])
-        number_diffusion_iterations = int(lines[current_line + 3].strip().split("!")[0])
-        domain_rotation = float(lines[current_line + 4].strip().split("!")[0])
-        utm_x = float(lines[current_line + 5].strip().split("!")[0])
-        utm_y = float(lines[current_line + 6].strip().split("!")[0])
-        utm_zone_number = int(lines[current_line + 7].strip().split("!")[0])
-        utm_zone_letter = int(lines[current_line + 8].strip().split("!")[0])
-        quic_cfd_flag = int(lines[current_line + 9].strip().split("!")[0])
-        explosive_bldg_flag = int(lines[current_line + 10].strip().split("!")[0])
-        bldg_array_flag = int(lines[current_line + 11].strip().split("!")[0])
+        try:
+            # Read QU grid parameters
+            nx = int(lines[1].strip().split()[0])
+            ny = int(lines[2].strip().split()[0])
+            nz = int(lines[3].strip().split()[0])
+            dx = float(lines[4].strip().split()[0])
+            dy = float(lines[5].strip().split()[0])
+
+            # Read stretch grid flag
+            stretch_grid_flag = int(lines[6].strip().split()[0])
+
+            # Read vertical grid lines as function of stretch grid flag
+            from_file_dz_array = []
+            custom_dz_array = []
+            if stretch_grid_flag == 0:
+                surface_vertical_cell_size = float(lines[7].strip().split()[0])
+                number_surface_cells = int(lines[8].strip().split()[0])
+                quic_domain_height = surface_vertical_cell_size * number_surface_cells
+                current_line = 9
+            elif stretch_grid_flag == 1:
+                surface_vertical_cell_size = float(lines[7].strip().split()[0])
+                number_surface_cells = 5
+                for i in range(9, 9 + nz):
+                    custom_dz_array.append(float(lines[i].strip().split()[0]))
+                quic_domain_height = round(sum(custom_dz_array), 2)
+                current_line = 9 + nz
+            elif stretch_grid_flag == 3:
+                surface_vertical_cell_size = float(lines[7].strip().split()[0])
+                number_surface_cells = int(lines[8].strip().split()[0])
+                for i in range(10, 10 + nz):
+                    from_file_dz_array.append(float(lines[i].strip().split()[0]))
+                quic_domain_height = round(sum(from_file_dz_array), 2)
+                current_line = 10 + nz
+            else:
+                raise ValueError("stretch_grid_flag must be 0, 1, or 3.")
+
+            # Read QU wind parameters
+            number_wind_steps = int(lines[current_line].strip().split()[0])
+            utc_offset = int(lines[current_line + 1].strip().split()[0])
+            _ = lines[current_line + 2].strip().split()[0]
+            wind_times = []
+            for i in range(current_line + 3, current_line + 3 + number_wind_steps):
+                wind_times.append(int(lines[i].strip()))
+            current_line = current_line + 3 + number_wind_steps
+
+            # Skip not used parameters
+            current_line += 9
+
+            # Read remaining QU parameters
+            sor_iter_max = int(lines[current_line].strip().split()[0])
+            sor_residual_reduction = int(lines[current_line + 1].strip().split()[0])
+            use_diffusion_flag = int(lines[current_line + 2].strip().split()[0])
+            number_diffusion_iterations = int(
+                lines[current_line + 3].strip().split()[0]
+            )
+            domain_rotation = float(lines[current_line + 4].strip().split()[0])
+            utm_x = float(lines[current_line + 5].strip().split()[0])
+            utm_y = float(lines[current_line + 6].strip().split()[0])
+            utm_zone_number = int(lines[current_line + 7].strip().split()[0])
+            utm_zone_letter = int(lines[current_line + 8].strip().split()[0])
+            quic_cfd_flag = int(lines[current_line + 9].strip().split()[0])
+            explosive_bldg_flag = int(lines[current_line + 10].strip().split()[0])
+            bldg_array_flag = int(lines[current_line + 11].strip().split()[0])
+
+        except IndexError:
+            raise ValueError(
+                f"Error parsing {cls.__name__} from file: QU_simparams.inp"
+                f"\nPlease check the file for correctness."
+            )
 
-        return cls(
+        qu_simparams = cls(
             nx=nx,
             ny=ny,
             nz=nz,
@@ -1519,9 +1603,9 @@ def from_file(cls, directory: str | Path, **kwargs):
             quic_cfd_flag=quic_cfd_flag,
             explosive_bldg_flag=explosive_bldg_flag,
             bldg_array_flag=bldg_array_flag,
-            _from_file=True,
-            _from_file_dz_array=_from_file_dz_array,
         )
+        qu_simparams._from_file_dz_array = from_file_dz_array
+        return qu_simparams
 
 
 class QFire_Advanced_User_Inputs(InputFile):
@@ -1605,22 +1689,29 @@ def from_file(cls, directory: str | Path, **kwargs):
             directory = Path(directory)
         with open(directory / "QFire_Advanced_User_Inputs.inp", "r") as f:
             lines = f.readlines()
-        return cls(
-            fraction_cells_launch_firebrands=float(lines[0].split()[0]),
-            firebrand_radius_scale_factor=float(lines[1].split()[0]),
-            firebrand_trajectory_time_step=int(lines[2].split()[0]),
-            firebrand_launch_interval=int(lines[3].split()[0]),
-            firebrands_per_deposition=int(lines[4].split()[0]),
-            firebrand_area_ratio=float(lines[5].split()[0]),
-            minimum_burn_rate_coefficient=float(lines[6].split()[0]),
-            max_firebrand_thickness_fraction=float(lines[7].split()[0]),
-            firebrand_germination_delay=int(lines[8].split()[0]),
-            vertical_velocity_scale_factor=float(lines[9].split()[0]),
-            minimum_firebrand_ignitions=int(lines[10].split()[0]),
-            maximum_firebrand_ignitions=int(lines[11].split()[0]),
-            minimum_landing_angle=float(lines[12].split()[0]),
-            maximum_firebrand_thickness=float(lines[13].split()[0]),
-        )
+
+        try:
+            return cls(
+                fraction_cells_launch_firebrands=float(lines[0].split()[0]),
+                firebrand_radius_scale_factor=float(lines[1].split()[0]),
+                firebrand_trajectory_time_step=int(lines[2].split()[0]),
+                firebrand_launch_interval=int(lines[3].split()[0]),
+                firebrands_per_deposition=int(lines[4].split()[0]),
+                firebrand_area_ratio=float(lines[5].split()[0]),
+                minimum_burn_rate_coefficient=float(lines[6].split()[0]),
+                max_firebrand_thickness_fraction=float(lines[7].split()[0]),
+                firebrand_germination_delay=int(lines[8].split()[0]),
+                vertical_velocity_scale_factor=float(lines[9].split()[0]),
+                minimum_firebrand_ignitions=int(lines[10].split()[0]),
+                maximum_firebrand_ignitions=int(lines[11].split()[0]),
+                minimum_landing_angle=float(lines[12].split()[0]),
+                maximum_firebrand_thickness=float(lines[13].split()[0]),
+            )
+        except IndexError:
+            raise ValueError(
+                f"Error parsing {cls.__name__} from file: QFire_Advanced_User_Inputs.inp"
+                f"\nPlease check the file for correctness."
+            )
 
 
 class QUIC_fire(InputFile):
@@ -1658,12 +1749,12 @@ class QUIC_fire(InputFile):
         Number of fire grid cells in the z-direction.
     stretch_grid_flag : Literal[0, 1]
         Vertical stretching flag: 0 = uniform dz, 1 = custom
-    dz : PositiveInt
+    dz : PositiveFloat
         Cell size in the z-direction [m] of the fire grid. Recommended value: 1m
     dz_array : List[PositiveFloat]
         Custom dz, one dz per line must be specified, from the ground to the
         top of the domain
-    fuel_density_flag: Literal[1, 2, 3, 4]
+    fuel_density_flag: Literal[1, 2, 3, 4, 5]
         Fuel density flag (defaults to 1):
         1 = density is uniform over the domain,
         2 = density is provided through QF_FuelDensity.inp,
@@ -1672,7 +1763,7 @@ class QUIC_fire(InputFile):
         5 = FastFuels input (assuming uniform dz of 1m)
     fuel_density : PositiveFloat
         Fuel density (kg/m3)
-    fuel_moisture_flag : Literal[1, 2, 3, 4]
+    fuel_moisture_flag : Literal[1, 2, 3, 4, 5]
         Fuel moisture flag (defaults to 1):
         1 = moisture is uniform over the domain,
         2 = moisture is provided through QF_FuelMoisture.inp,
@@ -1706,7 +1797,7 @@ class QUIC_fire(InputFile):
         0 = Default values (0, 0) over entire domain,
         1 = custom uniform values over the domain,
         2 = custom values provided by patch.dat and gap.dat
-    ignition_type: IgnitionType
+    ignition: Ignition
         Ignition type specified as an IgnitionsType class from ignitions.py
         1 = rectangle
         2 = square ring
@@ -1772,11 +1863,11 @@ class QUIC_fire(InputFile):
     out_time_wind_avg: PositiveInt = 30
     nz: PositiveInt
     stretch_grid_flag: Literal[0, 1] = 0
-    dz: PositiveInt = 1
+    dz: PositiveFloat = 1
     dz_array: list[PositiveFloat] = []
-    fuel_density_flag: Literal[1, 2, 3, 4] = 1
+    fuel_density_flag: Literal[1, 2, 3, 4, 5] = 1
     fuel_density: Union[PositiveFloat, None] = 0.5
-    fuel_moisture_flag: Literal[1, 2, 3, 4] = 1
+    fuel_moisture_flag: Literal[1, 2, 3, 4, 5] = 1
     fuel_moisture: Union[PositiveFloat, None] = 0.1
     fuel_height_flag: Literal[1, 2, 3, 4] = 1
     fuel_height: Union[PositiveFloat, None] = 1.0
@@ -1785,8 +1876,8 @@ class QUIC_fire(InputFile):
     patch_and_gap_flag: Literal[0, 1, 2] = 0
     patch_size: NonNegativeFloat = 0
     gap_size: NonNegativeFloat = 0
-    ignition_type: Union[
-        RectangleIgnition, SquareRingIgnition, CircularRingIgnition, IgnitionType
+    ignition: Union[
+        RectangleIgnition, SquareRingIgnition, CircularRingIgnition, Ignition
     ]
     ignitions_per_cell: PositiveInt = 2
     firebrand_flag: Literal[0, 1] = 0
@@ -1846,7 +1937,7 @@ def _stretch_grid_input(self) -> str:
     @computed_field
     @property
     def _ignition_lines(self) -> str:
-        return str(self.ignition_type)
+        return str(self.ignition)
 
     @computed_field
     @property
@@ -1890,6 +1981,12 @@ def _patch_and_gap_lines(self) -> str:
             return patch_and_gap_flag_line + f"\n{self.patch_size}\n{self.gap_size}"
         return patch_and_gap_flag_line
 
+    @classmethod
+    def from_dict(cls, data: dict):
+        if "ignition" in data:
+            data["ignition"] = serialize_ignition(data["ignition"])
+        return cls(**data)
+
     @classmethod
     def from_file(cls, directory: str | Path, **kwargs):
         """
@@ -1904,180 +2001,186 @@ def from_file(cls, directory: str | Path, **kwargs):
         with open(directory / "QUIC_fire.inp", "r") as f:
             lines = f.readlines()
 
-        # Read fire flag and random seed
-        fire_flag = int(lines[0].strip().split("!")[0])
-        random_seed = int(lines[1].strip().split("!")[0])
-
-        # Read fire times
-        time_now = int(lines[3].strip().split("!")[0])
-        sim_time = int(lines[4].strip().split("!")[0])
-        fire_time_step = int(lines[5].strip().split("!")[0])
-        quic_time_step = int(lines[6].strip().split("!")[0])
-        out_time_fire = int(lines[7].strip().split("!")[0])
-        out_time_wind = int(lines[8].strip().split("!")[0])
-        out_time_emis_rad = int(lines[9].strip().split("!")[0])
-        out_time_wind_avg = int(lines[10].strip().split("!")[0])
-
-        # Read fire grid parameters
-        nz = int(lines[12].strip().split("!")[0])
-        stretch_grid_flag = int(lines[13].strip().split("!")[0])
-        dz_array = []
-        if stretch_grid_flag == 0:
-            dz = int(lines[14].strip().split("!")[0])
-            current_line = 15
-        else:
-            for i in range(14, 14 + len(dz_array)):
-                try:
-                    float(lines[i].strip())
-                except ValueError:
-                    print(
-                        "QUIC_fire.inp: dz input value is not a float. Does the number of dz data match nz?"
-                    )
-                dz_array.append(float(lines[i].strip()))
-            current_line = 15 + len(dz_array)
+        try:
+            # Read fire flag and random seed
+            fire_flag = int(lines[0].strip().split()[0])
+            random_seed = int(lines[1].strip().split()[0])
+
+            # Read fire times
+            time_now = int(lines[3].strip().split()[0])
+            sim_time = int(lines[4].strip().split()[0])
+            fire_time_step = int(lines[5].strip().split()[0])
+            quic_time_step = int(lines[6].strip().split()[0])
+            out_time_fire = int(lines[7].strip().split()[0])
+            out_time_wind = int(lines[8].strip().split()[0])
+            out_time_emis_rad = int(lines[9].strip().split()[0])
+            out_time_wind_avg = int(lines[10].strip().split()[0])
+
+            # Read fire grid parameters
+            nz = int(lines[12].strip().split()[0])
+            stretch_grid_flag = int(lines[13].strip().split()[0])
+            dz_array = []
+            if stretch_grid_flag == 0:
+                dz = float(lines[14].strip().split()[0])
+                current_line = 15
+            else:
+                for i in range(14, 14 + len(dz_array)):
+                    try:
+                        float(lines[i].strip())
+                    except ValueError:
+                        print(
+                            "QUIC_fire.inp: dz input value is not a float. Does the number of dz data match nz?"
+                        )
+                    dz_array.append(float(lines[i].strip()))
+                current_line = 15 + len(dz_array)
+
+            current_line += 4  # skip unused lines
+
+            # Read fuel data
+            current_line += 1  # skip !FUEL line
+
+            # Read fuel density
+            fuel_density_flag = int(lines[current_line].strip().split()[0])
+            fuel_density = None
+            if fuel_density_flag == 1:
+                current_line += 1
+                fuel_density = float(lines[current_line].strip().split()[0])
 
-        current_line += 4  # skip unused lines
+            # Read fuel moisture
+            current_line += 1
+            fuel_moisture_flag = int(lines[current_line].strip().split()[0])
+            fuel_moisture = None
+            if fuel_moisture_flag == 1:
+                current_line += 1
+                fuel_moisture = float(lines[current_line].strip().split()[0])
 
-        # Read fuel data
-        current_line += 1  # skip !FUEL line
+            # Read fuel height
+            fuel_height_flag = 1
+            if fuel_density_flag == 1:
+                current_line += 1
+                fuel_height_flag = int(lines[current_line].strip().split()[0])
+            fuel_height = None
+            if fuel_density_flag == 1 and fuel_moisture_flag == 1:
+                current_line += 1
+                fuel_height = float(lines[current_line].strip().split()[0])
+
+            # Read size scale and patch/gap (Supported for v6 and above)
+            if version in ("v6"):
+                # Check if the next line is the ignition header
+                next_line = lines[current_line + 1].strip()
+                if next_line.startswith("! IGNITION LOCATIONS"):
+                    # Trying to read a v5 file with v6 reader so throw error
+                    raise ValueError(
+                        "Invalid file version. Selected reader for QUIC-Fire v6, but file is v5."
+                    )
 
-        # Read fuel density
-        fuel_density_flag = int(lines[current_line].strip().split("!")[0])
-        fuel_density = None
-        if fuel_density_flag == 1:
-            current_line += 1
-            fuel_density = float(lines[current_line].strip())
+                # Read size scale
+                current_line += 1
+                size_scale_flag = int(lines[current_line].strip().split()[0])
+                size_scale = 0.0005
+                if size_scale_flag == 1:
+                    current_line += 1
+                    size_scale = float(lines[current_line].strip().split()[0])
 
-        # Read fuel moisture
-        current_line += 1
-        fuel_moisture_flag = int(lines[current_line].strip().split("!")[0])
-        fuel_moisture = None
-        if fuel_moisture_flag == 1:
-            current_line += 1
-            fuel_moisture = float(lines[current_line].strip())
+                # Read patch and gap
+                current_line += 1
+                patch_and_gap_flag = int(lines[current_line].strip().split()[0])
+                patch_size = 0.0
+                gap_size = 0.0
+                if patch_and_gap_flag == 1:
+                    current_line += 1
+                    patch_size = float(lines[current_line].strip().split()[0])
+                    current_line += 1
+                    gap_size = float(lines[current_line].strip().split()[0])
+            elif version == "v5":
+                # The next line should be the ignition header. If it is not, then
+                # the file is not a v5 file, and we should throw an error.
+                next_line = lines[current_line + 1].strip()
+                if not next_line.startswith("! IGNITION LOCATIONS"):
+                    # Trying to read a v6 file with v5 reader so throw error
+                    raise ValueError(
+                        "Invalid file version. Selected reader for QUIC-Fire v5, but file is v6."
+                    )
 
-        # Read fuel height
-        current_line += 1
-        if fuel_density_flag == 1:
-            fuel_height_flag = int(lines[current_line].strip().split("!")[0])
-        else:
-            fuel_height_flag = fuel_density_flag
-        fuel_height = None
-        if fuel_density_flag == 1 and fuel_moisture_flag == 1:
+                # Set size scale and patch and gap to defaults
+                size_scale_flag = 0
+                size_scale = 0.0005
+                patch_and_gap_flag = 0
+                patch_size = 0.0
+                gap_size = 0.0
+            else:
+                raise ValueError(f"Unsupported version: {version}")
+
+            # Read ignition data
+            current_line += 2  # skip ! IGNITION LOCATIONS header
+            ignition_flag = int(lines[current_line].strip().split()[0])
+            add_lines = {1: 4, 2: 6, 3: 5, 4: 0, 5: 0, 6: 0, 7: 0}
+            add = add_lines.get(ignition_flag)
+            ignition_params = []
             current_line += 1
-            fuel_height = float(lines[current_line].strip())
-
-        # Read size scale and patch/gap (Supported for v6 and above)
-        if version in ("v6"):
-            # Check if the next line is the ignition header
-            next_line = lines[current_line + 1].strip()
-            if next_line.startswith("! IGNITION LOCATIONS"):
-                # Trying to read a v5 file with v6 reader so throw error
-                raise ValueError(
-                    "Invalid file version. Selected reader for QUIC-Fire v6, but file is v5."
+            for i in range(current_line, current_line + add):
+                ignition_line = float(lines[i].split()[0].strip())
+                ignition_params.append(ignition_line)
+            if ignition_flag == 1:
+                x_min, y_min, x_length, y_length = ignition_params
+                ignition = RectangleIgnition(
+                    x_min=x_min, y_min=y_min, x_length=x_length, y_length=y_length
+                )
+            elif ignition_flag == 2:
+                x_min, y_min, x_length, y_length, x_width, y_width = ignition_params
+                ignition = SquareRingIgnition(
+                    x_min=x_min,
+                    y_min=y_min,
+                    x_length=x_length,
+                    y_length=y_length,
+                    x_width=x_width,
+                    y_width=y_width,
                 )
+            elif ignition_flag == 3:
+                x_min, y_min, x_length, y_length, ring_width = ignition_params
+                ignition = CircularRingIgnition(
+                    x_min=x_min,
+                    y_min=y_min,
+                    x_length=x_length,
+                    y_length=y_length,
+                    ring_width=ring_width,
+                )
+            else:
+                ignition = Ignition(ignition_flag=ignition_flag)
 
-            # Read size scale
+            current_line += add
+            ignitions_per_cell = int(lines[current_line].strip().split()[0])
             current_line += 1
-            size_scale_flag = int(lines[current_line].strip().split("!")[0])
-            size_scale = 0.0005
-            if size_scale_flag == 1:
-                current_line += 1
-                size_scale = float(lines[current_line].strip())
 
-            # Read patch and gap
+            # Read firebrands
+            # current_line = ! FIREBRANDS
+            current_line += 1  # header
+            firebrand_flag = int(lines[current_line].strip().split()[0])
             current_line += 1
-            patch_and_gap_flag = int(lines[current_line].strip().split("!")[0])
-            patch_size = 0.0
-            gap_size = 0.0
-            if patch_and_gap_flag == 1:
-                current_line += 1
-                patch_size = float(lines[current_line].strip())
-                current_line += 1
-                gap_size = float(lines[current_line].strip())
-        elif version == "v5":
-            # The next line should be the ignition header. If it is not, then
-            # the file is not a v5 file, and we should throw an error.
-            next_line = lines[current_line + 1].strip()
-            if not next_line.startswith("! IGNITION LOCATIONS"):
-                # Trying to read a v6 file with v5 reader so throw error
-                raise ValueError(
-                    "Invalid file version. Selected reader for QUIC-Fire v5, but file is v6."
-                )
 
-            # Set size scale and patch and gap to defaults
-            size_scale_flag = 0
-            size_scale = 0.0005
-            patch_and_gap_flag = 0
-            patch_size = 0.0
-            gap_size = 0.0
-        else:
-            raise ValueError(f"Unsupported version: {version}")
-
-        # Read ignition data
-        current_line += 2  # skip ! IGNITION LOCATIONS header
-        ignition_flag = int(lines[current_line].strip().split("!")[0])
-        add_lines = {1: 4, 2: 6, 3: 5, 4: 0, 5: 0, 6: 0, 7: 0}
-        add = add_lines.get(ignition_flag)
-        ignition_params = []
-        current_line += 1
-        for i in range(current_line, current_line + add):
-            ignition_line = float(lines[i].split("!")[0].strip())
-            ignition_params.append(ignition_line)
-        if ignition_flag == 1:
-            x_min, y_min, x_length, y_length = ignition_params
-            ignition_type = RectangleIgnition(
-                x_min=x_min, y_min=y_min, x_length=x_length, y_length=y_length
-            )
-        elif ignition_flag == 2:
-            x_min, y_min, x_length, y_length, x_width, y_width = ignition_params
-            ignition_type = SquareRingIgnition(
-                x_min=x_min,
-                y_min=y_min,
-                x_length=x_length,
-                y_length=y_length,
-                x_width=x_width,
-                y_width=y_width,
-            )
-        elif ignition_flag == 3:
-            x_min, y_min, x_length, y_length, ring_width = ignition_params
-            ignition_type = CircularRingIgnition(
-                x_min=x_min,
-                y_min=y_min,
-                x_length=x_length,
-                y_length=y_length,
-                ring_width=ring_width,
+            # Read output flags
+            # current_line = !OUTPUT_FILES
+            eng_to_atm_out = int(lines[current_line + 1].strip().split()[0])
+            react_rate_out = int(lines[current_line + 2].strip().split()[0])
+            fuel_dens_out = int(lines[current_line + 3].strip().split()[0])
+            qf_wind_out = int(lines[current_line + 4].strip().split()[0])
+            qu_wind_inst_out = int(lines[current_line + 5].strip().split()[0])
+            qu_wind_avg_out = int(lines[current_line + 6].strip().split()[0])
+            # ! Output plume trajectories
+            fuel_moist_out = int(lines[current_line + 8].strip().split()[0])
+            mass_burnt_out = int(lines[current_line + 9].strip().split()[0])
+            firebrand_out = int(lines[current_line + 10].strip().split()[0])
+            emissions_out = int(lines[current_line + 11].strip().split()[0])
+            radiation_out = int(lines[current_line + 12].strip().split()[0])
+            intensity_out = int(lines[current_line + 13].strip().split()[0])
+            # ! AUTOKILL
+            auto_kill = int(lines[current_line + 15].strip().split()[0])
+
+        except IndexError:
+            raise ValueError(
+                f"Error parsing {cls.__name__} from file: QUIC_fire.inp"
+                f"\nPlease check the file for correctness."
             )
-        else:
-            ignition_type = IgnitionType(ignition_flag=ignition_flag)
-
-        current_line += add
-        ignitions_per_cell = int(lines[current_line].strip().split("!")[0])
-        current_line += 1
-
-        # Read firebrands
-        # current_line = ! FIREBRANDS
-        current_line += 1  # header
-        firebrand_flag = int(lines[current_line].strip().split("!")[0])
-        current_line += 1
-
-        # Read output flags
-        # current_line = !OUTPUT_FILES
-        eng_to_atm_out = int(lines[current_line + 1].strip().split("!")[0])
-        react_rate_out = int(lines[current_line + 2].strip().split("!")[0])
-        fuel_dens_out = int(lines[current_line + 3].strip().split("!")[0])
-        qf_wind_out = int(lines[current_line + 4].strip().split("!")[0])
-        qu_wind_inst_out = int(lines[current_line + 5].strip().split("!")[0])
-        qu_wind_avg_out = int(lines[current_line + 6].strip().split("!")[0])
-        # ! Output plume trajectories
-        fuel_moist_out = int(lines[current_line + 8].strip().split("!")[0])
-        mass_burnt_out = int(lines[current_line + 9].strip().split("!")[0])
-        firebrand_out = int(lines[current_line + 10].strip().split("!")[0])
-        emissions_out = int(lines[current_line + 11].strip().split("!")[0])
-        radiation_out = int(lines[current_line + 12].strip().split("!")[0])
-        intensity_out = int(lines[current_line + 13].strip().split("!")[0])
-        # ! AUTOKILL
-        auto_kill = int(lines[current_line + 15].strip().split("!")[0])
 
         return cls(
             fire_flag=fire_flag,
@@ -2105,7 +2208,7 @@ def from_file(cls, directory: str | Path, **kwargs):
             patch_and_gap_flag=patch_and_gap_flag,
             patch_size=patch_size,
             gap_size=gap_size,
-            ignition_type=ignition_type,
+            ignition=ignition,
             ignitions_per_cell=ignitions_per_cell,
             firebrand_flag=firebrand_flag,
             eng_to_atm_out=eng_to_atm_out,
@@ -2179,16 +2282,22 @@ def from_file(cls, directory: str | Path, **kwargs):
             directory = Path(directory)
         with open(directory / "QFire_Bldg_Advanced_User_Inputs.inp", "r") as f:
             lines = f.readlines()
-        return cls(
-            convert_buildings_to_fuel_flag=int(lines[0].split()[0]),
-            building_fuel_density=float(lines[1].split()[0]),
-            building_attenuation_coefficient=float(lines[2].split()[0]),
-            building_surface_roughness=float(lines[3].split()[0]),
-            convert_fuel_to_canopy_flag=int(lines[4].split()[0]),
-            update_canopy_winds_flag=int(lines[5].split()[0]),
-            fuel_attenuation_coefficient=float(lines[6].split()[0]),
-            fuel_surface_roughness=float(lines[7].split()[0]),
-        )
+        try:
+            return cls(
+                convert_buildings_to_fuel_flag=int(lines[0].split()[0]),
+                building_fuel_density=float(lines[1].split()[0]),
+                building_attenuation_coefficient=float(lines[2].split()[0]),
+                building_surface_roughness=float(lines[3].split()[0]),
+                convert_fuel_to_canopy_flag=int(lines[4].split()[0]),
+                update_canopy_winds_flag=int(lines[5].split()[0]),
+                fuel_attenuation_coefficient=float(lines[6].split()[0]),
+                fuel_surface_roughness=float(lines[7].split()[0]),
+            )
+        except IndexError:
+            raise ValueError(
+                f"Error parsing {cls.__name__} from file: QFire_Bldg_Advanced_User_Inputs.inp"
+                f"\nPlease check the file for correctness."
+            )
 
 
 class QFire_Plume_Advanced_User_Inputs(InputFile):
@@ -2276,25 +2385,30 @@ def from_file(cls, directory: str | Path, **kwargs):
 
         with open(directory / "QFire_Plume_Advanced_User_Inputs.inp", "r") as f:
             lines = f.readlines()
-
-        return cls(
-            max_plumes_per_timestep=int(lines[0].split()[0]),
-            min_plume_updraft_velocity=float(lines[1].split()[0]),
-            max_plume_updraft_velocity=float(lines[2].split()[0]),
-            min_velocity_ratio=float(lines[3].split()[0]),
-            brunt_vaisala_freq_squared=float(lines[4].split()[0]),
-            creeping_flag=int(lines[5].split()[0]),
-            adaptive_timestep_flag=int(lines[6].split()[0]),
-            plume_timestep=float(lines[7].split()[0]),
-            sor_option_flag=int(lines[8].split()[0]),
-            sor_alpha_plume_center=float(lines[9].split()[0]),
-            sor_alpha_plume_edge=float(lines[10].split()[0]),
-            max_plume_merging_angle=float(lines[11].split()[0]),
-            max_plume_overlap_fraction=float(lines[12].split()[0]),
-            plume_to_grid_updrafts_flag=int(lines[13].split()[0]),
-            max_points_along_plume_edge=int(lines[14].split()[0]),
-            plume_to_grid_intersection_flag=int(lines[15].split()[0]),
-        )
+        try:
+            return cls(
+                max_plumes_per_timestep=int(lines[0].split()[0]),
+                min_plume_updraft_velocity=float(lines[1].split()[0]),
+                max_plume_updraft_velocity=float(lines[2].split()[0]),
+                min_velocity_ratio=float(lines[3].split()[0]),
+                brunt_vaisala_freq_squared=float(lines[4].split()[0]),
+                creeping_flag=int(lines[5].split()[0]),
+                adaptive_timestep_flag=int(lines[6].split()[0]),
+                plume_timestep=float(lines[7].split()[0]),
+                sor_option_flag=int(lines[8].split()[0]),
+                sor_alpha_plume_center=float(lines[9].split()[0]),
+                sor_alpha_plume_edge=float(lines[10].split()[0]),
+                max_plume_merging_angle=float(lines[11].split()[0]),
+                max_plume_overlap_fraction=float(lines[12].split()[0]),
+                plume_to_grid_updrafts_flag=int(lines[13].split()[0]),
+                max_points_along_plume_edge=int(lines[14].split()[0]),
+                plume_to_grid_intersection_flag=int(lines[15].split()[0]),
+            )
+        except IndexError:
+            raise ValueError(
+                f"Error parsing {cls.__name__} from file: QFire_Plume_Advanced_User_Inputs.inp"
+                f"\nPlease check the file for correctness."
+            )
 
 
 class QU_TopoInputs(InputFile):
@@ -2306,7 +2420,7 @@ class QU_TopoInputs(InputFile):
     ----------
     filename : str
         Path to the custom topo file (only used with option 5). Cannot be .bin. Use .dat or .inp
-    topo_type : TopoType
+    topography : Topography
         Topography type specified as a TopoType class from topography.py
         0 = no terrain file provided, QUIC-Fire is run with flat terrain
         1 = Gaussian hill
@@ -2320,27 +2434,27 @@ class QU_TopoInputs(InputFile):
         9 = terrain is provided via QP_elevation.bin (see Section 2.7)
         10 = terrain is provided via terrainOutput.txt
         11 = terrain.dat (firetec)
-    smoothing_method : int
+    smoothing_method : Literal[0, 1, 2]
         0 = none (default for idealized topo)
         1 = Blur
         2 = David Robinson’s method based on second derivative
-    smoothing_passes : int
+    smoothing_passes : NonNegativeInt
         Number of smoothing passes. Real terrain MUST be smoothed
-    sor_iterations : int
+    sor_iterations : PositiveInt
         Number of SOR iteration to define background winds before starting the fire
-    sor_cycles : int
+    sor_cycles : Literal[0, 1, 2, 3, 4]
         Number of times the SOR solver initial fields is reset to define
         background winds before starting the fire
-    sor_relax : float
+    sor_relax : PositiveFloat
         SOR overrelaxation coefficient. Only used if there is topo.
     """
 
     name: str = "QU_TopoInputs"
     _extension: str = ".inp"
     filename: str = "topo.dat"
-    topo_type: SerializeAsAny[TopoType] = TopoType(topo_flag=TopoSources(0))
+    topography: SerializeAsAny[Topography] = Topography(topo_flag=TopoFlags(0))
     smoothing_method: Literal[0, 1, 2] = 2
-    smoothing_passes: PositiveInt = Field(le=500, default=500)
+    smoothing_passes: NonNegativeInt = Field(le=500, default=500)
     sor_iterations: PositiveInt = Field(le=500, default=200)
     sor_cycles: Literal[0, 1, 2, 3, 4] = 4
     sor_relax: PositiveFloat = Field(le=2, default=0.9)
@@ -2348,7 +2462,13 @@ class QU_TopoInputs(InputFile):
     @computed_field
     @property
     def _topo_lines(self) -> str:
-        return str(self.topo_type)
+        return str(self.topography)
+
+    @classmethod
+    def from_dict(cls, data: dict):
+        if "topography" in data:
+            data["topography"] = serialize_topography(data["topography"])
+        return cls(**data)
 
     @classmethod
     def from_file(cls, directory: str | Path, **kwargs):
@@ -2357,81 +2477,95 @@ def from_file(cls, directory: str | Path, **kwargs):
         with open(directory / "QU_TopoInputs.inp", "r") as f:
             lines = f.readlines()
 
-        # Line 0 is Header
-        filename = str(lines[1].strip())
-        # Get topo lines
-        topo_flag = int(lines[2].strip().split("!")[0])
-        add_dict = {
-            0: 0,
-            1: 4,
-            2: 2,
-            3: 3,
-            4: 5,
-            5: 0,
-            6: 3,
-            7: 2,
-            8: 2,
-            9: 0,
-            10: 0,
-            11: 0,
-        }
-        add = add_dict.get(topo_flag)
-        topo_params = []
-        for i in range(3, 3 + add):
-            topo_params.append(float(lines[i].strip().split("!")[0]))
-        if topo_flag == 1:
-            x_hilltop, y_hilltop, elevation_max, elevation_std = topo_params
-            topo_type = GaussianHillTopo(
-                x_hilltop=int(x_hilltop),
-                y_hilltop=int(y_hilltop),
-                elevation_max=int(elevation_max),
-                elevation_std=elevation_std,
-            )
-        elif topo_flag == 2:
-            max_height, location_param = topo_params
-            topo_type = HillPassTopo(
-                max_height=int(max_height), location_param=location_param
-            )
-        elif topo_flag == 3:
-            slope_axis, slope_value, flat_fraction = topo_params
-            topo_type = SlopeMesaTopo(
-                slope_axis=int(slope_axis),
-                slope_value=slope_value,
-                flat_fraction=flat_fraction,
-            )
-        elif topo_flag == 4:
-            x_start, y_center, slope_value, canyon_std, vertical_offset = topo_params
-            topo_type = CanyonTopo(
-                x_start=int(x_start),
-                y_center=int(y_center),
-                sloe_value=slope_value,
-                canyon_std=canyon_std,
-                vertical_offset=vertical_offset,
-            )
-        elif topo_flag == 6:
-            x_location, y_location, radius = topo_params
-            topo_type = HalfCircleTopo(
-                x_location=int(x_location), y_location=int(y_location), radius=radius
+        try:
+            # Line 0 is Header
+            filename = str(lines[1].strip())
+            # Get topo lines
+            topo_flag = int(lines[2].strip().split()[0])
+            add_dict = {
+                0: 0,
+                1: 4,
+                2: 2,
+                3: 3,
+                4: 5,
+                5: 0,
+                6: 3,
+                7: 2,
+                8: 2,
+                9: 0,
+                10: 0,
+                11: 0,
+            }
+            add = add_dict.get(topo_flag)
+            topo_params = []
+            for i in range(3, 3 + add):
+                topo_params.append(float(lines[i].strip().split()[0]))
+            if topo_flag == 1:
+                x_hilltop, y_hilltop, elevation_max, elevation_std = topo_params
+                topography = GaussianHillTopo(
+                    x_hilltop=int(x_hilltop),
+                    y_hilltop=int(y_hilltop),
+                    elevation_max=int(elevation_max),
+                    elevation_std=elevation_std,
+                )
+            elif topo_flag == 2:
+                max_height, location_param = topo_params
+                topography = HillPassTopo(
+                    max_height=int(max_height), location_param=location_param
+                )
+            elif topo_flag == 3:
+                slope_axis, slope_value, flat_fraction = topo_params
+                topography = SlopeMesaTopo(
+                    slope_axis=int(slope_axis),
+                    slope_value=slope_value,
+                    flat_fraction=flat_fraction,
+                )
+            elif topo_flag == 4:
+                (
+                    x_start,
+                    y_center,
+                    slope_value,
+                    canyon_std,
+                    vertical_offset,
+                ) = topo_params
+                topography = CanyonTopo(
+                    x_location=int(x_start),
+                    y_location=int(y_center),
+                    slope_value=slope_value,
+                    canyon_std=canyon_std,
+                    vertical_offset=vertical_offset,
+                )
+            elif topo_flag == 6:
+                x_location, y_location, radius = topo_params
+                topography = HalfCircleTopo(
+                    x_location=int(x_location),
+                    y_location=int(y_location),
+                    radius=radius,
+                )
+            elif topo_flag == 7:
+                period, amplitude = topo_params
+                topography = SinusoidTopo(period=period, amplitude=amplitude)
+            elif topo_flag == 8:
+                aspect, height = topo_params
+                topography = CosHillTopo(aspect=aspect, height=height)
+            else:
+                topography = Topography(topo_flag=topo_flag)
+            current_line = 3 + add
+            # Smoothing and SOR
+            smoothing_method = int(lines[current_line].strip().split()[0])
+            smoothing_passes = int(lines[current_line + 1].strip().split()[0])
+            sor_iterations = int(lines[current_line + 2].strip().split()[0])
+            sor_cycles = int(lines[current_line + 3].strip().split()[0])
+            sor_relax = float(lines[current_line + 4].strip().split()[0])
+        except IndexError:
+            raise ValueError(
+                f"Error parsing {cls.__name__} from file: QU_TopoInputs.inp"
+                f"\nPlease check the file for correctness."
             )
-        elif topo_flag == 7:
-            period, amplitude = topo_params
-            topo_type = SinusoidTopo(period=period, amplitude=amplitude)
-        elif topo_flag == 8:
-            aspect, height = topo_params
-            topo_type = CosHillTopo(aspect=aspect, height=height)
-        else:
-            topo_type = TopoType(topo_flag=topo_flag)
-        current_line = 3 + add
-        # Smoothing and SOR
-        smoothing_method = int(lines[current_line].strip().split("!")[0])
-        smoothing_passes = int(lines[current_line + 1].strip().split("!")[0])
-        sor_iterations = int(lines[current_line + 2].strip().split("!")[0])
-        sor_cycles = int(lines[current_line + 3].strip().split("!")[0])
-        sor_relax = float(lines[current_line + 4].strip().split("!")[0])
 
         return cls(
             filename=filename,
-            topo_type=topo_type,
+            topography=topography,
             smoothing_method=smoothing_method,
             smoothing_passes=smoothing_passes,
             sor_iterations=sor_iterations,
@@ -2465,11 +2599,16 @@ def from_file(cls, directory: str | Path, **kwargs):
             directory = Path(directory)
         with open(directory / "Runtime_Advanced_User_Inputs.inp", "r") as f:
             lines = f.readlines()
-
-        return cls(
-            num_cpus=int(lines[0].strip().split("!")[0]),
-            use_acw=int(lines[1].strip().split("!")[0]),
-        )
+        try:
+            return cls(
+                num_cpus=int(lines[0].strip().split()[0]),
+                use_acw=int(lines[1].strip().split()[0]),
+            )
+        except IndexError:
+            raise ValueError(
+                f"Error parsing {cls.__name__} from file: Runtime_Advanced_User_Inputs.inp"
+                f"\nPlease check the file for correctness."
+            )
 
 
 class QU_movingcoords(InputFile):
@@ -2481,21 +2620,6 @@ class QU_movingcoords(InputFile):
     name: str = "QU_movingcoords"
     _extension: str = ".inp"
 
-    @classmethod
-    def from_file(cls, directory: str | Path, **kwargs):
-        if isinstance(directory, str):
-            directory = Path(directory)
-
-        with open(directory / "QU_movingcoords.inp", "r") as f:
-            lines = f.readlines()
-
-        if int(lines[1].strip().split("!")[0]) == 1:
-            print(
-                "WARNING: QU_movingcoords.inp: Moving coordinates flag == 1 not supported."
-            )
-
-        return cls()
-
 
 class QP_buildout(InputFile):
     """
@@ -2506,23 +2630,6 @@ class QP_buildout(InputFile):
     name: str = "QP_buildout"
     _extension: str = ".inp"
 
-    @classmethod
-    def from_file(cls, directory: str | Path, **kwargs):
-        if isinstance(directory, str):
-            directory = Path(directory)
-
-        with open(directory / "QP_buildout.inp", "r") as f:
-            lines = f.readlines()
-
-        if int(lines[0].strip().split("!")[0]) == 1:
-            print("WARNING: QP_buildout.inp: number of buildings will be set to 0.")
-        if int(lines[1].strip().split("!")[0]) == 1:
-            print(
-                "WARNING: QP_buildout.inp: number of vegetative canopies will be set to 0."
-            )
-
-        return cls()
-
 
 class QU_metparams(InputFile):
     """
@@ -2557,15 +2664,21 @@ def from_file(cls, directory: str | Path, **kwargs):
             directory = Path(directory)
         with open(directory / "QU_metparams.inp", "r") as f:
             lines = f.readlines()
-        sensor_name = str(lines[4].strip().split()[0].strip())
-        if sensor_name != "sensor1":
-            print(
-                f"WARNING: wind sensor files must be named 'sensor*.inp'\n"
-                f"Current sensor name = {sensor_name}"
+        try:
+            sensor_name = str(lines[4].strip().split()[0].strip())
+            if sensor_name != "sensor1":
+                print(
+                    f"WARNING: wind sensor files must be named 'sensor*.inp'\n"
+                    f"Current sensor name = {sensor_name}"
+                )
+            return cls(
+                num_sensors=int(lines[2].strip().split()[0]),
+            )
+        except IndexError:
+            raise ValueError(
+                f"Error parsing {cls.__name__} from file: QU_metparams.inp"
+                f"\nPlease check the file for correctness."
             )
-        return cls(
-            num_sensors=int(lines[2].strip().split()[0]),
-        )
 
 
 class WindSensor(BaseModel, validate_assignment=True):
@@ -2691,20 +2804,32 @@ def from_file(cls, directory: str | Path, sensor_name: str):
         wind_times = [0]
         wind_speeds = []
         wind_directions = []
-        x_location = int(lines[4].strip().split("!")[0])
-        y_location = int(lines[5].strip().split("!")[0])
-        time_now = float(lines[6].strip().split("!")[0])
-        sensor_height = float(lines[11].split(" ")[0])
-        wind_speeds.append(float(lines[11].split(" ")[1]))
-        wind_directions.append(int(float(lines[11].split(" ")[2].strip())))
-        next_shift = 12
-        while (next_shift + 6) <= len(lines):
-            wind_times.append(float(lines[next_shift].strip().split("!")[0]) - time_now)
-            wind_speeds.append(float(lines[next_shift + 5].split(" ")[1]))
-            wind_directions.append(
-                int(float(lines[next_shift + 5].split(" ")[2].strip()))
+
+        try:
+            x_location = int(lines[4].strip().split()[0])
+            y_location = int(lines[5].strip().split()[0])
+            time_now = float(lines[6].strip().split()[0])
+            wind_line = lines[11].strip()
+            sensor_height = float(wind_line.split()[0])
+            wind_speed = float(wind_line.split()[1])
+            wind_speeds.append(wind_speed)
+            wind_direction = int(wind_line.split()[2])
+            wind_directions.append(wind_direction)
+            next_shift = 12
+            while (next_shift + 6) <= len(lines):
+                wind_time_line = lines[next_shift].strip()
+                wind_times.append(float(wind_time_line.split()[0]) - time_now)
+                wind_line = lines[next_shift + 5].strip()
+                wind_speeds.append(float(wind_line.split()[1]))
+                wind_directions.append(int(wind_line.split()[2].strip()))
+                next_shift += 6
+
+        except IndexError:
+            raise ValueError(
+                f"Error parsing {cls.__name__} from file: {sensor_file}"
+                f"\nPlease check the file for correctness."
             )
-            next_shift += 6
+
         return cls(
             name=sensor_name,
             sensor_height=sensor_height,
@@ -2802,6 +2927,9 @@ def wind_times(self) -> list:
         )
         return combined_times
 
+    def __len__(self):
+        return len(self.sensor_array)
+
     def __getattr__(self, name):
         for sensor in self.sensor_array:
             if name == sensor.name:
diff --git a/quicfire_tools/outputs.py b/quicfire_tools/outputs.py
index 2ba9b6e..ebf5b70 100644
--- a/quicfire_tools/outputs.py
+++ b/quicfire_tools/outputs.py
@@ -6,162 +6,27 @@
 
 # Core imports
 import re
+import json
 from pathlib import Path
+import importlib.resources
+
+# Internal imports
+from quicfire_tools.inputs import SimulationInputs
 
 # External imports
 import zarr
 import numpy as np
+import pyproj as proj
 import dask.array as da
 from numpy import ndarray
-from shutil import rmtree
-
-FUELS_OUTPUTS = {
-    "fire-energy_to_atmos": {
-        "file_format": "compressed",
-        "dimensions": ["z", "y", "x"],
-        "grid": "fire",
-        "delimiter": "-",
-        "extension": ".bin",
-        "description": "Energy released to the atmosphere that generates "
-        "buoyant plumes",
-        "units": "kW",
-    },
-    "fire-reaction_rate": {
-        "file_format": "compressed",
-        "dimensions": ["z", "y", "x"],
-        "grid": "fire",
-        "delimiter": "-",
-        "extension": ".bin",
-        "description": "Rate of reaction of fuel",
-        "units": "kg/m^3/s",
-    },
-    "fuels-dens": {
-        "file_format": "compressed",
-        "dimensions": ["z", "y", "x"],
-        "grid": "fire",
-        "delimiter": "-",
-        "extension": ".bin",
-        "description": "Fuel density",
-        "units": "kg/m^3",
-    },
-    "fuels-moist": {
-        "file_format": "compressed",
-        "dimensions": ["z", "y", "x"],
-        "grid": "fire",
-        "delimiter": "-",
-        "extension": ".bin",
-        "description": "Fuel moisture content",
-        "units": "g water / g air",
-    },
-    "groundfuelheight": {
-        "file_format": "gridded",
-        "dimensions": ["y", "x"],
-        "grid": "fire",
-        "delimiter": None,
-        "extension": ".bin",
-        "description": "2D array with initial fuel height in the ground layer",
-        "units": "m",
-    },
-    "mburnt_integ": {
-        "file_format": "gridded",
-        "dimensions": ["y", "x"],
-        "grid": "fire",
-        "delimiter": "-",
-        "extension": ".bin",
-        "description": "2D file containing the percentage of mass burnt for each (i,j) "
-        "location on the fire grid (vertically integrated)",
-        "units": "%",
-    },
-    "surfEnergy": {
-        "file_format": "gridded",
-        "dimensions": ["y", "x"],
-        "grid": "fire",
-        "delimiter": None,
-        "extension": ".bin",
-        "description": "Array contains total energy output by each surface cell "
-        "on a per second interval.",
-        "units": "kW/m^2",
-    },
-}
-THERMAL_RADIATION_OUTPUTS = {
-    "thermaldose": {
-        "file_format": "compressed",
-        "dimensions": ["z", "y", "x"],
-        "grid": "fire",
-        "delimiter": "-",
-        "extension": ".bin",
-        "description": "The output shows the thermal flux to skin of a person "
-        "collocated with fuel (for health effects).",
-        "units": "(kW/m^2)^(4/3)s",
-    },
-    "thermalradiation": {
-        "file_format": "compressed",
-        "dimensions": ["z", "y", "x"],
-        "grid": "fire",
-        "delimiter": "-",
-        "extension": ".bin",
-        "description": "The output shows the thermal flux to skin of a person "
-        "collocated with fuel (for health effects).",
-        "units": "kW/m^2",
-    },
-}
-WIND_OUTPUTS = {
-    "windu": {
-        "file_format": "gridded",
-        "dimensions": ["z", "y", "x"],
-        "grid": "fire",
-        "delimiter": "",
-        "extension": ".bin",
-        "description": "Fire u-components, cell centered ",
-        "units": "m/s",
-    },
-    "windv": {
-        "file_format": "gridded",
-        "dimensions": ["z", "y", "x"],
-        "grid": "fire",
-        "delimiter": "",
-        "extension": ".bin",
-        "description": "Fire v-components, cell centered ",
-        "units": "m/s",
-    },
-    "windw": {
-        "file_format": "gridded",
-        "dimensions": ["z", "y", "x"],
-        "grid": "fire",
-        "delimiter": "",
-        "extension": ".bin",
-        "description": "Fire w-components, cell centered ",
-        "units": "m/s",
-    },
-}
-EMISSIONS_OUTPUTS = {
-    "co_emissions": {
-        "file_format": "compressed",
-        "dimensions": ["z", "y", "x"],
-        "grid": "fire",
-        "delimiter": "-",
-        "extension": ".bin",
-        "description": "Mass of CO emitted between two emission file output"
-        "times in grams",
-        "units": "g",
-    },
-    "pm_emissions": {
-        "file_format": "compressed",
-        "dimensions": ["z", "y", "x"],
-        "grid": "fire",
-        "delimiter": "-",
-        "extension": ".bin",
-        "description": "Mass of PM2.5 emitted between two emission file output"
-        "times in grams",
-        "units": "g",
-    },
-}
-OUTPUTS_MAP = {
-    **FUELS_OUTPUTS,
-    **THERMAL_RADIATION_OUTPUTS,
-    **WIND_OUTPUTS,
-    **EMISSIONS_OUTPUTS,
-}
+from netCDF4 import Dataset
+
+
+OUTPUTS_DIR_PATH = (
+    importlib.resources.files("quicfire_tools").joinpath("data").joinpath("outputs")
+)
+with open(OUTPUTS_DIR_PATH.joinpath("outputs.json")) as f:
+    OUTPUTS_MAP = json.load(f)
 
 
 class OutputFile:
@@ -179,9 +44,20 @@ class OutputFile:
     dimensions: list[str]
         The dimensions of the output file as ["z", "y", "x"] or ["y", "x"].
     shape: tuple
-        The shape of the output file array data as (time, nz, ny, nx).
+        The shape of the output file array data as (nz, ny, nx).
     grid: str
         The grid type of the output file. Valid options are "fire" and "quic".
+    x_coords: list[float]
+        The x coordinates of the output file. Coordinates are in the UTM
+        coordinate system with the origin at the southwest corner of the
+        domain.
+    y_coords: list[float]
+        The y coordinates of the output file. Coordinates are in the UTM
+        coordinate system with the origin at the southwest corner of the
+        domain.
+    z_coords: list[float]
+        The z coordinates of the output file. Coordinates are in meters above
+        the ground level.
     delimiter: str
         The delimiter used in the output file name.
     extension: str
@@ -190,6 +66,8 @@ class OutputFile:
         A description of the output file.
     units: str
         The units of the output file.
+    filepaths: list[Path]
+        A list of file paths for each timestep.
     times: list[int]
         A list of times corresponding to the timesteps.
     """
@@ -199,25 +77,34 @@ def __init__(
         name: str,
         file_format: str,
         dimensions: list[str],
-        shape: tuple,
         grid: str,
+        x_coords: list[float],
+        y_coords: list[float],
+        z_coords: list[float],
         delimiter: str,
         extension: str,
         description: str,
         units: str,
+        times: list[int],
+        filepaths: list[Path],
         index_map=None,
+        crs: str = None,
     ):
         self.name = name
         self.file_format = file_format
         self.dimensions = dimensions
-        self.shape = shape
+        self.x_coords = x_coords
+        self.y_coords = y_coords
+        self.z_coords = z_coords
+        self.crs = crs
+        self.shape = (len(z_coords), len(y_coords), len(x_coords))
         self.grid = grid
         self.delimiter = delimiter
         self.extension = extension
         self.description = description
         self.units = units
-        self.times = []  # List of times corresponding to the timesteps
-        self.filepaths = []  # List of file paths for each timestep
+        self.times = times
+        self.filepaths = filepaths
         function_mappings = {
             "gridded": _process_gridded_bin,
             "compressed": _process_compressed_bin,
@@ -229,7 +116,45 @@ def __init__(
             index_map if (self.file_format == "compressed") else None
         )
 
-    def to_numpy(self, timestep: int | list[int] = None) -> np.ndarray:
+    def __eq__(self, other):
+        self_dict = self.__dict__.copy()
+        other_dict = other.__dict__.copy()
+        for key in self_dict.keys():
+            try:
+                if self_dict[key] != other_dict[key]:
+                    return False
+            except KeyError:
+                return False
+            except ValueError:
+                if np.any(self_dict[key] != other_dict[key]):
+                    return False
+        return True
+
+    def _select_files_based_on_timestep(
+        self, timestep: int | list[int] | range | None
+    ) -> list[Path]:
+        """Return files selected based on timestep."""
+        if timestep is None:
+            return self.filepaths
+        if isinstance(timestep, int):
+            timestep = [timestep]
+        try:
+            return [self.filepaths[ts] for ts in timestep]
+        except IndexError:
+            raise ValueError(f"Invalid timestep: {timestep}")
+
+    def _get_single_timestep(self, output_file: Path) -> np.ndarray:
+        """Return a numpy array for the given output file."""
+        return self._output_function(
+            output_file, self.shape, self._compressed_index_map
+        )
+
+    def _get_multiple_timesteps(self, output_files: list[Path]) -> np.ndarray:
+        """Return a numpy array for the given output files."""
+        arrays = [self._get_single_timestep(of) for of in output_files]
+        return arrays[0] if len(arrays) == 1 else np.concatenate(arrays, axis=0)
+
+    def to_numpy(self, timestep: int | list[int] | range = None) -> np.ndarray:
         """
         Return a numpy array for the given output and timestep(s) with shape
         (time, nz, ny, nx). If timestep is None, then all timesteps are
@@ -256,43 +181,200 @@ def to_numpy(self, timestep: int | list[int] = None) -> np.ndarray:
         >>> # Get the first timestep for the fire-energy_to_atmos output
         >>> fire_energy_slice = fire_energy.to_numpy(timestep=0)
         """
-        self._validate_timestep(timestep)
         selected_files = self._select_files_based_on_timestep(timestep)
         return self._get_multiple_timesteps(selected_files)
 
-    def _validate_timestep(self, timestep: int | list[int] | None) -> None:
-        """Validate the timestep input."""
+    def to_netcdf(self, directory: str | Path, timestep: int | list[int] = None):
+        """
+        Write a netCDF file for the given output and timestep(s) with dimensions
+        (time, nz, ny, nx). If timestep is None, then all timesteps are
+        returned.
+
+        Parameters
+        ----------
+        directory: str | Path
+            The path to the folder where netCDF file will be written.
+        timestep: int | list[int] | None
+            The timestep(s) to write. If None, then all timesteps are written.
+
+        Builds
+        -------
+        netCDF file to disk
+
+        Examples
+        --------
+        >>> import quicfire_tools as qft
+        >>> outputs = qft.SimulationOutputs("path/to/outputs", 50, 100, 100)
+        >>> fire_energy = outputs.get_output("fire-energy_to_atmos")
+        >>> out_dir = Path(path/to/output/dir)
+        >>> # Get all timesteps for the fire-energy_to_atmos output
+        >>> fire_energy_all = fire_energy.to_netcdf(directory = out_dir)
+        >>> # Get the first timestep for the fire-energy_to_atmos output
+        >>> fire_energy_slice = fire_energy.to_netcdf(directory = out_dir, timestep=0)
+        """
+        if isinstance(directory, str):
+            directory = Path(directory)
+
+        if not directory.exists():
+            directory.mkdir(parents=True)
+
         if timestep is None:
-            return
-        if isinstance(timestep, int):
-            if timestep not in range(len(self.times)):
-                raise ValueError(f"Invalid timestep: {timestep}")
-        elif all(isinstance(ts, int) for ts in timestep):
-            if any(ts not in range(len(self.times)) for ts in timestep):
-                raise ValueError(f"Invalid timestep: {timestep}")
+            times = self.times
+        elif isinstance(timestep, int):
+            times = [self.times[timestep]]
         else:
-            raise TypeError(f"Invalid timestep type: {type(timestep)}")
+            times = [self.times[i] for i in timestep]
+
+        dataset = Dataset(directory / f"{self.name}.nc", "w", format="NETCDF4")
+        dataset.title = self.name
+        dataset.subtitle = self.description
+
+        # Time dimension
+        dataset.createDimension("time", None)
+        dataset_time = dataset.createVariable("time", np.int64, ("time",))
+        dataset_time.long_name = "Time since start of simulation"
+        dataset_time.units = "s"
+        dataset_time[:] = np.array(times)
+
+        # Z dimension
+        dataset.createDimension("z", None)
+        dataset_z = dataset.createVariable("z", np.int64, ("z",))
+        dataset_z.long_name = "Cell height: bottom of cell from ground"
+        dataset_z.units = "m"
+        dataset_z[:] = self.z_coords
+
+        # Y dimension
+        dataset.createDimension("y", None)
+        dataset_y = dataset.createVariable("y", np.int64, ("y",))
+        dataset_y.long_name = "Northing: y-coordinate of bottom edge of the cell"
+        dataset_y.units = "m"
+        dataset_y[:] = self.y_coords
+
+        # X dimension
+        dataset.createDimension("x", None)
+        dataset_x = dataset.createVariable("x", np.int64, ("x",))
+        dataset_x.long_name = "Easting: x-coordinate of left edge of the cell"
+        dataset_x.units = "m"
+        dataset_x[:] = self.x_coords
+
+        # Output variable
+        output = dataset.createVariable(self.name, np.float32, ("time", "z", "y", "x"))
+        output.long_name = self.description
+        output.units = self.units
 
-    def _select_files_based_on_timestep(
-        self, timestep: int | list[int] | None
-    ) -> list[Path]:
-        """Return files selected based on timestep."""
-        if timestep is None:
-            return self.filepaths
-        if isinstance(timestep, int):
-            return [self.filepaths[timestep]]
-        return [self.filepaths[ts] for ts in timestep]
+        selected_files = self._select_files_based_on_timestep(timestep)
+        output[:, :, :, :] = self._get_multiple_timesteps(selected_files)
 
-    def _get_single_timestep(self, output_file: Path) -> np.ndarray:
-        """Return a numpy array for the given output file."""
-        return self._output_function(
-            output_file, self.shape, self._compressed_index_map
-        )
+        dataset.close()
+        return
 
-    def _get_multiple_timesteps(self, output_files: list[Path]) -> np.ndarray:
-        """Return a numpy array for the given output files."""
-        arrays = [self._get_single_timestep(of) for of in output_files]
-        return arrays[0] if len(arrays) == 1 else np.concatenate(arrays, axis=0)
+    def to_zarr(
+        self,
+        fpath: str | Path,
+        chunk_size: dict[str, int] = None,
+        **kwargs,
+    ) -> zarr.Group:
+        """
+        Write the outputs to a zarr file.
+
+        Parameters
+        ----------
+        fpath: str | Path
+            The path to the folder where zarr files are written to be written.
+        chunk_size: dict[str, int]
+            The chunk size for the zarr array. The dictionary may contain
+            keys "time", "z", "y", and "x" with integer values for the chunk
+            size in each dimension. The default chunk size is (1, nz, ny, nx).
+            If a key is not present, then the chunk size defaults to the size
+            of the dimension.
+        **kwargs
+            Additional keyword arguments to pass to the zarr array creation
+            function.
+
+        Returns
+        -------
+        zarr.Group
+            The zarr group containing the output data.
+
+        Examples
+        --------
+        >>> import quicfire_tools as qft
+        >>> outputs = qft.SimulationOutputs("path/to/outputs", 50, 100, 100)
+        >>> fire_energy = outputs.get_output("fire-energy_to_atmos")
+        >>> # Write fire-energy_to_atmos output to zarr
+        >>> zarr_group = fire_energy.to_zarr("path/to/zarr")
+        >>> zarr_group["fuels-dens"]
+        <zarr.core.Array '/fuels-dens' (2, 1, 200, 200) float64 read-only>
+        >>> # Write multiple timesteps to each zarr chunk to improve I/O
+        >>> zarr_group = fire_energy.to_zarr("path/to/zarr", chunk_size={"time": 100})
+        >>> zarr_group["fuels-dens"].chunks
+        (100, 1, 200, 200)
+        """
+        if isinstance(fpath, str):
+            fpath = Path(fpath)
+
+        if not fpath.exists():
+            fpath.mkdir(parents=True)
+
+        # Build the zarr file with directory storage
+        zarr_path = fpath / (self.name + ".zarr")
+        zroot = zarr.open(str(zarr_path), mode="w")
+        zroot.attrs["crs"] = self.crs
+
+        # Build the output data array. By default, the array has shape
+        # (time, nz, ny, nx) and chunks of size (1, nz, ny, nx).
+        shape = (len(self.times), *self.shape)
+        if chunk_size is None:
+            chunk_size = {}
+        chunks = [
+            chunk_size.get("time", 1),
+            chunk_size.get("z", self.shape[0]),
+            chunk_size.get("y", self.shape[1]),
+            chunk_size.get("x", self.shape[2]),
+        ]
+        output_array = zroot.create_dataset(
+            self.name, shape=shape, chunks=chunks, dtype=float, **kwargs
+        )
+        output_array.attrs["_ARRAY_DIMENSIONS"] = ["time", "z", "y", "x"]
+        output_array.attrs["long_name"] = self.description
+        output_array.attrs["units"] = self.units
+
+        # Write each timestep to the output array
+        for time_step in range(len(self.times)):
+            data = self.to_numpy(time_step)
+            output_array[time_step, ...] = data[0, ...]
+
+        # Time dimension
+        time_array = zroot.create_dataset("time", shape=len(self.times), dtype=int)
+        time_array.attrs["long_name"] = "Time since start of simulation"
+        time_array.attrs["units"] = "s"
+        time_array[...] = self.times
+
+        # Z dimension
+        z_array = zroot.create_dataset("z", shape=self.shape[0], dtype=float)
+        z_array.attrs["long_name"] = "Cell height: bottom of cell from ground"
+        z_array.attrs["units"] = "m"
+        z_array[...] = self.z_coords
+
+        # Y dimension
+        y_array = zroot.create_dataset("y", shape=self.shape[1], dtype=float)
+        y_array.attrs[
+            "long_name"
+        ] = "Northing: cell distance north from southern edge of domain"
+        y_array.attrs["units"] = "m"
+        y_array[...] = self.y_coords
+
+        # X dimension
+        x_array = zroot.create_dataset("x", shape=self.shape[2], dtype=float)
+        x_array.attrs[
+            "long_name"
+        ] = "Easting: cell distance east from western edge of domain"
+        x_array.attrs["units"] = "m"
+        x_array[...] = self.x_coords
+
+        zarr.consolidate_metadata(zarr_path)
+
+        return zroot
 
 
 class SimulationOutputs:
@@ -302,27 +384,183 @@ class SimulationOutputs:
     files. This class facilitates the retrieval of data in various formats
     and can return a numpy array for a specific output, or write the data to a
     zarr file.
-    """
 
-    def __init__(self, output_directory: Path | str, nz: int, ny: int, nx: int):
-        # Convert to Path and resolve
-        output_directory = Path(output_directory).resolve()
+    Attributes
+    ----------
+    output_directory: Path
+        The path to the directory containing the simulation outputs. QUIC-Fire
+        defaults this to the "Output" directory in the same directory as the
+        simulation input files, but that does not have to be the case for
+        quicfire-tools. The directory must contain the "fire_indexes.bin" and
+        "grid.bin" files.
+    fire_nz: int
+        The number of vertical cells in the fire grid.
+    ny: int
+        The number of cells in the y-direction.
+    nx: int
+        The number of cells in the x-direction.
+    dy: float
+        The grid spacing in the y-direction (m).
+    dx: float
+        The grid spacing in the x-direction (m).
+    utm_x: float
+        UTM-x coordinates of the south-west corner of domain [m]. Default is 0.
+    utm_y: float
+        UTM-y coordinates of the south-west corner of domain [m]. Default is 0.
+    utm_zone: int
+        UTM zone of the domain. Default is None. If None, the coordinates are
+        considered not to be georeferenced.
+    crs: str
+        EPSG code for the coordinate reference system. If the UTM zone is
+        provided, the crs is set to the UTM zone. If the UTM zone is not
+        provided, the crs is set to None.
+    """
 
-        # Validate outputs directory
+    def __init__(
+        self,
+        output_directory: Path | str,
+        fire_nz: int,
+        ny: int,
+        nx: int,
+        dy: float,
+        dx: float,
+        utm_x: float = 0,
+        utm_y: float = 0,
+        utm_zone: int = 1,
+    ):
+        if isinstance(output_directory, str):
+            output_directory = Path(output_directory)
         self._validate_output_dir(output_directory)
 
         # Assign attributes
         self.output_directory = output_directory
-        self.nz = nz
         self.ny = ny
         self.nx = nx
+        self.dy = dy
+        self.dx = dx
+        self.fire_nz = fire_nz
+        self.utm_x = utm_x
+        self.utm_y = utm_y
+        self.utm_zone = utm_zone
+
+        # TODO: fire_nz can be optional
+        # TODO: Throw warning if fire_nz not provided and qf_wind outputs are present
+        # TODO: raise error if fire_nz is not provided and qf_qind is output
+
+        # Compute the x and y coordinate grids
+        self.x_coords = np.linspace(utm_x, utm_x + dx * (nx - 1), nx)
+        self.y_coords = np.linspace(utm_y, utm_y + dy * (ny - 1), ny)
+
+        # Set the crs if the UTM zone is provided. The if statement here is
+        # looking for the default QUIC-Fire values of 1, 0, 0. If the UTM
+        # zone is not 1 or the x and y coordinates are not 0, then the crs
+        # is set to the UTM zone.
+        if utm_zone != 1 or (utm_x != 0 and utm_y != 0):
+            crs = proj.CRS.from_proj4(f"+proj=utm +zone={utm_zone} +ellps=WGS84")
+            self.crs = f"EPSG:{crs.to_epsg()}"
+        else:
+            self.crs = None
 
-        # Get indexing information from the fire grid
-        self._fire_indexes = self._process_fire_indexes()
+        # Get grid information from grid.bin and fire_indexes.bin
+        self._fire_indexes = _process_fire_indexes(
+            output_directory / "fire_indexes.bin"
+        )
+        (
+            self.quic_nz,
+            self.quic_z_coords,
+            self.en2atmos_nz,
+            self.en2atmos_z_coords,
+        ) = _process_grid_info(output_directory / "grid.bin", ny, nx)
+        self.quic_z_coords = self.quic_z_coords[: self.quic_nz]
+        self.en2atmos_z_coords = self.en2atmos_z_coords[: self.en2atmos_nz]
+        self.fire_z_coords = self.en2atmos_z_coords[:fire_nz]
+        self.quic_dz = _get_resolution_from_coords(self.quic_z_coords)
+        self.fire_dz = _get_resolution_from_coords(self.en2atmos_z_coords)
 
         # Build a list of present output files and their times
         self.outputs = {}
         self._build_output_files_map()
+        self._output_names = list(self.outputs.keys())
+
+    def __iter__(self):
+        """Return an iterator for the object."""
+        self._index = 0
+        return self
+
+    def __next__(self):
+        """Return the next item in the iterator."""
+        if self._index < len(self._output_names):
+            output = self.outputs[self._output_names[self._index]]
+            self._index += 1
+            return output
+        else:
+            raise StopIteration
+
+    def __eq__(self, other):
+        self_dict = self.__dict__.copy()
+        other_dict = other.__dict__.copy()
+        self_dict.pop("outputs")
+        other_dict.pop("outputs")
+        for key in self_dict.keys():
+            self_value = self_dict[key]
+            other_value = other_dict[key]
+            try:
+                if self_value != other_value:
+                    return False
+            except KeyError:
+                return False
+            except ValueError:
+                if np.any(self_value != other_value):
+                    return False
+        for key in self.outputs.keys():
+            try:
+                if self.outputs[key] != other.outputs[key]:
+                    return False
+            except KeyError:
+                return False
+        return True
+
+    @classmethod
+    def from_simulation_inputs(
+        cls, output_directory: Path | str, simulation_inputs: SimulationInputs
+    ) -> SimulationOutputs:
+        """
+        Create a SimulationOutputs object from a path to a QUIC-Fire "Output"
+        directory and a SimulationInputs object.
+
+        Parameters
+        ----------
+        output_directory: Path | str
+            The path to the directory containing the simulation outputs. The
+            directory must contain the "fire_indexes.bin" and "grid.bin" files.
+            This is typically the "Output" directory in the same directory as
+            the simulation input files, but that does not have to be the case
+            for quicfire-tools.
+        simulation_inputs: SimulationInputs
+            The SimulationInputs object containing the simulation input data.
+
+        Returns
+        -------
+        SimulationOutputs
+            A SimulationOutputs object.
+
+        Examples
+        --------
+        >>> import quicfire_tools as qft
+        >>> inputs = qft.SimulationInputs.from_directory("path/to/inputs")
+        >>> outputs = qft.SimulationOutputs.from_simulation_inputs("path/to/outputs", inputs)
+        """
+        return cls(
+            output_directory,
+            simulation_inputs.quic_fire.nz,
+            simulation_inputs.qu_simparams.ny,
+            simulation_inputs.qu_simparams.nx,
+            simulation_inputs.qu_simparams.dy,
+            simulation_inputs.qu_simparams.dx,
+            simulation_inputs.qu_simparams.utm_x,
+            simulation_inputs.qu_simparams.utm_y,
+            simulation_inputs.qu_simparams.utm_zone_number,
+        )
 
     @staticmethod
     def _validate_output_dir(outputs_directory):
@@ -341,7 +579,7 @@ def _validate_output_dir(outputs_directory):
             )
 
         # Check for required files
-        required_files = ["fire_indexes.bin"]
+        required_files = ["fire_indexes.bin", "grid.bin"]
         for f in required_files:
             if not (outputs_directory / f).exists():
                 raise FileNotFoundError(f"Required file {f} not in outputs directory")
@@ -358,26 +596,30 @@ def _build_output_files_map(self):
 
             # Check if any output files of the given type exist in the directory
             if output_files_list:
-                shape = self._get_output_shape(attributes)
+                z_coords = self._get_z_coords(attributes)
+                times = []
+                for file in output_files_list:
+                    time = self._get_output_file_time(key, file)
+                    times.append(time)
+
                 self.outputs[key] = OutputFile(
                     name=key,
                     file_format=attributes["file_format"],
                     dimensions=attributes["dimensions"],
-                    shape=shape,
                     grid=attributes["grid"],
+                    x_coords=self.x_coords,
+                    y_coords=self.y_coords,
+                    z_coords=z_coords,
                     delimiter=attributes["delimiter"],
                     extension=attributes["extension"],
                     description=attributes["description"],
                     units=attributes["units"],
+                    times=times,
+                    filepaths=output_files_list,
                     index_map=self._fire_indexes,
+                    crs=self.crs,
                 )
 
-                # Populate the output file with timesteps and filepaths
-                for filepath in output_files_list:
-                    time = self._get_output_file_time(key, filepath)
-                    self.outputs[key].times.append(time)
-                    self.outputs[key].filepaths.append(filepath)
-
     def _get_list_output_paths(self, name, ext) -> list[Path]:
         """
         Get a sorted list of output files in the Output/ directory for the
@@ -387,14 +629,22 @@ def _get_list_output_paths(self, name, ext) -> list[Path]:
         paths.sort()
         return paths
 
-    def _get_output_shape(self, attrs) -> tuple:
+    def _get_z_coords(self, attrs) -> tuple:
         number_dimensions = len(attrs["dimensions"])
+        grid = attrs["grid"]
+
         if number_dimensions == 2:
-            return 1, self.ny, self.nx
-        elif number_dimensions == 3 and attrs["grid"] == "fire":
-            return self.nz + 1, self.ny, self.nx
+            return [0.0]
+        elif number_dimensions == 3 and grid == "fire":
+            return self.fire_z_coords
+        elif number_dimensions == 3 and grid == "en2atmos":
+            return self.en2atmos_z_coords
+        elif number_dimensions == 3 and grid == "quic":
+            return self.quic_z_coords
         else:
-            return self.nz, self.ny, self.nx
+            raise ValueError(
+                f"Invalid number of dimensions ({number_dimensions}) for {grid} grid"
+            )
 
     @staticmethod
     def _get_output_file_time(output, fpath) -> int:
@@ -415,41 +665,6 @@ def _get_output_file_time(output, fpath) -> int:
         except ValueError:  # No numeric component
             return 0
 
-    def _process_fire_indexes(self) -> ndarray:
-        """
-        Process the `fire_indexes.bin` file to extract the necessary
-        information to rebuild the 3D fields of fire and fuel related variables.
-
-        - Data 1 (INT): Header
-        - Data 2 (INT): Number of cells with fuel (firegrid%num_fuel_cells = nfuel below)
-        - Data 3 (INT): Max index of the cells with fuel in the vertical direction
-        - Data 4-(nfuel+3) (INT): Unique cell identifiers (firegrid%num_fuel_cells)
-        - Data (nfuel+4)-(2*nfuel+4) (INT): i,j,k cell indexes
-        - Data (INT): Header
-
-        Function returns a 2D ndarray of shape (num_cells, 3) representing the
-        i, j, k indexes of each cell.
-        """
-        with open(self.output_directory / "fire_indexes.bin", "rb") as fid:
-            # Read in the number of cells and skip the header
-            num_cells = np.fromfile(fid, dtype=np.int32, offset=4, count=1)[0]
-
-            # Skip the max index of the cells with fuel in the vertical
-            # direction and unique cell identifiers
-            np.fromfile(fid, dtype=np.int32, count=7 + num_cells)
-
-            # Initialize an array to hold the indices
-            ijk = np.zeros((num_cells, 3), dtype=np.int32)
-
-            # Loop over each dimension's indices (Like a sparse meshgrid)
-            for i in range(0, 3):
-                ijk[:, i] = np.fromfile(fid, dtype=np.int32, count=num_cells)
-
-            # Convert to indices (numbered from 0)
-            ijk -= 1
-
-            return ijk
-
     def _validate_output(self, output: str) -> OutputFile:
         """Validate output."""
         try:
@@ -457,11 +672,11 @@ def _validate_output(self, output: str) -> OutputFile:
         except KeyError:
             raise ValueError(
                 f"{output} is not a valid output. Valid "
-                f"outputs are: {self.list_available_outputs()}"
+                f"outputs are: {self.list_outputs()}"
             )
         return output
 
-    def list_available_outputs(self) -> list[str]:
+    def list_outputs(self) -> list[str]:
         """
         Return a list of available output names.
 
@@ -474,10 +689,10 @@ def list_available_outputs(self) -> list[str]:
         --------
         >>> import quicfire_tools as qft
         >>> outputs = qft.SimulationOutputs("path/to/outputs")
-        >>> outputs.list_available_outputs()
+        >>> outputs.list_outputs()
         ['fire-energy_to_atmos', 'fuels-dens', 'groundfuelheight', 'mburnt_integ']
         """
-        return list(self.outputs.keys())
+        return self._output_names
 
     def get_output(self, key) -> OutputFile:
         """
@@ -508,7 +723,9 @@ def get_output(self, key) -> OutputFile:
         """
         return self._validate_output(key)
 
-    def to_numpy(self, key: str, timestep: None | int | list[int] = None) -> np.ndarray:
+    def to_numpy(
+        self, key: str, timestep: None | int | list[int] | range = None
+    ) -> np.ndarray:
         """
         Returns a 4D numpy array for the given output and timestep(s) with shape
         (time, nz, ny, nx). If timestep is None, then all timesteps are
@@ -580,199 +797,122 @@ def to_dask(self, key: str) -> da.Array:
 
         return dask_array
 
-    # ZCC Changes
-    def to_zarr(
-        self,
-        fpath: str | Path,
-        outputs: str | list[str] = None,
-        over_write: bool = False,
-    ):
-        """
-        Write the outputs to a zarr file.
 
-        Parameters
-        ----------
-        fpath: str | Path
-            The path to the folder where zarr files are written to be written.
-        outputs: str | list[str]
-            The name of the output(s) to write to the zarr file. If None, then
-            all outputs are written to the zarr file.
-        over_write: bool
-            Whether or not to over_write zarr if it already exists
+def _get_resolution_from_coords(coords: list[float]) -> float | list[float]:
+    """
+    Get the resolution from a list of coordinates. If the resolution is
+    constant, then a single float is returned. If the resolution is not
+    constant, then a list of floats is returned.
+    """
+    resolution = np.diff(coords)
+    if np.all(resolution == resolution[0]):
+        return resolution[0]
+    else:
+        return resolution
 
-        Builds
-        -------
-        zarr files to disk
-        """
-        # Hard coding dimentions. Will want to add dimensions to one of the classes.
-        dz = 1
-        dy = 2
-        dx = 2
 
-        if isinstance(fpath, str):
-            fpath = Path(fpath)
+def _process_grid_info(path_to_grid_bin: str | Path, ny: int, nx: int):
+    """
+    Reads grid information from the grid.bin file and populates attributes
+    for fire and quic grid sizes, as well as dx, dy, and dz.
+    """
+    if isinstance(path_to_grid_bin, str):
+        path_to_grid_bin = Path(path_to_grid_bin)
+
+    with open(path_to_grid_bin, "rb") as fid:
+        # Read the number of bytes in the vertical QUIC grid
+        num_bytes_quic_grid = np.fromfile(fid, dtype=np.int32, count=1)[0]
+
+        # Get the number of cells in the vertical direction
+        # num_cells * 32 bits = num_bytes * (8 bits / byte)
+        quic_nz = int(num_bytes_quic_grid / 4) - 2
+
+        # QUIC-URB grid bottom values
+        quic_grid_bottom = np.fromfile(fid, dtype=np.float32, count=quic_nz + 2)
+
+        np.fromfile(fid, dtype=np.int32, count=2)  # Header
+        np.fromfile(fid, dtype=np.float32, count=quic_nz + 2)  # quic grid mid values
+        np.fromfile(fid, dtype=np.int32, count=1)  # Header
+
+        # Try and read a topo or a fire header
+        test_header = np.fromfile(fid, dtype=np.int32, count=1)
+        if len(test_header) == 0:
+            raise ValueError("No fire or topo grids found in grid.bin file")
+
+        # Check if topo grid information is present
+        if test_header[0] == num_bytes_quic_grid:
+            np.fromfile(
+                fid, dtype=np.float32, count=quic_nz + 2
+            )  # Read sigma_bottom (same as quic_grid_bottom)
+            np.fromfile(fid, dtype=np.int32, count=2)  # Header
+            np.fromfile(
+                fid, dtype=np.float32, count=quic_nz + 2
+            )  # Read sigma_mid (same as quic_grid_mid)
+            np.fromfile(fid, dtype=np.int32, count=2)  # Header
+            np.fromfile(
+                fid, dtype=np.float32, count=ny * nx * (quic_nz + 2)
+            )  # quic grid bottom terrain following
+            np.fromfile(fid, dtype=np.int32, count=2)  # Header
+            np.fromfile(
+                fid, dtype=np.float32, count=ny * nx * (quic_nz + 2)
+            )  # quic grid mid terrain following
+            np.fromfile(fid, dtype=np.int32, count=2)  # Header
+            np.fromfile(
+                fid, dtype=np.float32, count=ny * nx
+            )  # quic grid volume correction
+            np.fromfile(fid, dtype=np.int32, count=1)  # Header
+            _ = np.fromfile(fid, dtype=np.int32, count=1)[0]  # fire header
+        else:
+            _ = test_header[0]  # fire header
 
-        if not fpath.exists():
-            fpath.mkdir(parents=True)
+        num_eng_to_atmos_cells = np.fromfile(fid, dtype=np.int32, count=1)[0]
+        np.fromfile(fid, dtype=np.int32, count=2)  # Header
+        eng_to_amos_grid_bottom = np.fromfile(
+            fid, dtype=np.float32, count=num_eng_to_atmos_cells + 1
+        )
 
-        self.zarr_folder_path = fpath  # store folder location of zarrs to object
-
-        # Get a list of outputs to write to the zarr file
-        if outputs is None:
-            outputs = self.list_available_outputs()
-        elif isinstance(outputs, str):
-            outputs = [outputs]
-
-        # Write each output to the zarr file
-        for output_name in outputs:
-            # Get the output object and verify it exists
-            output = self.get_output(output_name)
-
-            # Build zarr path and store to output object
-            zarr_path = fpath / (output_name + ".zarr")
-            output.zarr_path = zarr_path  # store zarr_path to output object
-
-            write_zarr_to_disk = True
-            if zarr_path.exists():
-                if not over_write:
-                    write_zarr_to_disk = False
-                    print(
-                        "A dataset for {} already exists in the zarr file.\n".format(
-                            output_name
-                        ),
-                        "Set over_write flag to True if you would like to rebuild the zarr.",
-                    )
-                else:
-                    rmtree(zarr_path)
-
-            if write_zarr_to_disk:
-                # Create the zarr file
-                zarr_file = zarr.open(str(zarr_path), mode="w")
-
-                # Create a zarr dataset for the output
-                shape = (len(output.times), *output.shape)
-                chunks = [1 if i == 0 else shape[i] for i in range(len(shape))]
-
-                # Output to zarr dataset
-                DATA_NAME = "data"
-                zarr_dataset = zarr_file.create_dataset(
-                    DATA_NAME, shape=shape, chunks=chunks, dtype=float
-                )
+        np.fromfile(fid, dtype=np.int32, count=1)  # ending header
+        np.fromfile(fid, dtype=np.int32, count=4)  # Header
 
-                # Metadata
-                zarr_dataset.attrs["_ARRAY_DIMENSIONS"] = ["time", "z", "y", "x"]
-                zarr_dataset.attrs["long_name"] = output_name
-                zarr_dataset.attrs["units"] = output.units
-                # Write each timestep to the output's zarr dataset
-                for time_step in range(len(output.times)):
-                    data = self.to_numpy(output_name, time_step)
-                    zarr_dataset[time_step, ...] = data[0, ...]
-
-                # Build datasets for coordinates
-                # Build time coordinate
-                zarr_dataset = zarr_file.create_dataset(
-                    "time", shape=len(output.times), dtype=int
-                )
-                # Metadata
-                zarr_dataset.attrs["_ARRAY_DIMENSIONS"] = ["time"]
-                zarr_dataset.attrs["long_name"] = "Time since start of simulation"
-                zarr_dataset.attrs["units"] = "s"
-                # Store Values
-                zarr_dataset[...] = np.array(output.times)
-
-                # Build z coordinate
-                zarr_dataset = zarr_file.create_dataset(
-                    "z", shape=output.shape[0], dtype=int
-                )
-                # Metadata
-                zarr_dataset.attrs["_ARRAY_DIMENSIONS"] = ["z"]
-                zarr_dataset.attrs[
-                    "long_name"
-                ] = "cell height: bottom of cell from ground"
-                zarr_dataset.attrs["units"] = "m"
-                # Store Values
-                zarr_dataset[...] = np.array(range(output.shape[0])) * dz
-
-                # Build y coordinate
-                zarr_dataset = zarr_file.create_dataset(
-                    "y", shape=output.shape[1], dtype=int
-                )
-                # Metadata
-                zarr_dataset.attrs["_ARRAY_DIMENSIONS"] = ["y"]
-                zarr_dataset.attrs[
-                    "long_name"
-                ] = "Latitude: cell dist north from southern edge of domain"
-                zarr_dataset.attrs["units"] = "m"
-                # Store Values
-                zarr_dataset[...] = np.array(range(output.shape[1])) * dy
-
-                # Build x coordinate
-                zarr_dataset = zarr_file.create_dataset(
-                    "x", shape=output.shape[2], dtype=int
-                )
-                # Metadata
-                zarr_dataset.attrs["_ARRAY_DIMENSIONS"] = ["x"]
-                zarr_dataset.attrs[
-                    "long_name"
-                ] = "Longitude: cell dist east from western edge of domain"
-                zarr_dataset.attrs["units"] = "m"
-                # Store Values
-                zarr_dataset[...] = np.array(range(0, output.shape[2])) * dx
-
-                zarr.convenience.consolidate_metadata(zarr_path)
-        return
+        return (
+            quic_nz,
+            quic_grid_bottom[:],
+            num_eng_to_atmos_cells,
+            eng_to_amos_grid_bottom[:],
+        )
+
+
+def _process_fire_indexes(path_to_fire_indexes_bin: str | Path) -> ndarray:
+    """
+    Process the `fire_indexes.bin` file to extract the necessary
+    information to rebuild the 3D fields of fire and fuel related variables.
+    Function returns a 2D ndarray of shape (num_cells, 3) representing the
+    i, j, k indexes of each cell that contains data in the sparse (compressed)
+    format.
+    """
+    if isinstance(path_to_fire_indexes_bin, str):
+        path_to_fire_indexes_bin = Path(path_to_fire_indexes_bin)
+
+    with open(path_to_fire_indexes_bin, "rb") as fid:
+        np.fromfile(fid, dtype=np.int32, count=1)  # Header
+        num_cells = np.fromfile(fid, dtype=np.int32, count=1)[0]
+        np.fromfile(fid, dtype=np.int32, count=2)  # Header
+        np.fromfile(fid, dtype=np.int32, count=1)  # max k index
+        np.fromfile(fid, dtype=np.int32, count=2)  # Header
+        np.fromfile(fid, dtype=np.int32, count=num_cells)  # unique cell IDs
+        np.fromfile(fid, dtype=np.int32, count=2)  # Header
+
+        # Initialize an array to hold the indices
+        ijk = np.zeros((num_cells, 3), dtype=np.int32)
+
+        # Loop over each dimension's indices (Like a sparse meshgrid)
+        for i in range(0, 3):
+            ijk[:, i] = np.fromfile(fid, dtype=np.int32, count=num_cells)
+
+        # Convert to indices (numbered from 0)
+        ijk -= 1
 
-    # #Anthony's method
-    # def to_zarr(
-    #     self, fpath: str | Path, outputs: str | list[str] = None
-    # ) -> zarr.hierarchy.Group:
-    #     """
-    #     Write the outputs to a zarr file.
-
-    #     Parameters
-    #     ----------
-    #     fpath: str | Path
-    #         The path to the zarr file to be written.
-    #     outputs: str | list[str]
-    #         The name of the output(s) to write to the zarr file. If None, then
-    #         all outputs are written to the zarr file.
-
-    #     Returns
-    #     -------
-    #     zarr.hierarchy.Group
-    #         The zarr file object.
-    #     """
-    #     if isinstance(fpath, str):
-    #         fpath = Path(fpath)
-
-    #     # Create the zarr file
-    #     zarr_file = zarr.open(str(fpath), mode="w")
-
-    #     # Get a list of outputs to write to the zarr file
-    #     if outputs is None:
-    #         outputs = self.list_available_outputs()
-    #     elif isinstance(outputs, str):
-    #         outputs = [outputs]
-
-    #     # Write each output to the zarr file
-    #     for output_name in outputs:
-    #         # Get the output object and verify it exists
-    #         output = self.get_output(output_name)
-
-    #         # Create a zarr dataset for the output
-    #         shape = (len(output.times), *output.shape)
-    #         chunks = [1 if i == 0 else shape[i] for i in range(len(shape))]
-    #         zarr_dataset = zarr_file.create_dataset(
-    #             output_name, shape=shape, chunks=chunks, dtype=float
-    #         )
-    #         zarr_dataset.attrs["_ARRAY_DIMENSIONS"] = ["time", "z", "y", "x"]
-
-    #         # Write each timestep to the output's zarr dataset
-    #         for time_step in range(len(output.times)):
-    #             data = self.to_numpy(output_name, time_step)
-    #             zarr_dataset[time_step, ...] = data[0, ...]
-
-    #     return zarr_file
+        return ijk
 
 
 def _process_compressed_bin(filename, dim_zyx, *args) -> ndarray:
@@ -841,7 +981,7 @@ def _process_compressed_bin(filename, dim_zyx, *args) -> ndarray:
         array_3d[indices] = sparse_values
 
     full_array[0, ...] = array_3d
-    return full_array.astype(float)
+    return full_array
 
 
 def _process_gridded_bin(filename, dims_zyx, *args) -> ndarray:
diff --git a/quicfire_tools/topography.py b/quicfire_tools/topography.py
index 4b813cf..7b837f0 100644
--- a/quicfire_tools/topography.py
+++ b/quicfire_tools/topography.py
@@ -1,6 +1,7 @@
 """
 QUIC-Fire Tools Topography Model
 """
+
 from __future__ import annotations
 
 # Core Imports
@@ -8,11 +9,10 @@
 
 # External Imports
 from typing import Literal
-
 from pydantic import BaseModel, Field, PositiveFloat, SerializeAsAny
 
 
-class TopoSources(int, Enum):
+class TopoFlags(int, Enum):
     """
     Enum class for all valid topography source options in QUIC-Fire.
     """
@@ -31,13 +31,13 @@ class TopoSources(int, Enum):
     terrain_dat = 11
 
 
-class TopoType(BaseModel):
+class Topography(BaseModel):
     """
     Base class for all topography types in QUIC-Fire. This class is used to
     provide a common string representation for all topography types.
     """
 
-    topo_flag: SerializeAsAny[TopoSources]
+    topo_flag: SerializeAsAny[TopoFlags]
 
     def __str__(self):
         return (
@@ -51,7 +51,7 @@ def __str__(self):
         )
 
 
-class GaussianHillTopo(TopoType):
+class GaussianHillTopo(Topography):
     """
     Creates a Gaussian hill topography in QUIC-Fire.
 
@@ -67,7 +67,7 @@ class GaussianHillTopo(TopoType):
         Standard deviation of the Gaussian hill [m]
     """
 
-    topo_flag: SerializeAsAny[TopoSources] = TopoSources(1)
+    topo_flag: SerializeAsAny[TopoFlags] = TopoFlags(1)
     x_hilltop: PositiveFloat
     y_hilltop: PositiveFloat
     elevation_max: PositiveFloat
@@ -84,7 +84,7 @@ def __str__(self):
         return flag_line + params
 
 
-class HillPassTopo(TopoType):
+class HillPassTopo(Topography):
     """
     Creates a hill pass topography in QUIC-Fire.
 
@@ -96,7 +96,7 @@ class HillPassTopo(TopoType):
         Location parameter of the hill pass [m]
     """
 
-    topo_flag: SerializeAsAny[TopoSources] = TopoSources(2)
+    topo_flag: SerializeAsAny[TopoFlags] = TopoFlags(2)
     max_height: PositiveFloat
     location_param: PositiveFloat
 
@@ -109,7 +109,7 @@ def __str__(self):
         return flag_line + params
 
 
-class SlopeMesaTopo(TopoType):
+class SlopeMesaTopo(Topography):
     """
     Creates a slope mesa topography in QUIC-Fire.
 
@@ -121,7 +121,7 @@ class SlopeMesaTopo(TopoType):
         Slope in dh/dx or dh/dy
     """
 
-    topo_flag: SerializeAsAny[TopoSources] = TopoSources(3)
+    topo_flag: SerializeAsAny[TopoFlags] = TopoFlags(3)
     slope_axis: Literal[0, 1]
     slope_value: PositiveFloat
     flat_fraction: float = Field(ge=0, le=1)
@@ -136,7 +136,7 @@ def __str__(self):
         return flag_line + params
 
 
-class CanyonTopo(TopoType):
+class CanyonTopo(Topography):
     """
     Creates a canyon topography in QUIC-Fire.
 
@@ -154,7 +154,7 @@ class CanyonTopo(TopoType):
         Canyon vertical offset [m].
     """
 
-    topo_flag: SerializeAsAny[TopoSources] = TopoSources(4)
+    topo_flag: SerializeAsAny[TopoFlags] = TopoFlags(4)
     x_location: PositiveFloat
     y_location: PositiveFloat
     slope_value: PositiveFloat
@@ -173,7 +173,7 @@ def __str__(self):
         return flag_line + params
 
 
-class HalfCircleTopo(TopoType):
+class HalfCircleTopo(Topography):
     """
     Creates a half-circle topography in QUIC-Fire.
 
@@ -187,7 +187,7 @@ class HalfCircleTopo(TopoType):
         The radius of the half-circle topography [m].
     """
 
-    topo_flag: SerializeAsAny[TopoSources] = TopoSources(6)
+    topo_flag: SerializeAsAny[TopoFlags] = TopoFlags(6)
     x_location: PositiveFloat
     y_location: PositiveFloat
     radius: PositiveFloat
@@ -202,7 +202,7 @@ def __str__(self):
         return flag_line + params
 
 
-class SinusoidTopo(TopoType):
+class SinusoidTopo(Topography):
     """
     Creates a sinusoidal topography in QUIC-Fire.
 
@@ -214,7 +214,7 @@ class SinusoidTopo(TopoType):
         The amplitude of the sinusoidal wave [m].
     """
 
-    topo_flag: SerializeAsAny[TopoSources] = TopoSources(7)
+    topo_flag: SerializeAsAny[TopoFlags] = TopoFlags(7)
     period: PositiveFloat
     amplitude: PositiveFloat
 
@@ -224,7 +224,7 @@ def __str__(self):
         return flag_line + params
 
 
-class CosHillTopo(TopoType):
+class CosHillTopo(Topography):
     """
     Creates a cosine-shaped hill topography in QUIC-Fire.
 
@@ -236,7 +236,7 @@ class CosHillTopo(TopoType):
         The height of the hill [m].
     """
 
-    topo_flag: SerializeAsAny[TopoSources] = TopoSources(8)
+    topo_flag: SerializeAsAny[TopoFlags] = TopoFlags(8)
     aspect: PositiveFloat
     height: PositiveFloat
 
@@ -244,3 +244,23 @@ def __str__(self):
         flag_line = super().__str__()
         params = f"\n{self.aspect}\t! [0], aspect\n" f"{self.height}\t! m, height"
         return flag_line + params
+
+
+def serialize_topography(topo_data: dict):
+    topo_flag = topo_data.get("topo_flag")
+    if topo_flag == TopoFlags(1):
+        return GaussianHillTopo(**topo_data)
+    elif topo_flag == TopoFlags(2):
+        return HillPassTopo(**topo_data)
+    elif topo_flag == TopoFlags(3):
+        return SlopeMesaTopo(**topo_data)
+    elif topo_flag == TopoFlags(4):
+        return CanyonTopo(**topo_data)
+    elif topo_flag == TopoFlags(6):
+        return HalfCircleTopo(**topo_data)
+    elif topo_flag == TopoFlags(7):
+        return SinusoidTopo(**topo_data)
+    elif topo_flag == TopoFlags(8):
+        return CosHillTopo(**topo_data)
+    else:
+        return Topography(**topo_data)
diff --git a/quicfire_tools/utils.py b/quicfire_tools/utils.py
index 486664a..a5850c7 100644
--- a/quicfire_tools/utils.py
+++ b/quicfire_tools/utils.py
@@ -1,14 +1,14 @@
 """
 Utility functions for quicfire-tools.
 """
+
 from __future__ import annotations
 
-from pathlib import Path
 import numpy as np
+from pathlib import Path
+from numpy import ndarray
 from scipy.io import FortranFile
 
-from quicfire_tools.topography import TopoType
-
 
 def compute_parabolic_stretched_grid(
     dz_surf: float, n_surf_cell: int, n_tot_cells: int, domain_height: float
@@ -74,82 +74,39 @@ def compute_parabolic_stretched_grid(
     return dz
 
 
-def read_topo_dat(
-    topo_path: Path | str, filename: str, x_dim: int, y_dim: int, order: str = "C"
-):
-    """
-    Read in a topo.dat file as a numpy array.
+def read_dat_file(
+    filename: Path | str, nz: int, ny: int, nx: int, order: str = "C"
+) -> ndarray:
     """
-    if isinstance(topo_path, str):
-        topo_path = Path(topo_path)
+    Read in a .dat file as a numpy array.
 
-    full_path = topo_path / filename
+    Parameters
+    ----------
+    filename : Path or str
+        The path to the .dat file to read.
+    nz : int
+        The number of cells in the z-direction.
+    ny : int
+        The number of cells in the y-direction.
+    nx : int
+        The number of cells in the x-direction.
+    order : str, optional
+        The order of the array. Default is "C".
+
+    Returns
+    -------
+    ndarray
+        A 3D numpy array representing the data in the .dat file. The array
+        has dimensions (nz, ny, nx).
+    """
+    if isinstance(filename, str):
+        filename = Path(filename)
 
-    with open(full_path, "rb") as fin:
+    with open(filename, "rb") as fin:
         arr = (
             FortranFile(fin)
             .read_reals(dtype="float32")
-            .reshape((y_dim, x_dim), order=order)
+            .reshape((nz, ny, nx), order=order)
         )
 
     return arr
-
-
-def calculate_quic_height(
-    topo_type: TopoType,
-    fire_nz: int,
-    fire_dz: int,
-    nx: int,
-    ny: int,
-    topo_path: Path | str = None,
-    topo_name: str = "topo.dat",
-) -> int:
-    """
-    Calculate the QUIC domain height from the fire grid height and the maximum elevation.
-
-    Parameters
-    ----------
-    topo_height : TopoType
-        Instance of TopoType class defining topography parameters for chosen method.
-    fire_nz : int
-        Number of cells in vertical dimension of fire grid
-    fire_dz : int
-        Cell size of fire grid in vertical direction (m)
-    nx : int
-        Number of cells in x-direction of domain
-    ny : int
-        Number of cells in y-direction of domain
-    topo_path : Path | str
-        Path to directory where topo.dat file is saved.
-    topo_name : str
-        Name of Fortran .dat file definiing topography. Defaults to "topo.dat"
-    """
-    fire_height = fire_nz * fire_dz
-    if topo_type.topo_flag.value == 0:
-        topo_height = 0
-    elif topo_type.topo_flag.value == 1:
-        topo_height = topo_type.elevation_max
-    elif topo_type.topo_flag.value == 2:
-        topo_height = topo_type.max_height
-    elif topo_type.topo_flag.value == 3:
-        # something with slope_value and flat_fraction
-        pass
-    elif topo_type.topo_flag.value == 4:
-        # don't know how to do this one either
-        pass
-    elif topo_type.topo_flag.value == 6:
-        # maybe something with radius?
-        pass
-    elif topo_type.topo_flag.value == 7:
-        topo_height = 2 * topo_type.amplitude  # probably?
-    elif topo_type.topo_flag.value == 8:
-        topo_height = topo_type.height
-    elif topo_type.topo_flag.value == 5:
-        if topo_path is None:
-            raise ValueError(
-                "Must supply path to directory containing topography .dat file"
-            )
-        topo_dat = read_topo_dat(topo_path, topo_name, nx, ny)
-        topo_height = np.max(topo_dat) - np.min(topo_dat)
-
-    return int((topo_height + fire_height) * 3)
diff --git a/requirements/requirements.txt b/requirements/requirements.txt
index 645a64f..f413683 100644
--- a/requirements/requirements.txt
+++ b/requirements/requirements.txt
@@ -1,5 +1,8 @@
 dask
+dask-expr
+netCDF4
 numpy<2
 pandera
 pydantic>=2
+pyproj
 zarr>=2


Test patch:
diff --git a/requirements/test_requirements.txt b/requirements/test_requirements.txt
index 9b79449..17b06bc 100644
--- a/requirements/test_requirements.txt
+++ b/requirements/test_requirements.txt
@@ -1,7 +1,10 @@
 dask
+dask-expr
+netcdf4
 numpy<2
 pandera
 pydantic>=2
+pyproj
 pytest
 requests
 scipy
diff --git a/tests/build_simulations_for_outputs_tests.py b/tests/build_simulations_for_outputs_tests.py
deleted file mode 100644
index 3b14f1e..0000000
--- a/tests/build_simulations_for_outputs_tests.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from pathlib import Path
-from quicfire_tools.examples import line_fire_simulation, custom_fuels_simulation
-
-
-TEST_PATH = Path(__file__).parent
-TEST_DATA_PATH = TEST_PATH / "data"
-
-# Write simulation inputs to the test data directory
-line_fire_sim_path = TEST_DATA_PATH / "test-simulation-line-fire"
-line_fire_sim_path.mkdir(exist_ok=True)
-line_fire_simulation.write_inputs(line_fire_sim_path)
-line_fire_simulation.to_json(line_fire_sim_path / "line-fire-simulation.json")
-
-custom_fuels_sim_path = TEST_DATA_PATH / "test-simulation-custom-fuels"
-custom_fuels_sim_path.mkdir(exist_ok=True)
-custom_fuels_simulation.write_inputs(custom_fuels_sim_path)
-custom_fuels_simulation.to_json(custom_fuels_sim_path / "custom-fuels-simulation.json")
diff --git a/tests/data/samples/Canyon/QFire_Advanced_User_Inputs.inp b/tests/data/samples/Canyon/QFire_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..9485541
--- /dev/null
+++ b/tests/data/samples/Canyon/QFire_Advanced_User_Inputs.inp
@@ -0,0 +1,14 @@
+0.05			! N/A, fraction of the cells on fire that will launch a firebrand
+40.			! N/A, scaling factor of the radius represented by the firebrands launched
+1				! s, time step for the firebrands trajectory calculation
+10				! s, how often to launch firebrands
+500			! N/A, number of firebrands distributed over the landing area
+20.			! N/A, FB_FRACTION_LAUNCHED_to_RT_ratio
+50.			! N/A, min_b_value_coef
+0.75			! N/A, fb_frac_of_max_size
+180			! s, germination_delay
+5.				! N/A, fraction of the cell on fire (to scale w)
+50				! N/A, minimum number of ignitions via firebrands at a point
+100			! N/A, maximum number of ignitions via firebrands at a point
+0.523598		! rad, min_theta_value (pi/6)
+0.03        ! m, fb thickness
\ No newline at end of file
diff --git a/tests/data/samples/Canyon/QFire_Bldg_Advanced_User_Inputs.inp b/tests/data/samples/Canyon/QFire_Bldg_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..83c79bb
--- /dev/null
+++ b/tests/data/samples/Canyon/QFire_Bldg_Advanced_User_Inputs.inp
@@ -0,0 +1,9 @@
+1			! N/A, flag to convert QUIC-URB buildings to fuel (0 = no, 1 = yes)
+0.5		! kg/m3, thin fuel density within buildings (if no fuel is specified)
+2.			! N/A, attenuation coefficient within buildings
+0.1	   ! m, surface roughness within buildings
+1			! N/A, flag to convert fuel to canopy for winds (0 = no, 1 = yes)
+1			! N/A, update canopy winds when fuel is consumed (0 = no, 1 = yes)
+1			! N/A, attenuation coefficient within fuel for the Cionco profile (default = 1)
+0.1	   ! m, surface roughness within fuel (default = 0.1 m)
+
diff --git a/tests/data/samples/Canyon/QFire_Plume_Advanced_User_Inputs.inp b/tests/data/samples/Canyon/QFire_Plume_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..f12f227
--- /dev/null
+++ b/tests/data/samples/Canyon/QFire_Plume_Advanced_User_Inputs.inp
@@ -0,0 +1,16 @@
+85000				! N/A, max number of plume at each time step
+0.1				! m/s, minimum vertical velocity of a plume. If wc is below minimum, the plume is eliminated (default 0.1 m/s)
+100				! m/s, maximum vertical velocity of a plume (default 100 m/s)
+0.1				! N/A, minimum speed ratio (plume vertical velocity/wind speed). If below, the plume is eliminated (default 0.1)
+0.					! 1/s2, brunt vaisala frequency squared
+1					! N/A creeping flag: 0 = off, 1 = on
+0					! Flag time step (0 = constant, 1 = adaptive)
+1              ! s, flag = 0: plumes time step plume, flag = 1: minimum plumes time step
+1              ! sor option (0 = reset lambda at each call, 1 = keep)
+10             ! alpha 2 - plume centerline
+2              ! alpha 2 - plume edges
+30.            ! deg, max angle between plumes to merging
+0.7            ! N/A, fraction of overlap of plumes for merging 
+1              ! N/A, which wplume-to-grid scheme: 0 = new, 1 = old
+10             ! N/A, number of points per edge for new scheme (only new scheme)
+1              ! N/A, w computation: 1 = max(w), 0 = sum(w^3)
diff --git a/tests/data/samples/Canyon/QP_buildout.inp b/tests/data/samples/Canyon/QP_buildout.inp
new file mode 100755
index 0000000..e69de29
diff --git a/tests/data/samples/Canyon/QUIC_fire.inp b/tests/data/samples/Canyon/QUIC_fire.inp
new file mode 100644
index 0000000..cb132b4
--- /dev/null
+++ b/tests/data/samples/Canyon/QUIC_fire.inp
@@ -0,0 +1,53 @@
+1					! Fire flag: 1 = run fire; 0 = no fire
+222				! Random number generator: -1: use time and date, any other integer > 0 is used as the seed
+! FIRE TIMES
+1653321600		! When the fire is ignited in Unix Epoch time (integer seconds since 1970/1/1 00:00:00). Must be greater or equal to the time of the first wind
+600				! Total simulation time for the fire [s]
+1					! time step for the fire simulation [s]
+1					! Number of fire time steps done before updating the quic wind field (integer, >= 1)
+50					! After how many fire time steps to print out fire-related files (excluding emissions and radiation)
+50					! After how many quic updates to print out wind-related files
+50					! After how many fire time steps to average emissions and radiation
+50					! After how many quic updates to print out averaged wind-related files
+! FIRE GRID
+1					! Number of vertical layers of fire grid cells (integer)
+0					! Vertical stretching flag: 0 = uniform dz, 1 = custom
+1.             ! m, dz
+! FILE PATH
+""
+1              ! Fuel types are in separate files
+1              ! File is stream (1) or headers (2)
+! FUEL
+1					! fuel density flag: 1 = uniform; 2 = provided thru QF_FuelDensity.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.7            ! kg/m3, fuel density
+1					! fuel moisture flag: 1 = uniform; 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.05           ! gwater/gfuel, fuel moisture
+1              ! fuel height flag: 1 = uniform
+1.             ! m, fuel height
+0              ! size scale flag: 0 = default, 1 = uniform, 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation), 5 = Fastfuels
+0              ! patch & gap flag: 0 = default (0,0), 1 = uniform(supply patch and gap value in meters [following two lines]), 2 = provided thru patch.dat & gap.dat (no interpolation)
+! IGNITION LOCATIONS
+1					! 1 = rectangle, 2 = square ring, 3 = circular ring, 4 = file (QF_Ignitions.inp), 5 = time-dependent ignitions (QF_IgnitionPattern.inp), 6 = ignite.dat (firetech)
+250            ! m, SW corner, x-dir
+225            ! m, SW corner, y-dir
+10             ! m, length in x-dir
+100            ! m, length in y-dir
+100            ! number of ignitions
+! FIREBRANDS
+0				   ! 0 = off, 1 = on
+! OUTPUT FILES (formats depend on the grid type flag)
+0					! Output gridded energy-to-atmosphere (3D fire grid + extra layers)
+0					! Output compressed array reaction rate (fire grid)
+1					! Output compressed array fuel density (fire grid)
+0					! Output gridded wind (u,v,w,sigma) (3D fire grid)
+1					! Output gridded QU winds with fire effects, instantaneous (QUIC-URB grid)
+0					! Output gridded QU winds with fire effects, averaged (QUIC-URB grid)
+0					! Output plume trajectories (ONLY FOR DEBUG)
+0					! Output compressed array fuel moisture (fire grid)
+1					! Output vertically-integrated % mass burnt (fire grid)
+0					! Output trajectories firebrands
+0					! Output compressed array emissions (fire grid)
+0					! Output gridded thermal radiation (fire grid)
+0              ! Output surface fire intensity at every fire time step
+! AUTOKILL
+0              ! Kill if the fire is out and there are no more ignitions or firebrands (0 = no, 1 = yes)
\ No newline at end of file
diff --git a/tests/data/samples/Canyon/QU_TopoInputs.inp b/tests/data/samples/Canyon/QU_TopoInputs.inp
new file mode 100644
index 0000000..3e1bebc
--- /dev/null
+++ b/tests/data/samples/Canyon/QU_TopoInputs.inp
@@ -0,0 +1,16 @@
+Specify file name for custom topo (full path)
+""
+4     ! N/A, topo flag: 0 = flat, 1 = Gaussian hill, 2 = hill pass, 3 = slope mesa, 4 = canyon, 5 = custom, 6 = half circle, 7 = sinusoid, 8 = cos hill, 9 = QP_elevation.inp, 10 = terrainOutput.txt (ARA), 11 = terrain.dat (firetec)
+300   ! m, x-start of canyon on slope
+150   ! m, y-center of canyon
+0.07   ! N/A, slope
+100   ! m, std
+20    ! m, height offset of the bottom of the canyon
+0     ! N/A, smoothing method (0 = none (default for idealized terrain), 1 = blur, 2 = David's)
+0     ! N/A, number of smoothing passes
+100   ! N/A, number of initial SOR iterations (only if fire is run)
+4     ! N/A, sor cycles
+1.3   ! N/A, sor relaxation parameter (default for flat is 1.78, ignored if there is no terrain)
+0     ! N/A, add slope flow correction (0 = no, 1 = yes)
+Command to run slope flow code (system specific)
+""
diff --git a/tests/data/samples/Canyon/QU_buildings.inp b/tests/data/samples/Canyon/QU_buildings.inp
new file mode 100755
index 0000000..db22cec
--- /dev/null
+++ b/tests/data/samples/Canyon/QU_buildings.inp
@@ -0,0 +1,4 @@
+!QUIC 6.26
+0.1			!Wall roughness length (m)
+0			!Number of Buildings
+0			!Number of Polygon Building Nodes
diff --git a/tests/data/samples/Canyon/QU_fileoptions.inp b/tests/data/samples/Canyon/QU_fileoptions.inp
new file mode 100755
index 0000000..097b6fc
--- /dev/null
+++ b/tests/data/samples/Canyon/QU_fileoptions.inp
@@ -0,0 +1,6 @@
+
+2   !output data file format flag (1=ascii, 2=binary, 3=both)
+0   !flag to write out non-mass conserved initial field (uofield.dat) (1=write,0=no write)
+0   !flag to write out the file uosensorfield.dat, the initial sensor velocity field (1=write,0=no write)
+0   !flag to write out the file QU_staggered_velocity.bin used by QUIC-Pressure(1=write,0=no write)
+0   !flag to generate startup files
\ No newline at end of file
diff --git a/tests/data/samples/Canyon/QU_landuse.inp b/tests/data/samples/Canyon/QU_landuse.inp
new file mode 100755
index 0000000..edc8fe3
Binary files /dev/null and b/tests/data/samples/Canyon/QU_landuse.inp differ
diff --git a/tests/data/samples/Canyon/QU_metparams.inp b/tests/data/samples/Canyon/QU_metparams.inp
new file mode 100755
index 0000000..a41fb25
--- /dev/null
+++ b/tests/data/samples/Canyon/QU_metparams.inp
@@ -0,0 +1,7 @@
+!QUIC 6.26
+0 !Met input flag (0=QUIC,1=WRF,2=ITT MM5,3=HOTMAC)
+1 !Number of measuring sites
+1 !Maximum size of data points profiles
+sensor1 !Site Name 
+!File name
+sensor1.inp
diff --git a/tests/data/samples/Canyon/QU_movingcoords.inp b/tests/data/samples/Canyon/QU_movingcoords.inp
new file mode 100755
index 0000000..d349e21
--- /dev/null
+++ b/tests/data/samples/Canyon/QU_movingcoords.inp
@@ -0,0 +1,17 @@
+!QUIC 6.3
+0   !Moving coordinates flag (0=no, 1=yes)
+0   !Reference bearing of the ship relative to the non-rotated domain (degrees)
+!Time (Unix Time), Ship Speed (m/s), Ship Bearing (deg), Ocean Current Speed (m/s), Ocean Current Direction (deg)
+1488794400	0	0	0	0
+1488794450	0	0	0	0
+1488794500	0	0	0	0
+1488794550	0	0	0	0
+1488794600	0	0	0	0
+1488794650	0	0	0	0
+1488794700	0	0	0	0
+1488794750	0	0	0	0
+1488794800	0	0	0	0
+1488794850	0	0	0	0
+1488794900	0	0	0	0
+1488794950	0	0	0	0
+1488795000	0	0	0	0
diff --git a/tests/data/samples/Canyon/QU_simparams.inp b/tests/data/samples/Canyon/QU_simparams.inp
new file mode 100755
index 0000000..d65678a
--- /dev/null
+++ b/tests/data/samples/Canyon/QU_simparams.inp
@@ -0,0 +1,60 @@
+!QUIC 6.26
+400 !nx - Domain Length(X) Grid Cells
+300 !ny - Domain Width(Y) Grid Cells
+25 ! NZ - domain height(Z) grid cells
+2 ! DX [m]
+2 ! DY [m]
+3 !Vertical stretching flag (0 = uniform, 1 = custom, 2 = parabolic Z, 3 = parabolic DZ, 4 = exponential)
+1.000000 ! Surface DZ [m]
+5 ! Number of uniform surface cells
+! DZ array [m]
+1.000000
+1.000000
+1.000000
+1.000000
+1.000000
+1.078399
+1.313596
+1.705591
+2.254385
+2.959976
+3.822366
+4.841553
+6.017539
+7.350323
+8.839905
+10.486285
+12.289463
+14.249439
+16.366213
+18.639786
+21.070156
+23.657325
+26.401292
+29.302056
+32.354352
+1 ! Number of time increments
+0 ! UTC conversion [hours]
+! Begining of time step in Unix epoch time [integer seconds since 1970/1/1 00:00:00]
+1653321600
+2 ! Rooftop flag (0 = none, 1 = log profile, 2 = vortex)
+3 ! Upwind cavity flag (0 = none, 1 = Rockle, 2 = MVP, 3 = HMVP)
+4 ! Street canyon flag (0 = none, 1 = Roeckle, 2 = CPB, 3 = exp. param. PKK, 4 = Roeckle w/ Fackrel)
+1 ! Street intersection flag (0 = off, 1 = on)
+4 ! Wake flag (0 = none, 1 = Rockle, 2 = modified Rockle, 3 = area scaled)
+1 ! Sidewall flag (0 = off, 1 = on)
+1 ! Individual tree wake flag
+2 ! Canopy flag (0 = off, 1 = Cionco w/o wakes, 2 = Cionco w/ wakes)
+1 ! Season flag (1 = Summer, 2 = Winter, 3 = Transition)
+10 ! Maximum number of iterations
+3 ! Residual reduction (Orders of Magnitude)
+0 ! Use diffusion algorithm (0 = off, 1 = on)
+20 ! Number of diffusion iterations
+0 ! Domain rotation relative to true north [degrees] (cw = +)
+0.0 ! UTMX of domain origin [m]
+0.0 ! UTMY of domain origin [m]
+1 ! UTM zone
+17 ! UTM zone leter (1 = A, 2 = B, etc.)
+0 ! QUIC-CFD Flag
+0 ! Explosive building damage flag (1 = on)
+0 ! Building Array Flag (1 = on)
diff --git a/tests/data/samples/Canyon/Runtime_Advanced_User_Inputs.inp b/tests/data/samples/Canyon/Runtime_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..3d78e22
--- /dev/null
+++ b/tests/data/samples/Canyon/Runtime_Advanced_User_Inputs.inp
@@ -0,0 +1,2 @@
+8  ! max number of cpu threads used
+0  ! Use Adaptive Computation Window (0=Disabled 1=Enabled)
\ No newline at end of file
diff --git a/tests/data/samples/Canyon/sensor1.inp b/tests/data/samples/Canyon/sensor1.inp
new file mode 100755
index 0000000..d38d7b0
--- /dev/null
+++ b/tests/data/samples/Canyon/sensor1.inp
@@ -0,0 +1,13 @@
+sensor1 !Site Name 
+0 !Upper level flag (1 = use this profile for upper level winds)
+50 !Upper level height (meters)
+1 !Site Coordinate Flag (1=QUIC, 2=UTM, 3=Lat/Lon)
+1 !X coordinate (meters)
+1 !Y coordinate (meters)
+1653321600 !Begining of time step in Unix Epoch time (integer seconds since 1970/1/1 00:00:00)
+1 !site boundary layer flag (1 = log, 2 = exp, 3 = urban canopy, 4 = discrete data points)
+0.1 !site zo
+0. ! 1/L
+!Height (m),Speed	(m/s), Direction (deg relative to true N)
+10.	10.	270
+
diff --git a/tests/data/samples/CosHill/QFire_Advanced_User_Inputs.inp b/tests/data/samples/CosHill/QFire_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..9485541
--- /dev/null
+++ b/tests/data/samples/CosHill/QFire_Advanced_User_Inputs.inp
@@ -0,0 +1,14 @@
+0.05			! N/A, fraction of the cells on fire that will launch a firebrand
+40.			! N/A, scaling factor of the radius represented by the firebrands launched
+1				! s, time step for the firebrands trajectory calculation
+10				! s, how often to launch firebrands
+500			! N/A, number of firebrands distributed over the landing area
+20.			! N/A, FB_FRACTION_LAUNCHED_to_RT_ratio
+50.			! N/A, min_b_value_coef
+0.75			! N/A, fb_frac_of_max_size
+180			! s, germination_delay
+5.				! N/A, fraction of the cell on fire (to scale w)
+50				! N/A, minimum number of ignitions via firebrands at a point
+100			! N/A, maximum number of ignitions via firebrands at a point
+0.523598		! rad, min_theta_value (pi/6)
+0.03        ! m, fb thickness
\ No newline at end of file
diff --git a/tests/data/samples/CosHill/QFire_Bldg_Advanced_User_Inputs.inp b/tests/data/samples/CosHill/QFire_Bldg_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..83c79bb
--- /dev/null
+++ b/tests/data/samples/CosHill/QFire_Bldg_Advanced_User_Inputs.inp
@@ -0,0 +1,9 @@
+1			! N/A, flag to convert QUIC-URB buildings to fuel (0 = no, 1 = yes)
+0.5		! kg/m3, thin fuel density within buildings (if no fuel is specified)
+2.			! N/A, attenuation coefficient within buildings
+0.1	   ! m, surface roughness within buildings
+1			! N/A, flag to convert fuel to canopy for winds (0 = no, 1 = yes)
+1			! N/A, update canopy winds when fuel is consumed (0 = no, 1 = yes)
+1			! N/A, attenuation coefficient within fuel for the Cionco profile (default = 1)
+0.1	   ! m, surface roughness within fuel (default = 0.1 m)
+
diff --git a/tests/data/samples/CosHill/QFire_Plume_Advanced_User_Inputs.inp b/tests/data/samples/CosHill/QFire_Plume_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..f12f227
--- /dev/null
+++ b/tests/data/samples/CosHill/QFire_Plume_Advanced_User_Inputs.inp
@@ -0,0 +1,16 @@
+85000				! N/A, max number of plume at each time step
+0.1				! m/s, minimum vertical velocity of a plume. If wc is below minimum, the plume is eliminated (default 0.1 m/s)
+100				! m/s, maximum vertical velocity of a plume (default 100 m/s)
+0.1				! N/A, minimum speed ratio (plume vertical velocity/wind speed). If below, the plume is eliminated (default 0.1)
+0.					! 1/s2, brunt vaisala frequency squared
+1					! N/A creeping flag: 0 = off, 1 = on
+0					! Flag time step (0 = constant, 1 = adaptive)
+1              ! s, flag = 0: plumes time step plume, flag = 1: minimum plumes time step
+1              ! sor option (0 = reset lambda at each call, 1 = keep)
+10             ! alpha 2 - plume centerline
+2              ! alpha 2 - plume edges
+30.            ! deg, max angle between plumes to merging
+0.7            ! N/A, fraction of overlap of plumes for merging 
+1              ! N/A, which wplume-to-grid scheme: 0 = new, 1 = old
+10             ! N/A, number of points per edge for new scheme (only new scheme)
+1              ! N/A, w computation: 1 = max(w), 0 = sum(w^3)
diff --git a/tests/data/samples/CosHill/QP_buildout.inp b/tests/data/samples/CosHill/QP_buildout.inp
new file mode 100755
index 0000000..e69de29
diff --git a/tests/data/samples/CosHill/QUIC_fire.inp b/tests/data/samples/CosHill/QUIC_fire.inp
new file mode 100644
index 0000000..eb36326
--- /dev/null
+++ b/tests/data/samples/CosHill/QUIC_fire.inp
@@ -0,0 +1,53 @@
+1					! Fire flag: 1 = run fire; 0 = no fire
+222				! Random number generator: -1: use time and date, any other integer > 0 is used as the seed
+! FIRE TIMES
+1653321600		! When the fire is ignited in Unix Epoch time (integer seconds since 1970/1/1 00:00:00). Must be greater or equal to the time of the first wind
+600				! Total simulation time for the fire [s]
+1					! time step for the fire simulation [s]
+1					! Number of fire time steps done before updating the quic wind field (integer, >= 1)
+60					! After how many fire time steps to print out fire-related files (excluding emissions and radiation)
+60					! After how many quic updates to print out wind-related files
+60					! After how many fire time steps to average emissions and radiation
+60					! After how many quic updates to print out averaged wind-related files
+! FIRE GRID
+1					! Number of vertical layers of fire grid cells (integer)
+0					! Vertical stretching flag: 0 = uniform dz, 1 = custom
+1.             ! m, dz
+! FILE PATH
+""
+1              ! Fuel types are in separate files
+1              ! File is stream (1) or headers (2)
+! FUEL
+1					! fuel density flag: 1 = uniform; 2 = provided thru QF_FuelDensity.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.7            ! kg/m3, fuel density
+1					! fuel moisture flag: 1 = uniform; 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.05           ! gwater/gfuel, fuel moisture
+1              ! fuel height flag: 1 = uniform
+1.             ! m, fuel height
+0              ! size scale flag: 0 = default, 1 = uniform, 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation), 5 = Fastfuels
+0              ! patch & gap flag: 0 = default (0,0), 1 = uniform(supply patch and gap value in meters [following two lines]), 2 = provided thru patch.dat & gap.dat (no interpolation)
+! IGNITION LOCATIONS
+1					! 1 = rectangle, 2 = square ring, 3 = circular ring, 4 = file (QF_Ignitions.inp), 5 = time-dependent ignitions (QF_IgnitionPattern.inp), 6 = ignite.dat (firetech)
+150            ! m, SW corner, x-dir
+100            ! m, SW corner, y-dir
+10             ! m, length in x-dir
+100            ! m, length in y-dir
+100            ! number of ignitions
+! FIREBRANDS
+0				   ! 0 = off, 1 = on
+! OUTPUT FILES (formats depend on the grid type flag)
+0					! Output gridded energy-to-atmosphere (3D fire grid + extra layers)
+0					! Output compressed array reaction rate (fire grid)
+0					! Output compressed array fuel density (fire grid)
+0					! Output gridded wind (u,v,w,sigma) (3D fire grid)
+0					! Output gridded QU winds with fire effects, instantaneous (QUIC-URB grid)
+0					! Output gridded QU winds with fire effects, averaged (QUIC-URB grid)
+0					! Output plume trajectories (ONLY FOR DEBUG)
+0					! Output compressed array fuel moisture (fire grid)
+1					! Output vertically-integrated % mass burnt (fire grid)
+0					! Output trajectories firebrands
+0					! Output compressed array emissions (fire grid)
+0					! Output gridded thermal radiation (fire grid)
+0              ! Output surface fire intensity at every fire time step
+! AUTOKILL
+0              ! Kill if the fire is out and there are no more ignitions or firebrands (0 = no, 1 = yes)
\ No newline at end of file
diff --git a/tests/data/samples/CosHill/QU_TopoInputs.inp b/tests/data/samples/CosHill/QU_TopoInputs.inp
new file mode 100644
index 0000000..a9f9843
--- /dev/null
+++ b/tests/data/samples/CosHill/QU_TopoInputs.inp
@@ -0,0 +1,13 @@
+Specify file name for custom topo (full path)
+""
+8     ! N/A, topo flag: 0 = flat, 1 = Gaussian hill, 2 = hill pass, 3 = slope mesa, 4 = canyon, 5 = custom, 6 = half circle, 7 = sinusoid, 8 = cos hill, 9 = QP_elevation.inp, 10 = terrainOutput.txt (ARA), 11 = terrain.dat (firetec)
+100   ! m, mean
+10    ! m, height
+0     ! N/A, smoothing method (0 = none (default for idealized terrain), 1 = blur, 2 = David's)
+0     ! N/A, number of smoothing passes
+100   ! N/A, number of initial SOR iterations (only if fire is run)
+4     ! N/A, sor cycles
+1.3   ! N/A, sor relaxation parameter (default for flat is 1.78, ignored if there is no terrain)
+0     ! N/A, add slope flow correction (0 = no, 1 = yes)
+Command to run slope flow code (system specific)
+""
\ No newline at end of file
diff --git a/tests/data/samples/CosHill/QU_buildings.inp b/tests/data/samples/CosHill/QU_buildings.inp
new file mode 100755
index 0000000..db22cec
--- /dev/null
+++ b/tests/data/samples/CosHill/QU_buildings.inp
@@ -0,0 +1,4 @@
+!QUIC 6.26
+0.1			!Wall roughness length (m)
+0			!Number of Buildings
+0			!Number of Polygon Building Nodes
diff --git a/tests/data/samples/CosHill/QU_fileoptions.inp b/tests/data/samples/CosHill/QU_fileoptions.inp
new file mode 100755
index 0000000..097b6fc
--- /dev/null
+++ b/tests/data/samples/CosHill/QU_fileoptions.inp
@@ -0,0 +1,6 @@
+
+2   !output data file format flag (1=ascii, 2=binary, 3=both)
+0   !flag to write out non-mass conserved initial field (uofield.dat) (1=write,0=no write)
+0   !flag to write out the file uosensorfield.dat, the initial sensor velocity field (1=write,0=no write)
+0   !flag to write out the file QU_staggered_velocity.bin used by QUIC-Pressure(1=write,0=no write)
+0   !flag to generate startup files
\ No newline at end of file
diff --git a/tests/data/samples/CosHill/QU_landuse.inp b/tests/data/samples/CosHill/QU_landuse.inp
new file mode 100755
index 0000000..edc8fe3
Binary files /dev/null and b/tests/data/samples/CosHill/QU_landuse.inp differ
diff --git a/tests/data/samples/CosHill/QU_metparams.inp b/tests/data/samples/CosHill/QU_metparams.inp
new file mode 100755
index 0000000..a41fb25
--- /dev/null
+++ b/tests/data/samples/CosHill/QU_metparams.inp
@@ -0,0 +1,7 @@
+!QUIC 6.26
+0 !Met input flag (0=QUIC,1=WRF,2=ITT MM5,3=HOTMAC)
+1 !Number of measuring sites
+1 !Maximum size of data points profiles
+sensor1 !Site Name 
+!File name
+sensor1.inp
diff --git a/tests/data/samples/CosHill/QU_movingcoords.inp b/tests/data/samples/CosHill/QU_movingcoords.inp
new file mode 100755
index 0000000..d349e21
--- /dev/null
+++ b/tests/data/samples/CosHill/QU_movingcoords.inp
@@ -0,0 +1,17 @@
+!QUIC 6.3
+0   !Moving coordinates flag (0=no, 1=yes)
+0   !Reference bearing of the ship relative to the non-rotated domain (degrees)
+!Time (Unix Time), Ship Speed (m/s), Ship Bearing (deg), Ocean Current Speed (m/s), Ocean Current Direction (deg)
+1488794400	0	0	0	0
+1488794450	0	0	0	0
+1488794500	0	0	0	0
+1488794550	0	0	0	0
+1488794600	0	0	0	0
+1488794650	0	0	0	0
+1488794700	0	0	0	0
+1488794750	0	0	0	0
+1488794800	0	0	0	0
+1488794850	0	0	0	0
+1488794900	0	0	0	0
+1488794950	0	0	0	0
+1488795000	0	0	0	0
diff --git a/tests/data/samples/CosHill/QU_simparams.inp b/tests/data/samples/CosHill/QU_simparams.inp
new file mode 100755
index 0000000..d772e37
--- /dev/null
+++ b/tests/data/samples/CosHill/QU_simparams.inp
@@ -0,0 +1,60 @@
+!QUIC 6.26
+400 !nx - Domain Length(X) Grid Cells
+300 !ny - Domain Width(Y) Grid Cells
+25 ! NZ - domain height(Z) grid cells
+2 ! DX [m]
+2 ! DY [m]
+3 !Vertical stretching flag (0 = uniform, 1 = custom, 2 = parabolic Z, 3 = parabolic DZ, 4 = exponential)
+1.000000 ! Surface DZ [m]
+5 ! Number of uniform surface cells
+! DZ array [m]
+1.000000
+1.000000
+1.000000
+1.000000
+1.000000
+1.078399
+1.313596
+1.705591
+2.254385
+2.959976
+3.822366
+4.841553
+6.017539
+7.350323
+8.839905
+10.486285
+12.289463
+14.249439
+16.366213
+18.639786
+21.070156
+23.657325
+26.401292
+29.302056
+32.354352
+1 ! Number of time increments
+0 ! UTC conversion [hours]
+! Begining of time step in Unix epoch time [integer seconds since 1970/1/1 00:00:00]
+1653321600
+2 ! Rooftop flag (0 = none, 1 = log profile, 2 = vortex)
+3 ! Upwind cavity flag (0 = none, 1 = Rockle, 2 = MVP, 3 = HMVP)
+4 ! Street canyon flag (0 = none, 1 = Roeckle, 2 = CPB, 3 = exp. param. PKK, 4 = Roeckle w/ Fackrel)
+1 ! Street intersection flag (0 = off, 1 = on)
+4 ! Wake flag (0 = none, 1 = Rockle, 2 = modified Rockle, 3 = area scaled)
+1 ! Sidewall flag (0 = off, 1 = on)
+1 ! Individual tree wake flag
+2 ! Canopy flag (0 = off, 1 = Cionco w/o wakes, 2 = Cionco w/ wakes)
+1 ! Season flag (1 = Summer, 2 = Winter, 3 = Transition)
+100 ! Maximum number of iterations
+3 ! Residual reduction (Orders of Magnitude)
+0 ! Use diffusion algorithm (0 = off, 1 = on)
+20 ! Number of diffusion iterations
+0 ! Domain rotation relative to true north [degrees] (cw = +)
+0.0 ! UTMX of domain origin [m]
+0.0 ! UTMY of domain origin [m]
+1 ! UTM zone
+17 ! UTM zone leter (1 = A, 2 = B, etc.)
+0 ! QUIC-CFD Flag
+0 ! Explosive building damage flag (1 = on)
+0 ! Building Array Flag (1 = on)
diff --git a/tests/data/samples/CosHill/Runtime_Advanced_User_Inputs.inp b/tests/data/samples/CosHill/Runtime_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..3d78e22
--- /dev/null
+++ b/tests/data/samples/CosHill/Runtime_Advanced_User_Inputs.inp
@@ -0,0 +1,2 @@
+8  ! max number of cpu threads used
+0  ! Use Adaptive Computation Window (0=Disabled 1=Enabled)
\ No newline at end of file
diff --git a/tests/data/samples/CosHill/sensor1.inp b/tests/data/samples/CosHill/sensor1.inp
new file mode 100755
index 0000000..d38d7b0
--- /dev/null
+++ b/tests/data/samples/CosHill/sensor1.inp
@@ -0,0 +1,13 @@
+sensor1 !Site Name 
+0 !Upper level flag (1 = use this profile for upper level winds)
+50 !Upper level height (meters)
+1 !Site Coordinate Flag (1=QUIC, 2=UTM, 3=Lat/Lon)
+1 !X coordinate (meters)
+1 !Y coordinate (meters)
+1653321600 !Begining of time step in Unix Epoch time (integer seconds since 1970/1/1 00:00:00)
+1 !site boundary layer flag (1 = log, 2 = exp, 3 = urban canopy, 4 = discrete data points)
+0.1 !site zo
+0. ! 1/L
+!Height (m),Speed	(m/s), Direction (deg relative to true N)
+10.	10.	270
+
diff --git a/tests/data/samples/EglinCanopyTest/Output/DebugMsg.txt b/tests/data/samples/EglinCanopyTest/Output/DebugMsg.txt
new file mode 100644
index 0000000..e69de29
diff --git a/tests/data/samples/EglinCanopyTest/Output/QU_celltype.bin b/tests/data/samples/EglinCanopyTest/Output/QU_celltype.bin
new file mode 100644
index 0000000..4beb63f
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/QU_celltype.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/QU_surface.bin b/tests/data/samples/EglinCanopyTest/Output/QU_surface.bin
new file mode 100644
index 0000000..209942e
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/QU_surface.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/QU_undisturbed.bin b/tests/data/samples/EglinCanopyTest/Output/QU_undisturbed.bin
new file mode 100644
index 0000000..e69de29
diff --git a/tests/data/samples/EglinCanopyTest/Output/co_emissions-00600.bin b/tests/data/samples/EglinCanopyTest/Output/co_emissions-00600.bin
new file mode 100644
index 0000000..f016ad2
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/co_emissions-00600.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/fire-energy_to_atmos-00000.bin b/tests/data/samples/EglinCanopyTest/Output/fire-energy_to_atmos-00000.bin
new file mode 100644
index 0000000..c3a26d8
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/fire-energy_to_atmos-00000.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/fire-energy_to_atmos-00600.bin b/tests/data/samples/EglinCanopyTest/Output/fire-energy_to_atmos-00600.bin
new file mode 100644
index 0000000..4f6a3ae
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/fire-energy_to_atmos-00600.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/fire_indexes.bin b/tests/data/samples/EglinCanopyTest/Output/fire_indexes.bin
new file mode 100644
index 0000000..bd2cc40
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/fire_indexes.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/fuels-dens-00000.bin b/tests/data/samples/EglinCanopyTest/Output/fuels-dens-00000.bin
new file mode 100644
index 0000000..ffce652
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/fuels-dens-00000.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/fuels-dens-00600.bin b/tests/data/samples/EglinCanopyTest/Output/fuels-dens-00600.bin
new file mode 100644
index 0000000..3b139e7
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/fuels-dens-00600.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/fuels_SAV.bin b/tests/data/samples/EglinCanopyTest/Output/fuels_SAV.bin
new file mode 100644
index 0000000..6aeba24
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/fuels_SAV.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/fuels_gap.bin b/tests/data/samples/EglinCanopyTest/Output/fuels_gap.bin
new file mode 100644
index 0000000..4e1ce5a
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/fuels_gap.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/fuels_patch.bin b/tests/data/samples/EglinCanopyTest/Output/fuels_patch.bin
new file mode 100644
index 0000000..91b0b62
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/fuels_patch.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/grid.bin b/tests/data/samples/EglinCanopyTest/Output/grid.bin
new file mode 100644
index 0000000..0a446c9
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/grid.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/groundfuelheight.bin b/tests/data/samples/EglinCanopyTest/Output/groundfuelheight.bin
new file mode 100644
index 0000000..8a42afe
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/groundfuelheight.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/ignite_selected.dat b/tests/data/samples/EglinCanopyTest/Output/ignite_selected.dat
new file mode 100644
index 0000000..9f05925
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/ignite_selected.dat differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/mburnt_integ-00000.bin b/tests/data/samples/EglinCanopyTest/Output/mburnt_integ-00000.bin
new file mode 100644
index 0000000..f7d5745
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/mburnt_integ-00000.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/mburnt_integ-00600.bin b/tests/data/samples/EglinCanopyTest/Output/mburnt_integ-00600.bin
new file mode 100644
index 0000000..8db38e4
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/mburnt_integ-00600.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/output_times.txt b/tests/data/samples/EglinCanopyTest/Output/output_times.txt
new file mode 100644
index 0000000..3566b14
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/Output/output_times.txt
@@ -0,0 +1,4 @@
+         0       600
+         0       600
+         0       600
+         0       600
diff --git a/tests/data/samples/EglinCanopyTest/Output/qu_windu00600.bin b/tests/data/samples/EglinCanopyTest/Output/qu_windu00600.bin
new file mode 100644
index 0000000..d2822ec
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/qu_windu00600.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/thermalradiation-00600.bin b/tests/data/samples/EglinCanopyTest/Output/thermalradiation-00600.bin
new file mode 100644
index 0000000..081b95a
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/thermalradiation-00600.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/total_emissions.csv b/tests/data/samples/EglinCanopyTest/Output/total_emissions.csv
new file mode 100644
index 0000000..26b7e6b
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/Output/total_emissions.csv
@@ -0,0 +1,3 @@
+ Time [s],CO [g],PM2.5 [g],Water [g],Total CO [g],Total PM2.5 [g],Total Water [g]
+         0,                0.00,                0.00,                0.00,                0.00,                0.00,                0.00
+       600,                0.00,                0.00,                0.00,                0.00,                0.00,                0.00
diff --git a/tests/data/samples/EglinCanopyTest/Output/total_mass_burnt.csv b/tests/data/samples/EglinCanopyTest/Output/total_mass_burnt.csv
new file mode 100644
index 0000000..b52e6f7
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/Output/total_mass_burnt.csv
@@ -0,0 +1,3 @@
+ Time [s],Mass burnt [kg],Mass burnt [% init]
+         0,     0.000000000    , 0.00
+       600,     173447.9062    ,21.72
diff --git a/tests/data/samples/EglinCanopyTest/Output/totalinitialfuelheight.bin b/tests/data/samples/EglinCanopyTest/Output/totalinitialfuelheight.bin
new file mode 100644
index 0000000..68b923d
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/totalinitialfuelheight.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/Output/windu00600.bin b/tests/data/samples/EglinCanopyTest/Output/windu00600.bin
new file mode 100644
index 0000000..f9d2f09
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Output/windu00600.bin differ
diff --git a/tests/data/samples/EglinCanopyTest/QFire_Advanced_User_Inputs.inp b/tests/data/samples/EglinCanopyTest/QFire_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..9485541
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/QFire_Advanced_User_Inputs.inp
@@ -0,0 +1,14 @@
+0.05			! N/A, fraction of the cells on fire that will launch a firebrand
+40.			! N/A, scaling factor of the radius represented by the firebrands launched
+1				! s, time step for the firebrands trajectory calculation
+10				! s, how often to launch firebrands
+500			! N/A, number of firebrands distributed over the landing area
+20.			! N/A, FB_FRACTION_LAUNCHED_to_RT_ratio
+50.			! N/A, min_b_value_coef
+0.75			! N/A, fb_frac_of_max_size
+180			! s, germination_delay
+5.				! N/A, fraction of the cell on fire (to scale w)
+50				! N/A, minimum number of ignitions via firebrands at a point
+100			! N/A, maximum number of ignitions via firebrands at a point
+0.523598		! rad, min_theta_value (pi/6)
+0.03        ! m, fb thickness
\ No newline at end of file
diff --git a/tests/data/samples/EglinCanopyTest/QFire_Bldg_Advanced_User_Inputs.inp b/tests/data/samples/EglinCanopyTest/QFire_Bldg_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..83c79bb
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/QFire_Bldg_Advanced_User_Inputs.inp
@@ -0,0 +1,9 @@
+1			! N/A, flag to convert QUIC-URB buildings to fuel (0 = no, 1 = yes)
+0.5		! kg/m3, thin fuel density within buildings (if no fuel is specified)
+2.			! N/A, attenuation coefficient within buildings
+0.1	   ! m, surface roughness within buildings
+1			! N/A, flag to convert fuel to canopy for winds (0 = no, 1 = yes)
+1			! N/A, update canopy winds when fuel is consumed (0 = no, 1 = yes)
+1			! N/A, attenuation coefficient within fuel for the Cionco profile (default = 1)
+0.1	   ! m, surface roughness within fuel (default = 0.1 m)
+
diff --git a/tests/data/samples/EglinCanopyTest/QFire_Plume_Advanced_User_Inputs.inp b/tests/data/samples/EglinCanopyTest/QFire_Plume_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..f4e2971
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/QFire_Plume_Advanced_User_Inputs.inp
@@ -0,0 +1,16 @@
+85000				! N/A, max number of plume at each time step
+1.0			! m/s, minimum vertical velocity of a plume. If wc is below minimum, the plume is eliminated (default 0.1 m/s)
+100				! m/s, maximum vertical velocity of a plume (default 100 m/s)
+0.1				! N/A, minimum speed ratio (plume vertical velocity/wind speed). If below, the plume is eliminated (default 0.1)
+0.					! 1/s2, brunt vaisala frequency squared
+1					! N/A creeping flag: 0 = off, 1 = on
+0					! Flag time step (0 = constant, 1 = adaptive)
+1              ! s, flag = 0: plumes time step plume, flag = 1: minimum plumes time step
+1              ! sor option (0 = reset lambda at each call, 1 = keep)
+10             ! alpha 2 - plume centerline
+2              ! alpha 2 - plume edges
+30.            ! deg, max angle between plumes to merging
+0.7            ! N/A, fraction of overlap of plumes for merging 
+1              ! N/A, which wplume-to-grid scheme: 0 = new, 1 = old
+10             ! N/A, number of points per edge for new scheme (only new scheme)
+0              ! N/A, w computation: 1 = max(w), 0 = sum(w^3)
diff --git a/tests/data/samples/EglinCanopyTest/QFire_Pyvista.inp b/tests/data/samples/EglinCanopyTest/QFire_Pyvista.inp
new file mode 100644
index 0000000..d2a32d6
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/QFire_Pyvista.inp
@@ -0,0 +1,28 @@
+projectdir=LineFire/
+legacyVersion=0
+Plotting=sub
+Interactive=1
+framerate=1
+title=Hitchiti500.mp4
+outdir=vistaVids/
+quivers=1
+quiverScale=1.0
+quiverDensity=0.25
+WindSlices=0
+sliceAxis=y
+sliceWind=w
+nSlices=3
+sliceOpacity=0.45
+plumeViz=1
+xyView=0
+fireTracking=0
+trackingOffset=<-250.0,-250.0,250.0>
+trackHistory=10
+#camPosition=<1900.0,500.0,500.0>
+#camFocus=<1300.0,800.0,100.0>
+#camNormal=<0,0.0,1.0>
+camOrbit=0
+windBarNormalized=none
+windBarBounds=[-10.0,10.0]
+compile=0 run=1 compiler='gfortran' compiler_mode='release' testcase='LineFire' ./adv_compile_and_run.sh
+
diff --git a/tests/data/samples/EglinCanopyTest/QP_buildout.inp b/tests/data/samples/EglinCanopyTest/QP_buildout.inp
new file mode 100755
index 0000000..e69de29
diff --git a/tests/data/samples/EglinCanopyTest/QUIC_fire.inp b/tests/data/samples/EglinCanopyTest/QUIC_fire.inp
new file mode 100644
index 0000000..b56fd14
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/QUIC_fire.inp
@@ -0,0 +1,45 @@
+1					! Fire flag: 1 = run fire; 0 = no fire
+222				! Random number generator: -1: use time and date, any other integer > 0 is used as the seed
+! FIRE TIMES
+1653321600		! When the fire is ignited in Unix Epoch time (integer seconds since 1970/1/1 00:00:00). Must be greater or equal to the time of the first wind
+600				! Total simulation time for the fire [s]
+1					! time step for the fire simulation [s]
+1					! Number of fire time steps done before updating the quic wind field (integer, >= 1)
+600					! After how many fire time steps to print out fire-related files (excluding emissions and radiation)
+600					! After how many quic updates to print out wind-related files
+600					! After how many fire time steps to average emissions and radiation
+600					! After how many quic updates to print out averaged wind-related files
+! FIRE GRID
+23					! Number of vertical layers of fire grid cells (integer)
+0					! Vertical stretching flag: 0 = uniform dz, 1 = custom
+1.             		! m, dz
+! FILE PATH
+""
+1              ! Fuel types are in separate files
+1              ! File is stream (1) or headers (2)
+! FUEL
+5					! fuel density flag: 1 = uniform; 2 = provided thru QF_FuelDensity.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+5					! fuel moisture flag: 1 = uniform; 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0              ! size scale flag: 0 = default, 1 = uniform, 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation), 5 = Fastfuels
+0              ! patch & gap flag: 0 = default (0,0), 1 = uniform(supply patch and gap value in meters [following two lines]), 2 = provided thru patch.dat & gap.dat (no interpolation)
+! IGNITION LOCATIONS
+7					! 1 = rectangle, 2 = square ring, 3 = circular ring, 4 = file (QF_Ignitions.inp), 5 = time-dependent ignitions (QF_IgnitionPattern.inp), 6 = ignite.dat (firetech)
+2            ! number of ignitions
+! FIREBRANDS
+0				   ! 0 = off, 1 = on
+! OUTPUT FILES (formats depend on the grid type flag)
+1					! Output gridded energy-to-atmosphere (3D fire grid + extra layers)
+1					! Output compressed array reaction rate (fire grid)
+1					! Output compressed array fuel density (fire grid)
+1					! Output gridded wind (u,v,w,sigma) (3D fire grid)
+1					! Output gridded QU winds with fire effects, instantaneous (QUIC-URB grid)
+0					! Output gridded QU winds with fire effects, averaged (QUIC-URB grid)
+0					! Output plume trajectories (ONLY FOR DEBUG)
+1					! Output compressed array fuel moisture (fire grid)
+1					! Output vertically-integrated % mass burnt (fire grid)
+0					! Output trajectories firebrands
+1					! Output compressed array emissions (fire grid)
+1					! Output gridded thermal radiation (fire grid)
+0                   ! Output surface fire intensity at every fire time step
+! AUTOKILL
+0              ! Kill if the fire is out and there are no more ignitions or firebrands (0 = no, 1 = yes)
\ No newline at end of file
diff --git a/tests/data/samples/EglinCanopyTest/QU_TopoInputs.inp b/tests/data/samples/EglinCanopyTest/QU_TopoInputs.inp
new file mode 100644
index 0000000..69b0bf2
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/QU_TopoInputs.inp
@@ -0,0 +1,11 @@
+Specify file name for custom topo (full path)
+"/Users/drobinson/QUIC_Fire_Terrain/Data/FirePaperTopos/canyon.dat"
+0     ! N/A, topo flag: 0 = flat, 1 = Gaussian hill, 2 = hill pass, 3 = slope mesa, 4 = canyon, 5 = custom, 6 = half circle, 7 = sinusoid, 8 = cos hill, 9 = QP_elevation.inp, 10 = terrainOutput.txt (ARA), 11 = terrain.dat (firetec)
+0     ! N/A, smoothing method (0 = none (default for idealized terrain), 1 = blur, 2 = David's)
+0     ! N/A, number of smoothing passes
+200   ! N/A, number of initial SOR iterations (only if fire is run)
+1     ! N/A, sor cycles
+1.7   ! N/A, sor relaxation parameter (default for flat is 1.78, ignored if there is no terrain)
+0     ! N/A, add slope flow correction (0 = no, 1 = yes)
+Command to run slope flow code (system specific)
+""
diff --git a/tests/data/samples/EglinCanopyTest/QU_buildings.inp b/tests/data/samples/EglinCanopyTest/QU_buildings.inp
new file mode 100755
index 0000000..db22cec
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/QU_buildings.inp
@@ -0,0 +1,4 @@
+!QUIC 6.26
+0.1			!Wall roughness length (m)
+0			!Number of Buildings
+0			!Number of Polygon Building Nodes
diff --git a/tests/data/samples/EglinCanopyTest/QU_fileoptions.inp b/tests/data/samples/EglinCanopyTest/QU_fileoptions.inp
new file mode 100755
index 0000000..2e6bd3b
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/QU_fileoptions.inp
@@ -0,0 +1,6 @@
+
+2   !output data file format flag (1=ascii, 2=binary, 3=both)
+0   !flag to write out non-mass conserved initial field (uofield.dat) (1=write,0=no write)
+0   !flag to write out the file uosensorfield.dat, the initial sensor velocity field (1=write,0=no write)
+0   !flag to write out the file QU_staggered_velocity.bin used by QUIC-Pressure(1=write,0=no write)
+0   !flag to generate startup files (0 = no, 1 = yes)
\ No newline at end of file
diff --git a/tests/data/samples/EglinCanopyTest/QU_landuse.inp b/tests/data/samples/EglinCanopyTest/QU_landuse.inp
new file mode 100755
index 0000000..edc8fe3
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/QU_landuse.inp differ
diff --git a/tests/data/samples/EglinCanopyTest/QU_metparams.inp b/tests/data/samples/EglinCanopyTest/QU_metparams.inp
new file mode 100755
index 0000000..642a0ce
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/QU_metparams.inp
@@ -0,0 +1,13 @@
+!QUIC 6.26
+0 !Met input flag (0=QUIC,1=WRF,2=ITT MM5,3=HOTMAC)
+1 !Number of measuring sites
+1 !Maximum size of data points profiles
+sensor1 !Site Name 
+!File name
+sensor1.inp
+sensor2 !Site Name 
+!File name
+sensor2.inp
+sensor3 !Site Name 
+!File name
+sensor3.inp
diff --git a/tests/data/samples/EglinCanopyTest/QU_movingcoords.inp b/tests/data/samples/EglinCanopyTest/QU_movingcoords.inp
new file mode 100755
index 0000000..d349e21
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/QU_movingcoords.inp
@@ -0,0 +1,17 @@
+!QUIC 6.3
+0   !Moving coordinates flag (0=no, 1=yes)
+0   !Reference bearing of the ship relative to the non-rotated domain (degrees)
+!Time (Unix Time), Ship Speed (m/s), Ship Bearing (deg), Ocean Current Speed (m/s), Ocean Current Direction (deg)
+1488794400	0	0	0	0
+1488794450	0	0	0	0
+1488794500	0	0	0	0
+1488794550	0	0	0	0
+1488794600	0	0	0	0
+1488794650	0	0	0	0
+1488794700	0	0	0	0
+1488794750	0	0	0	0
+1488794800	0	0	0	0
+1488794850	0	0	0	0
+1488794900	0	0	0	0
+1488794950	0	0	0	0
+1488795000	0	0	0	0
diff --git a/tests/data/samples/EglinCanopyTest/QU_simparams.inp b/tests/data/samples/EglinCanopyTest/QU_simparams.inp
new file mode 100755
index 0000000..04116e4
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/QU_simparams.inp
@@ -0,0 +1,61 @@
+!QUIC 6.26
+250 !nx - Domain Length(X) Grid Cells
+600 !ny - Domain Width(Y) Grid Cells
+26 ! NZ - domain height(Z) grid cells
+2 ! DX [m]
+2 ! DY [m]
+3 !Vertical stretching flag (0 = uniform, 1 = custom, 2 = parabolic Z, 3 = parabolic DZ, 4 = exponential)
+1.000000 ! Surface DZ [m]
+5 ! Number of uniform surface cells
+! DZ array [m]
+1.0
+1.0
+1.0
+1.0
+1.0
+1.0
+1.0
+1.0
+1.5
+2
+3
+4
+5.5
+7
+8.5
+10
+11.5
+13
+26
+40
+70
+100
+140
+180
+220
+280
+1 ! Number of time increments
+0 ! UTC conversion [hours]
+! Begining of time step in Unix epoch time [integer seconds since 1970/1/1 00:00:00]
+1653321600
+2 ! Rooftop flag (0 = none, 1 = log profile, 2 = vortex)
+3 ! Upwind cavity flag (0 = none, 1 = Rockle, 2 = MVP, 3 = HMVP)
+4 ! Street canyon flag (0 = none, 1 = Roeckle, 2 = CPB, 3 = exp. param. PKK, 4 = Roeckle w/ Fackrel)
+1 ! Street intersection flag (0 = off, 1 = on)
+4 ! Wake flag (0 = none, 1 = Rockle, 2 = modified Rockle, 3 = area scaled)
+1 ! Sidewall flag (0 = off, 1 = on)
+1 ! Individual tree wake flag
+2 ! Canopy flag (0 = off, 1 = Cionco w/o wakes, 2 = Cionco w/ wakes)
+1 ! Season flag (1 = Summer, 2 = Winter, 3 = Transition)
+10 ! Maximum number of iterations
+3 ! Residual reduction (Orders of Magnitude)
+0 ! Use diffusion algorithm (0 = off, 1 = on)
+20 ! Number of diffusion iterations
+0 ! Domain rotation relative to true north [degrees] (cw = +)
+0.0 ! UTMX of domain origin [m]
+0.0 ! UTMY of domain origin [m]
+1 ! UTM zone
+17 ! UTM zone leter (1 = A, 2 = B, etc.)
+0 ! QUIC-CFD Flag
+0 ! Explosive building damage flag (1 = on)
+0 ! Building Array Flag (1 = on)
diff --git a/tests/data/samples/EglinCanopyTest/Reference/co_emissions-00600.npz b/tests/data/samples/EglinCanopyTest/Reference/co_emissions-00600.npz
new file mode 100644
index 0000000..614f5e2
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Reference/co_emissions-00600.npz differ
diff --git a/tests/data/samples/EglinCanopyTest/Reference/fire-energy_to_atmos-00000.npz b/tests/data/samples/EglinCanopyTest/Reference/fire-energy_to_atmos-00000.npz
new file mode 100644
index 0000000..2756461
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Reference/fire-energy_to_atmos-00000.npz differ
diff --git a/tests/data/samples/EglinCanopyTest/Reference/fire-energy_to_atmos-00600.npz b/tests/data/samples/EglinCanopyTest/Reference/fire-energy_to_atmos-00600.npz
new file mode 100644
index 0000000..4b469db
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Reference/fire-energy_to_atmos-00600.npz differ
diff --git a/tests/data/samples/EglinCanopyTest/Reference/fuels-dens-00000.npz b/tests/data/samples/EglinCanopyTest/Reference/fuels-dens-00000.npz
new file mode 100644
index 0000000..9ff4d77
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Reference/fuels-dens-00000.npz differ
diff --git a/tests/data/samples/EglinCanopyTest/Reference/fuels-dens-00600.npz b/tests/data/samples/EglinCanopyTest/Reference/fuels-dens-00600.npz
new file mode 100644
index 0000000..8fe226b
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Reference/fuels-dens-00600.npz differ
diff --git a/tests/data/samples/EglinCanopyTest/Reference/groundfuelheight.npz b/tests/data/samples/EglinCanopyTest/Reference/groundfuelheight.npz
new file mode 100644
index 0000000..8a504aa
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Reference/groundfuelheight.npz differ
diff --git a/tests/data/samples/EglinCanopyTest/Reference/mburnt_integ-00000.npz b/tests/data/samples/EglinCanopyTest/Reference/mburnt_integ-00000.npz
new file mode 100644
index 0000000..b578e66
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Reference/mburnt_integ-00000.npz differ
diff --git a/tests/data/samples/EglinCanopyTest/Reference/mburnt_integ-00600.npz b/tests/data/samples/EglinCanopyTest/Reference/mburnt_integ-00600.npz
new file mode 100644
index 0000000..1e5101b
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Reference/mburnt_integ-00600.npz differ
diff --git a/tests/data/samples/EglinCanopyTest/Reference/qu_windu00600.npz b/tests/data/samples/EglinCanopyTest/Reference/qu_windu00600.npz
new file mode 100644
index 0000000..bef36fd
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Reference/qu_windu00600.npz differ
diff --git a/tests/data/samples/EglinCanopyTest/Reference/thermalradiation-00600.npz b/tests/data/samples/EglinCanopyTest/Reference/thermalradiation-00600.npz
new file mode 100644
index 0000000..3cdbe14
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Reference/thermalradiation-00600.npz differ
diff --git a/tests/data/samples/EglinCanopyTest/Reference/windu00600.npz b/tests/data/samples/EglinCanopyTest/Reference/windu00600.npz
new file mode 100644
index 0000000..c4ef42b
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/Reference/windu00600.npz differ
diff --git a/tests/data/samples/EglinCanopyTest/Runtime_Advanced_User_Inputs.inp b/tests/data/samples/EglinCanopyTest/Runtime_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..3d78e22
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/Runtime_Advanced_User_Inputs.inp
@@ -0,0 +1,2 @@
+8  ! max number of cpu threads used
+0  ! Use Adaptive Computation Window (0=Disabled 1=Enabled)
\ No newline at end of file
diff --git a/tests/data/samples/EglinCanopyTest/gridlist b/tests/data/samples/EglinCanopyTest/gridlist
new file mode 100644
index 0000000..1fd8a47
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/gridlist
@@ -0,0 +1,8 @@
+n=250 
+m=600 
+l=23
+aa1=1.
+nv=8
+dx=2
+dy=2
+dz=1.
diff --git a/tests/data/samples/EglinCanopyTest/ignite.dat b/tests/data/samples/EglinCanopyTest/ignite.dat
new file mode 100644
index 0000000..c137f6e
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/ignite.dat
@@ -0,0 +1,11 @@
+       igntype=    5
+       &atvlist
+          natv=    5
+    targettemp= 1000.00
+ flamedistance=    4.00
+/
+  330.00  201.00  330.00 1000.00    1.19  239.49
+  284.28  201.00  284.28 1000.00   10.28  248.58
+  238.56  201.00  238.56 1000.00   19.37  257.67
+  192.84  201.00  192.84 1000.00   28.46  266.76
+  147.12  201.00  147.12 1000.00   37.55  275.85
diff --git a/tests/data/samples/EglinCanopyTest/rasterorigin.txt b/tests/data/samples/EglinCanopyTest/rasterorigin.txt
new file mode 100644
index 0000000..3549dca
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/rasterorigin.txt
@@ -0,0 +1,7 @@
+0.
+0.
+752265.868913356
+3752846.04249607
+742265.868913356
+3742846.04249607
+10000
diff --git a/tests/data/samples/EglinCanopyTest/sensor1.inp b/tests/data/samples/EglinCanopyTest/sensor1.inp
new file mode 100755
index 0000000..d5b14b1
--- /dev/null
+++ b/tests/data/samples/EglinCanopyTest/sensor1.inp
@@ -0,0 +1,13 @@
+sensor1 !Site Name 
+0 !Upper level flag (1 = use this profile for upper level winds)
+50 !Upper level height (meters)
+1 !Site Coordinate Flag (1=QUIC, 2=UTM, 3=Lat/Lon)
+1 !X coordinate (meters)
+160 !Y coordinate (meters)
+1653321600 !Begining of time step in Unix Epoch time (integer seconds since 1970/1/1 00:00:00)
+1 !site boundary layer flag (1 = log, 2 = exp, 3 = urban canopy, 4 = discrete data points)
+0.1 !site zo
+0.0 ! 1/L (default = 0)
+!Height (m),Speed	(m/s), Direction (deg relative to true N)
+10. 6.	270
+
diff --git a/tests/data/samples/EglinCanopyTest/treesfueldepth.dat b/tests/data/samples/EglinCanopyTest/treesfueldepth.dat
new file mode 100644
index 0000000..16ab191
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/treesfueldepth.dat differ
diff --git a/tests/data/samples/EglinCanopyTest/treesmoist.dat b/tests/data/samples/EglinCanopyTest/treesmoist.dat
new file mode 100644
index 0000000..e706109
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/treesmoist.dat differ
diff --git a/tests/data/samples/EglinCanopyTest/treesrhof.dat b/tests/data/samples/EglinCanopyTest/treesrhof.dat
new file mode 100644
index 0000000..15b2b07
Binary files /dev/null and b/tests/data/samples/EglinCanopyTest/treesrhof.dat differ
diff --git a/tests/data/samples/GaussHill/QFire_Advanced_User_Inputs.inp b/tests/data/samples/GaussHill/QFire_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..9485541
--- /dev/null
+++ b/tests/data/samples/GaussHill/QFire_Advanced_User_Inputs.inp
@@ -0,0 +1,14 @@
+0.05			! N/A, fraction of the cells on fire that will launch a firebrand
+40.			! N/A, scaling factor of the radius represented by the firebrands launched
+1				! s, time step for the firebrands trajectory calculation
+10				! s, how often to launch firebrands
+500			! N/A, number of firebrands distributed over the landing area
+20.			! N/A, FB_FRACTION_LAUNCHED_to_RT_ratio
+50.			! N/A, min_b_value_coef
+0.75			! N/A, fb_frac_of_max_size
+180			! s, germination_delay
+5.				! N/A, fraction of the cell on fire (to scale w)
+50				! N/A, minimum number of ignitions via firebrands at a point
+100			! N/A, maximum number of ignitions via firebrands at a point
+0.523598		! rad, min_theta_value (pi/6)
+0.03        ! m, fb thickness
\ No newline at end of file
diff --git a/tests/data/samples/GaussHill/QFire_Bldg_Advanced_User_Inputs.inp b/tests/data/samples/GaussHill/QFire_Bldg_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..83c79bb
--- /dev/null
+++ b/tests/data/samples/GaussHill/QFire_Bldg_Advanced_User_Inputs.inp
@@ -0,0 +1,9 @@
+1			! N/A, flag to convert QUIC-URB buildings to fuel (0 = no, 1 = yes)
+0.5		! kg/m3, thin fuel density within buildings (if no fuel is specified)
+2.			! N/A, attenuation coefficient within buildings
+0.1	   ! m, surface roughness within buildings
+1			! N/A, flag to convert fuel to canopy for winds (0 = no, 1 = yes)
+1			! N/A, update canopy winds when fuel is consumed (0 = no, 1 = yes)
+1			! N/A, attenuation coefficient within fuel for the Cionco profile (default = 1)
+0.1	   ! m, surface roughness within fuel (default = 0.1 m)
+
diff --git a/tests/data/samples/GaussHill/QFire_Plume_Advanced_User_Inputs.inp b/tests/data/samples/GaussHill/QFire_Plume_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..35ffd07
--- /dev/null
+++ b/tests/data/samples/GaussHill/QFire_Plume_Advanced_User_Inputs.inp
@@ -0,0 +1,16 @@
+85000				! N/A, max number of plume at each time step
+0.1				! m/s, minimum vertical velocity of a plume. If wc is below minimum, the plume is eliminated (default 0.1 m/s)
+100				! m/s, maximum vertical velocity of a plume (default 100 m/s)
+0.1				! N/A, minimum speed ratio (plume vertical velocity/wind speed). If below, the plume is eliminated (default 0.1)
+0.					! 1/s2, brunt vaisala frequency squared
+1					! N/A creeping flag: 0 = off, 1 = on
+0					! Flag time step (0 = constant, 1 = adaptive)
+1              ! s, flag = 0: plumes time step plume, flag = 1: minimum plumes time step
+1              ! sor option (0 = reset lambda at each call, 1 = keep)
+10             ! alpha 2 - plume centerline
+2              ! alpha 2 - plume edges
+30.            ! deg, max angle between plumes to merging
+0.7            ! N/A, fraction of overlap of plumes for merging 
+0              ! N/A, which wplume-to-grid scheme: 0 = new, 1 = old
+10             ! N/A, number of points per edge for new scheme (only new scheme)
+1              ! N/A, w computation: 1 = max(w), 0 = sum(w^3)
diff --git a/tests/data/samples/GaussHill/QP_buildout.inp b/tests/data/samples/GaussHill/QP_buildout.inp
new file mode 100755
index 0000000..e69de29
diff --git a/tests/data/samples/GaussHill/QUIC_fire.inp b/tests/data/samples/GaussHill/QUIC_fire.inp
new file mode 100644
index 0000000..cc6e25e
--- /dev/null
+++ b/tests/data/samples/GaussHill/QUIC_fire.inp
@@ -0,0 +1,53 @@
+1					! Fire flag: 1 = run fire; 0 = no fire
+222				! Random number generator: -1: use time and date, any other integer > 0 is used as the seed
+! FIRE TIMES
+1653321600		! When the fire is ignited in Unix Epoch time (integer seconds since 1970/1/1 00:00:00). Must be greater or equal to the time of the first wind
+600				! Total simulation time for the fire [s]
+1					! time step for the fire simulation [s]
+1					! Number of fire time steps done before updating the quic wind field (integer, >= 1)
+50					! After how many fire time steps to print out fire-related files (excluding emissions and radiation)
+50					! After how many quic updates to print out wind-related files
+50					! After how many fire time steps to average emissions and radiation
+50					! After how many quic updates to print out averaged wind-related files
+! FIRE GRID
+1					! Number of vertical layers of fire grid cells (integer)
+0					! Vertical stretching flag: 0 = uniform dz, 1 = custom
+1.             ! m, dz
+! FILE PATH
+""
+1              ! Fuel types are in separate files
+1              ! File is stream (1) or headers (2)
+! FUEL
+1					! fuel density flag: 1 = uniform; 2 = provided thru QF_FuelDensity.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.7            ! kg/m3, fuel density
+1					! fuel moisture flag: 1 = uniform; 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.05           ! gwater/gfuel, fuel moisture
+1              ! fuel height flag: 1 = uniform
+1.             ! m, fuel height
+0              ! size scale flag: 0 = default, 1 = uniform, 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation), 5 = Fastfuels
+0              ! patch & gap flag: 0 = default (0,0), 1 = uniform(supply patch and gap value in meters [following two lines]), 2 = provided thru patch.dat & gap.dat (no interpolation)
+! IGNITION LOCATIONS
+1					! 1 = rectangle, 2 = square ring, 3 = circular ring, 4 = file (QF_Ignitions.inp), 5 = time-dependent ignitions (QF_IgnitionPattern.inp), 6 = ignite.dat (firetech)
+300            ! m, SW corner, x-dir
+125            ! m, SW corner, y-dir
+10             ! m, length in x-dir
+100            ! m, length in y-dir
+100            ! number of ignitions
+! FIREBRANDS
+0				   ! 0 = off, 1 = on
+! OUTPUT FILES (formats depend on the grid type flag)
+0					! Output gridded energy-to-atmosphere (3D fire grid + extra layers)
+0					! Output compressed array reaction rate (fire grid)
+0					! Output compressed array fuel density (fire grid)
+0					! Output gridded wind (u,v,w,sigma) (3D fire grid)
+0					! Output gridded QU winds with fire effects, instantaneous (QUIC-URB grid)
+0					! Output gridded QU winds with fire effects, averaged (QUIC-URB grid)
+0					! Output plume trajectories (ONLY FOR DEBUG)
+1					! Output compressed array fuel moisture (fire grid)
+1					! Output vertically-integrated % mass burnt (fire grid)
+0					! Output trajectories firebrands
+1					! Output compressed array emissions (fire grid)
+1					! Output gridded thermal radiation (fire grid)
+0              ! Output surface fire intensity at every fire time step
+! AUTOKILL
+0              ! Kill if the fire is out and there are no more ignitions or firebrands (0 = no, 1 = yes)
\ No newline at end of file
diff --git a/tests/data/samples/GaussHill/QU_TopoInputs.inp b/tests/data/samples/GaussHill/QU_TopoInputs.inp
new file mode 100644
index 0000000..558a06b
--- /dev/null
+++ b/tests/data/samples/GaussHill/QU_TopoInputs.inp
@@ -0,0 +1,15 @@
+Specify file name for custom topo (full path)
+""
+1     ! N/A, topo flag: 0 = flat, 1 = Gaussian hill, 2 = hill pass, 3 = slope mesa, 4 = canyon, 5 = custom, 6 = half circle, 7 = sinusoid, 8 = cos hill, 9 = QP_elevation.inp, 10 = terrainOutput.txt (ARA), 11 = terrain.dat (firetec)
+400   ! m, x-center
+300   ! m, y-center
+100    ! m, max height
+150   ! m, std
+0     ! N/A, smoothing method (0 = none (default for idealized terrain), 1 = blur, 2 = David's)
+0     ! N/A, number of smoothing passes
+150   ! N/A, number of initial SOR iterations (only if fire is run)
+4     ! N/A, sor cycles
+1.3   ! N/A, sor relaxation parameter (default for flat is 1.78, ignored if there is no terrain)
+0     ! N/A, add slope flow correction (0 = no, 1 = yes)
+Command to run slope flow code (system specific)
+""
diff --git a/tests/data/samples/GaussHill/QU_buildings.inp b/tests/data/samples/GaussHill/QU_buildings.inp
new file mode 100755
index 0000000..db22cec
--- /dev/null
+++ b/tests/data/samples/GaussHill/QU_buildings.inp
@@ -0,0 +1,4 @@
+!QUIC 6.26
+0.1			!Wall roughness length (m)
+0			!Number of Buildings
+0			!Number of Polygon Building Nodes
diff --git a/tests/data/samples/GaussHill/QU_fileoptions.inp b/tests/data/samples/GaussHill/QU_fileoptions.inp
new file mode 100755
index 0000000..2e6bd3b
--- /dev/null
+++ b/tests/data/samples/GaussHill/QU_fileoptions.inp
@@ -0,0 +1,6 @@
+
+2   !output data file format flag (1=ascii, 2=binary, 3=both)
+0   !flag to write out non-mass conserved initial field (uofield.dat) (1=write,0=no write)
+0   !flag to write out the file uosensorfield.dat, the initial sensor velocity field (1=write,0=no write)
+0   !flag to write out the file QU_staggered_velocity.bin used by QUIC-Pressure(1=write,0=no write)
+0   !flag to generate startup files (0 = no, 1 = yes)
\ No newline at end of file
diff --git a/tests/data/samples/GaussHill/QU_landuse.inp b/tests/data/samples/GaussHill/QU_landuse.inp
new file mode 100755
index 0000000..edc8fe3
Binary files /dev/null and b/tests/data/samples/GaussHill/QU_landuse.inp differ
diff --git a/tests/data/samples/GaussHill/QU_metparams.inp b/tests/data/samples/GaussHill/QU_metparams.inp
new file mode 100755
index 0000000..a41fb25
--- /dev/null
+++ b/tests/data/samples/GaussHill/QU_metparams.inp
@@ -0,0 +1,7 @@
+!QUIC 6.26
+0 !Met input flag (0=QUIC,1=WRF,2=ITT MM5,3=HOTMAC)
+1 !Number of measuring sites
+1 !Maximum size of data points profiles
+sensor1 !Site Name 
+!File name
+sensor1.inp
diff --git a/tests/data/samples/GaussHill/QU_movingcoords.inp b/tests/data/samples/GaussHill/QU_movingcoords.inp
new file mode 100755
index 0000000..d349e21
--- /dev/null
+++ b/tests/data/samples/GaussHill/QU_movingcoords.inp
@@ -0,0 +1,17 @@
+!QUIC 6.3
+0   !Moving coordinates flag (0=no, 1=yes)
+0   !Reference bearing of the ship relative to the non-rotated domain (degrees)
+!Time (Unix Time), Ship Speed (m/s), Ship Bearing (deg), Ocean Current Speed (m/s), Ocean Current Direction (deg)
+1488794400	0	0	0	0
+1488794450	0	0	0	0
+1488794500	0	0	0	0
+1488794550	0	0	0	0
+1488794600	0	0	0	0
+1488794650	0	0	0	0
+1488794700	0	0	0	0
+1488794750	0	0	0	0
+1488794800	0	0	0	0
+1488794850	0	0	0	0
+1488794900	0	0	0	0
+1488794950	0	0	0	0
+1488795000	0	0	0	0
diff --git a/tests/data/samples/GaussHill/QU_simparams.inp b/tests/data/samples/GaussHill/QU_simparams.inp
new file mode 100755
index 0000000..b4acc66
--- /dev/null
+++ b/tests/data/samples/GaussHill/QU_simparams.inp
@@ -0,0 +1,57 @@
+!QUIC 6.26
+400 !nx - Domain Length(X) Grid Cells
+300 !ny - Domain Width(Y) Grid Cells
+22 ! NZ - domain height(Z) grid cells
+2 ! DX [m]
+2 ! DY [m]
+3 !Vertical stretching flag (0 = uniform, 1 = custom, 2 = parabolic Z, 3 = parabolic DZ, 4 = exponential)
+1.000000 ! Surface DZ [m]
+5 ! Number of uniform surface cells
+! DZ array [m]
+1.000000
+1.000000
+1.000000
+1.000000
+1.226767
+1.907070
+3.040907
+4.628279
+6.669186
+9.163628
+12.111605
+15.513117
+19.368164
+23.676746
+28.438862
+33.654514
+39.323700
+45.446422
+52.022678
+59.052469
+66.535795
+74.220088
+1 ! Number of time increments
+0 ! UTC conversion [hours]
+! Begining of time step in Unix epoch time [integer seconds since 1970/1/1 00:00:00]
+1653321600
+2 ! Rooftop flag (0 = none, 1 = log profile, 2 = vortex)
+3 ! Upwind cavity flag (0 = none, 1 = Rockle, 2 = MVP, 3 = HMVP)
+4 ! Street canyon flag (0 = none, 1 = Roeckle, 2 = CPB, 3 = exp. param. PKK, 4 = Roeckle w/ Fackrel)
+1 ! Street intersection flag (0 = off, 1 = on)
+4 ! Wake flag (0 = none, 1 = Rockle, 2 = modified Rockle, 3 = area scaled)
+1 ! Sidewall flag (0 = off, 1 = on)
+1 ! Individual tree wake flag
+2 ! Canopy flag (0 = off, 1 = Cionco w/o wakes, 2 = Cionco w/ wakes)
+1 ! Season flag (1 = Summer, 2 = Winter, 3 = Transition)
+10 ! Maximum number of iterations
+3 ! Residual reduction (Orders of Magnitude)
+0 ! Use diffusion algorithm (0 = off, 1 = on)
+20 ! Number of diffusion iterations
+0 ! Domain rotation relative to true north [degrees] (cw = +)
+0.0 ! UTMX of domain origin [m]
+0.0 ! UTMY of domain origin [m]
+1 ! UTM zone
+17 ! UTM zone leter (1 = A, 2 = B, etc.)
+0 ! QUIC-CFD Flag
+0 ! Explosive building damage flag (1 = on)
+0 ! Building Array Flag (1 = on)
diff --git a/tests/data/samples/GaussHill/Runtime_Advanced_User_Inputs.inp b/tests/data/samples/GaussHill/Runtime_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..3d78e22
--- /dev/null
+++ b/tests/data/samples/GaussHill/Runtime_Advanced_User_Inputs.inp
@@ -0,0 +1,2 @@
+8  ! max number of cpu threads used
+0  ! Use Adaptive Computation Window (0=Disabled 1=Enabled)
\ No newline at end of file
diff --git a/tests/data/samples/GaussHill/sensor1.inp b/tests/data/samples/GaussHill/sensor1.inp
new file mode 100755
index 0000000..56d5b24
--- /dev/null
+++ b/tests/data/samples/GaussHill/sensor1.inp
@@ -0,0 +1,13 @@
+sensor1 !Site Name 
+0 !Upper level flag (1 = use this profile for upper level winds)
+50 !Upper level height (meters)
+1 !Site Coordinate Flag (1=QUIC, 2=UTM, 3=Lat/Lon)
+1 !X coordinate (meters)
+1 !Y coordinate (meters)
+1653321600 !Begining of time step in Unix Epoch time (integer seconds since 1970/1/1 00:00:00)
+1 !site boundary layer flag (1 = log, 2 = exp, 3 = urban canopy, 4 = discrete data points)
+0.1 !site zo
+0. ! 1/L (default = 0)
+!Height (m),Speed	(m/s), Direction (deg relative to true N)
+10. 6.	270
+
diff --git a/tests/data/samples/HillPass/QFire_Advanced_User_Inputs.inp b/tests/data/samples/HillPass/QFire_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..9485541
--- /dev/null
+++ b/tests/data/samples/HillPass/QFire_Advanced_User_Inputs.inp
@@ -0,0 +1,14 @@
+0.05			! N/A, fraction of the cells on fire that will launch a firebrand
+40.			! N/A, scaling factor of the radius represented by the firebrands launched
+1				! s, time step for the firebrands trajectory calculation
+10				! s, how often to launch firebrands
+500			! N/A, number of firebrands distributed over the landing area
+20.			! N/A, FB_FRACTION_LAUNCHED_to_RT_ratio
+50.			! N/A, min_b_value_coef
+0.75			! N/A, fb_frac_of_max_size
+180			! s, germination_delay
+5.				! N/A, fraction of the cell on fire (to scale w)
+50				! N/A, minimum number of ignitions via firebrands at a point
+100			! N/A, maximum number of ignitions via firebrands at a point
+0.523598		! rad, min_theta_value (pi/6)
+0.03        ! m, fb thickness
\ No newline at end of file
diff --git a/tests/data/samples/HillPass/QFire_Bldg_Advanced_User_Inputs.inp b/tests/data/samples/HillPass/QFire_Bldg_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..83c79bb
--- /dev/null
+++ b/tests/data/samples/HillPass/QFire_Bldg_Advanced_User_Inputs.inp
@@ -0,0 +1,9 @@
+1			! N/A, flag to convert QUIC-URB buildings to fuel (0 = no, 1 = yes)
+0.5		! kg/m3, thin fuel density within buildings (if no fuel is specified)
+2.			! N/A, attenuation coefficient within buildings
+0.1	   ! m, surface roughness within buildings
+1			! N/A, flag to convert fuel to canopy for winds (0 = no, 1 = yes)
+1			! N/A, update canopy winds when fuel is consumed (0 = no, 1 = yes)
+1			! N/A, attenuation coefficient within fuel for the Cionco profile (default = 1)
+0.1	   ! m, surface roughness within fuel (default = 0.1 m)
+
diff --git a/tests/data/samples/HillPass/QFire_Plume_Advanced_User_Inputs.inp b/tests/data/samples/HillPass/QFire_Plume_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..f12f227
--- /dev/null
+++ b/tests/data/samples/HillPass/QFire_Plume_Advanced_User_Inputs.inp
@@ -0,0 +1,16 @@
+85000				! N/A, max number of plume at each time step
+0.1				! m/s, minimum vertical velocity of a plume. If wc is below minimum, the plume is eliminated (default 0.1 m/s)
+100				! m/s, maximum vertical velocity of a plume (default 100 m/s)
+0.1				! N/A, minimum speed ratio (plume vertical velocity/wind speed). If below, the plume is eliminated (default 0.1)
+0.					! 1/s2, brunt vaisala frequency squared
+1					! N/A creeping flag: 0 = off, 1 = on
+0					! Flag time step (0 = constant, 1 = adaptive)
+1              ! s, flag = 0: plumes time step plume, flag = 1: minimum plumes time step
+1              ! sor option (0 = reset lambda at each call, 1 = keep)
+10             ! alpha 2 - plume centerline
+2              ! alpha 2 - plume edges
+30.            ! deg, max angle between plumes to merging
+0.7            ! N/A, fraction of overlap of plumes for merging 
+1              ! N/A, which wplume-to-grid scheme: 0 = new, 1 = old
+10             ! N/A, number of points per edge for new scheme (only new scheme)
+1              ! N/A, w computation: 1 = max(w), 0 = sum(w^3)
diff --git a/tests/data/samples/HillPass/QP_buildout.inp b/tests/data/samples/HillPass/QP_buildout.inp
new file mode 100755
index 0000000..fd084f4
--- /dev/null
+++ b/tests/data/samples/HillPass/QP_buildout.inp
@@ -0,0 +1,2 @@
+           0  ! total number of buildings
+           0  ! total number of vegitative canopies
diff --git a/tests/data/samples/HillPass/QUIC_fire.inp b/tests/data/samples/HillPass/QUIC_fire.inp
new file mode 100644
index 0000000..4688c2b
--- /dev/null
+++ b/tests/data/samples/HillPass/QUIC_fire.inp
@@ -0,0 +1,53 @@
+1					! Fire flag: 1 = run fire; 0 = no fire
+222				! Random number generator: -1: use time and date, any other integer > 0 is used as the seed
+! FIRE TIMES
+1653321600		! When the fire is ignited in Unix Epoch time (integer seconds since 1970/1/1 00:00:00). Must be greater or equal to the time of the first wind
+600				! Total simulation time for the fire [s]
+1					! time step for the fire simulation [s]
+1					! Number of fire time steps done before updating the quic wind field (integer, >= 1)
+50					! After how many fire time steps to print out fire-related files (excluding emissions and radiation)
+50					! After how many quic updates to print out wind-related files
+50					! After how many fire time steps to average emissions and radiation
+50					! After how many quic updates to print out averaged wind-related files
+! FIRE GRID
+1					! Number of vertical layers of fire grid cells (integer)
+0					! Vertical stretching flag: 0 = uniform dz, 1 = custom
+1.             ! m, dz
+! FILE PATH
+""
+1              ! Fuel types are in separate files
+1              ! File is stream (1) or headers (2)
+! FUEL
+1					! fuel density flag: 1 = uniform; 2 = provided thru QF_FuelDensity.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.7            ! kg/m3, fuel density
+1					! fuel moisture flag: 1 = uniform; 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.05           ! gwater/gfuel, fuel moisture
+1              ! fuel height flag: 1 = uniform
+1.             ! m, fuel height
+0              ! size scale flag: 0 = default, 1 = uniform, 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation), 5 = Fastfuels
+0              ! patch & gap flag: 0 = default (0,0), 1 = uniform(supply patch and gap value in meters [following two lines]), 2 = provided thru patch.dat & gap.dat (no interpolation)
+! IGNITION LOCATIONS
+1					! 1 = rectangle, 2 = square ring, 3 = circular ring, 4 = file (QF_Ignitions.inp), 5 = time-dependent ignitions (QF_IgnitionPattern.inp), 6 = ignite.dat (firetech)
+150            ! m, SW corner, x-dir
+100            ! m, SW corner, y-dir
+10             ! m, length in x-dir
+100            ! m, length in y-dir
+100            ! number of ignitions
+! FIREBRANDS
+0				   ! 0 = off, 1 = on
+! OUTPUT FILES (formats depend on the grid type flag)
+0					! Output gridded energy-to-atmosphere (3D fire grid + extra layers)
+0					! Output compressed array reaction rate (fire grid)
+1					! Output compressed array fuel density (fire grid)
+0					! Output gridded wind (u,v,w,sigma) (3D fire grid)
+1					! Output gridded QU winds with fire effects, instantaneous (QUIC-URB grid)
+0					! Output gridded QU winds with fire effects, averaged (QUIC-URB grid)
+0					! Output plume trajectories (ONLY FOR DEBUG)
+0					! Output compressed array fuel moisture (fire grid)
+1					! Output vertically-integrated % mass burnt (fire grid)
+0					! Output trajectories firebrands
+0					! Output compressed array emissions (fire grid)
+0					! Output gridded thermal radiation (fire grid)
+0              ! Output surface fire intensity at every fire time step
+! AUTOKILL
+0              ! Kill if the fire is out and there are no more ignitions or firebrands (0 = no, 1 = yes)
\ No newline at end of file
diff --git a/tests/data/samples/HillPass/QU_TopoInputs.inp b/tests/data/samples/HillPass/QU_TopoInputs.inp
new file mode 100644
index 0000000..10b66ac
--- /dev/null
+++ b/tests/data/samples/HillPass/QU_TopoInputs.inp
@@ -0,0 +1,13 @@
+Specify file name for custom topo (full path)
+""
+2     ! N/A, topo flag: 0 = flat, 1 = Gaussian hill, 2 = hill pass, 3 = slope mesa, 4 = canyon, 5 = custom, 6 = half circle, 7 = sinusoid, 8 = cos hill, 9 = QP_elevation.inp, 10 = terrainOutput.txt (ARA), 11 = terrain.dat (firetec)
+70    ! m, max height
+5     ! m, location parameter
+0     ! N/A, smoothing method (0 = none (default for idealized terrain), 1 = blur, 2 = David's)
+0     ! N/A, number of smoothing passes
+100   ! N/A, number of initial SOR iterations (only if fire is run)
+4     ! N/A, sor cycles
+1.3   ! N/A, sor relaxation parameter (default for flat is 1.78, ignored if there is no terrain)
+0     ! N/A, add slope flow correction (0 = no, 1 = yes)
+Command to run slope flow code (system specific)
+""
\ No newline at end of file
diff --git a/tests/data/samples/HillPass/QU_buildings.inp b/tests/data/samples/HillPass/QU_buildings.inp
new file mode 100755
index 0000000..db22cec
--- /dev/null
+++ b/tests/data/samples/HillPass/QU_buildings.inp
@@ -0,0 +1,4 @@
+!QUIC 6.26
+0.1			!Wall roughness length (m)
+0			!Number of Buildings
+0			!Number of Polygon Building Nodes
diff --git a/tests/data/samples/HillPass/QU_fileoptions.inp b/tests/data/samples/HillPass/QU_fileoptions.inp
new file mode 100755
index 0000000..097b6fc
--- /dev/null
+++ b/tests/data/samples/HillPass/QU_fileoptions.inp
@@ -0,0 +1,6 @@
+
+2   !output data file format flag (1=ascii, 2=binary, 3=both)
+0   !flag to write out non-mass conserved initial field (uofield.dat) (1=write,0=no write)
+0   !flag to write out the file uosensorfield.dat, the initial sensor velocity field (1=write,0=no write)
+0   !flag to write out the file QU_staggered_velocity.bin used by QUIC-Pressure(1=write,0=no write)
+0   !flag to generate startup files
\ No newline at end of file
diff --git a/tests/data/samples/HillPass/QU_landuse.inp b/tests/data/samples/HillPass/QU_landuse.inp
new file mode 100755
index 0000000..edc8fe3
Binary files /dev/null and b/tests/data/samples/HillPass/QU_landuse.inp differ
diff --git a/tests/data/samples/HillPass/QU_metparams.inp b/tests/data/samples/HillPass/QU_metparams.inp
new file mode 100755
index 0000000..a41fb25
--- /dev/null
+++ b/tests/data/samples/HillPass/QU_metparams.inp
@@ -0,0 +1,7 @@
+!QUIC 6.26
+0 !Met input flag (0=QUIC,1=WRF,2=ITT MM5,3=HOTMAC)
+1 !Number of measuring sites
+1 !Maximum size of data points profiles
+sensor1 !Site Name 
+!File name
+sensor1.inp
diff --git a/tests/data/samples/HillPass/QU_movingcoords.inp b/tests/data/samples/HillPass/QU_movingcoords.inp
new file mode 100755
index 0000000..d349e21
--- /dev/null
+++ b/tests/data/samples/HillPass/QU_movingcoords.inp
@@ -0,0 +1,17 @@
+!QUIC 6.3
+0   !Moving coordinates flag (0=no, 1=yes)
+0   !Reference bearing of the ship relative to the non-rotated domain (degrees)
+!Time (Unix Time), Ship Speed (m/s), Ship Bearing (deg), Ocean Current Speed (m/s), Ocean Current Direction (deg)
+1488794400	0	0	0	0
+1488794450	0	0	0	0
+1488794500	0	0	0	0
+1488794550	0	0	0	0
+1488794600	0	0	0	0
+1488794650	0	0	0	0
+1488794700	0	0	0	0
+1488794750	0	0	0	0
+1488794800	0	0	0	0
+1488794850	0	0	0	0
+1488794900	0	0	0	0
+1488794950	0	0	0	0
+1488795000	0	0	0	0
diff --git a/tests/data/samples/HillPass/QU_simparams.inp b/tests/data/samples/HillPass/QU_simparams.inp
new file mode 100755
index 0000000..d772e37
--- /dev/null
+++ b/tests/data/samples/HillPass/QU_simparams.inp
@@ -0,0 +1,60 @@
+!QUIC 6.26
+400 !nx - Domain Length(X) Grid Cells
+300 !ny - Domain Width(Y) Grid Cells
+25 ! NZ - domain height(Z) grid cells
+2 ! DX [m]
+2 ! DY [m]
+3 !Vertical stretching flag (0 = uniform, 1 = custom, 2 = parabolic Z, 3 = parabolic DZ, 4 = exponential)
+1.000000 ! Surface DZ [m]
+5 ! Number of uniform surface cells
+! DZ array [m]
+1.000000
+1.000000
+1.000000
+1.000000
+1.000000
+1.078399
+1.313596
+1.705591
+2.254385
+2.959976
+3.822366
+4.841553
+6.017539
+7.350323
+8.839905
+10.486285
+12.289463
+14.249439
+16.366213
+18.639786
+21.070156
+23.657325
+26.401292
+29.302056
+32.354352
+1 ! Number of time increments
+0 ! UTC conversion [hours]
+! Begining of time step in Unix epoch time [integer seconds since 1970/1/1 00:00:00]
+1653321600
+2 ! Rooftop flag (0 = none, 1 = log profile, 2 = vortex)
+3 ! Upwind cavity flag (0 = none, 1 = Rockle, 2 = MVP, 3 = HMVP)
+4 ! Street canyon flag (0 = none, 1 = Roeckle, 2 = CPB, 3 = exp. param. PKK, 4 = Roeckle w/ Fackrel)
+1 ! Street intersection flag (0 = off, 1 = on)
+4 ! Wake flag (0 = none, 1 = Rockle, 2 = modified Rockle, 3 = area scaled)
+1 ! Sidewall flag (0 = off, 1 = on)
+1 ! Individual tree wake flag
+2 ! Canopy flag (0 = off, 1 = Cionco w/o wakes, 2 = Cionco w/ wakes)
+1 ! Season flag (1 = Summer, 2 = Winter, 3 = Transition)
+100 ! Maximum number of iterations
+3 ! Residual reduction (Orders of Magnitude)
+0 ! Use diffusion algorithm (0 = off, 1 = on)
+20 ! Number of diffusion iterations
+0 ! Domain rotation relative to true north [degrees] (cw = +)
+0.0 ! UTMX of domain origin [m]
+0.0 ! UTMY of domain origin [m]
+1 ! UTM zone
+17 ! UTM zone leter (1 = A, 2 = B, etc.)
+0 ! QUIC-CFD Flag
+0 ! Explosive building damage flag (1 = on)
+0 ! Building Array Flag (1 = on)
diff --git a/tests/data/samples/HillPass/Runtime_Advanced_User_Inputs.inp b/tests/data/samples/HillPass/Runtime_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..3d78e22
--- /dev/null
+++ b/tests/data/samples/HillPass/Runtime_Advanced_User_Inputs.inp
@@ -0,0 +1,2 @@
+8  ! max number of cpu threads used
+0  ! Use Adaptive Computation Window (0=Disabled 1=Enabled)
\ No newline at end of file
diff --git a/tests/data/samples/HillPass/sensor1.inp b/tests/data/samples/HillPass/sensor1.inp
new file mode 100755
index 0000000..d38d7b0
--- /dev/null
+++ b/tests/data/samples/HillPass/sensor1.inp
@@ -0,0 +1,13 @@
+sensor1 !Site Name 
+0 !Upper level flag (1 = use this profile for upper level winds)
+50 !Upper level height (meters)
+1 !Site Coordinate Flag (1=QUIC, 2=UTM, 3=Lat/Lon)
+1 !X coordinate (meters)
+1 !Y coordinate (meters)
+1653321600 !Begining of time step in Unix Epoch time (integer seconds since 1970/1/1 00:00:00)
+1 !site boundary layer flag (1 = log, 2 = exp, 3 = urban canopy, 4 = discrete data points)
+0.1 !site zo
+0. ! 1/L
+!Height (m),Speed	(m/s), Direction (deg relative to true N)
+10.	10.	270
+
diff --git a/tests/data/samples/LineFire/Output/DebugMsg.txt b/tests/data/samples/LineFire/Output/DebugMsg.txt
new file mode 100644
index 0000000..e69de29
diff --git a/tests/data/samples/LineFire/Output/QU_celltype.bin b/tests/data/samples/LineFire/Output/QU_celltype.bin
new file mode 100644
index 0000000..c5dfe04
Binary files /dev/null and b/tests/data/samples/LineFire/Output/QU_celltype.bin differ
diff --git a/tests/data/samples/LineFire/Output/QU_surface.bin b/tests/data/samples/LineFire/Output/QU_surface.bin
new file mode 100644
index 0000000..d05965f
Binary files /dev/null and b/tests/data/samples/LineFire/Output/QU_surface.bin differ
diff --git a/tests/data/samples/LineFire/Output/QU_undisturbed.bin b/tests/data/samples/LineFire/Output/QU_undisturbed.bin
new file mode 100644
index 0000000..e69de29
diff --git a/tests/data/samples/LineFire/Output/QU_velocity.bin b/tests/data/samples/LineFire/Output/QU_velocity.bin
new file mode 100644
index 0000000..5b87f36
Binary files /dev/null and b/tests/data/samples/LineFire/Output/QU_velocity.bin differ
diff --git a/tests/data/samples/LineFire/Output/fire-energy_to_atmos-00000.bin b/tests/data/samples/LineFire/Output/fire-energy_to_atmos-00000.bin
new file mode 100644
index 0000000..da3b17b
Binary files /dev/null and b/tests/data/samples/LineFire/Output/fire-energy_to_atmos-00000.bin differ
diff --git a/tests/data/samples/LineFire/Output/fire-energy_to_atmos-00060.bin b/tests/data/samples/LineFire/Output/fire-energy_to_atmos-00060.bin
new file mode 100644
index 0000000..4160d7f
Binary files /dev/null and b/tests/data/samples/LineFire/Output/fire-energy_to_atmos-00060.bin differ
diff --git a/tests/data/samples/LineFire/Output/fire_indexes.bin b/tests/data/samples/LineFire/Output/fire_indexes.bin
new file mode 100644
index 0000000..f9c94a2
Binary files /dev/null and b/tests/data/samples/LineFire/Output/fire_indexes.bin differ
diff --git a/tests/data/samples/LineFire/Output/fuels-dens-00000.bin b/tests/data/samples/LineFire/Output/fuels-dens-00000.bin
new file mode 100644
index 0000000..aa958d6
Binary files /dev/null and b/tests/data/samples/LineFire/Output/fuels-dens-00000.bin differ
diff --git a/tests/data/samples/LineFire/Output/fuels-dens-00060.bin b/tests/data/samples/LineFire/Output/fuels-dens-00060.bin
new file mode 100644
index 0000000..28d7629
Binary files /dev/null and b/tests/data/samples/LineFire/Output/fuels-dens-00060.bin differ
diff --git a/tests/data/samples/LineFire/Output/fuels_SAV.bin b/tests/data/samples/LineFire/Output/fuels_SAV.bin
new file mode 100644
index 0000000..4e7a832
Binary files /dev/null and b/tests/data/samples/LineFire/Output/fuels_SAV.bin differ
diff --git a/tests/data/samples/LineFire/Output/fuels_gap.bin b/tests/data/samples/LineFire/Output/fuels_gap.bin
new file mode 100644
index 0000000..bf2eed8
Binary files /dev/null and b/tests/data/samples/LineFire/Output/fuels_gap.bin differ
diff --git a/tests/data/samples/LineFire/Output/fuels_patch.bin b/tests/data/samples/LineFire/Output/fuels_patch.bin
new file mode 100644
index 0000000..3fcd662
Binary files /dev/null and b/tests/data/samples/LineFire/Output/fuels_patch.bin differ
diff --git a/tests/data/samples/LineFire/Output/grid.bin b/tests/data/samples/LineFire/Output/grid.bin
new file mode 100644
index 0000000..ed75993
Binary files /dev/null and b/tests/data/samples/LineFire/Output/grid.bin differ
diff --git a/tests/data/samples/LineFire/Output/groundfuelheight.bin b/tests/data/samples/LineFire/Output/groundfuelheight.bin
new file mode 100644
index 0000000..d39256f
Binary files /dev/null and b/tests/data/samples/LineFire/Output/groundfuelheight.bin differ
diff --git a/tests/data/samples/LineFire/Output/ignite_selected.dat b/tests/data/samples/LineFire/Output/ignite_selected.dat
new file mode 100644
index 0000000..a617c27
Binary files /dev/null and b/tests/data/samples/LineFire/Output/ignite_selected.dat differ
diff --git a/tests/data/samples/LineFire/Output/mburnt_integ-00000.bin b/tests/data/samples/LineFire/Output/mburnt_integ-00000.bin
new file mode 100644
index 0000000..e40ad19
Binary files /dev/null and b/tests/data/samples/LineFire/Output/mburnt_integ-00000.bin differ
diff --git a/tests/data/samples/LineFire/Output/mburnt_integ-00060.bin b/tests/data/samples/LineFire/Output/mburnt_integ-00060.bin
new file mode 100644
index 0000000..d26b69f
Binary files /dev/null and b/tests/data/samples/LineFire/Output/mburnt_integ-00060.bin differ
diff --git a/tests/data/samples/LineFire/Output/output_times.txt b/tests/data/samples/LineFire/Output/output_times.txt
new file mode 100644
index 0000000..5f6d555
--- /dev/null
+++ b/tests/data/samples/LineFire/Output/output_times.txt
@@ -0,0 +1,4 @@
+         0        60
+         0        60
+         0        60
+         0        60
diff --git a/tests/data/samples/LineFire/Output/surfEnergy00000.bin b/tests/data/samples/LineFire/Output/surfEnergy00000.bin
new file mode 100644
index 0000000..e40ad19
Binary files /dev/null and b/tests/data/samples/LineFire/Output/surfEnergy00000.bin differ
diff --git a/tests/data/samples/LineFire/Output/surfEnergy00009.bin b/tests/data/samples/LineFire/Output/surfEnergy00009.bin
new file mode 100644
index 0000000..11816ee
Binary files /dev/null and b/tests/data/samples/LineFire/Output/surfEnergy00009.bin differ
diff --git a/tests/data/samples/LineFire/Output/timelog.log b/tests/data/samples/LineFire/Output/timelog.log
new file mode 100644
index 0000000..9f77149
--- /dev/null
+++ b/tests/data/samples/LineFire/Output/timelog.log
@@ -0,0 +1,39 @@
+ Percentage of cells with fuel (fire domain):    98.0099945     %
+ Percentage of cells with fuel (QU domain):    3.92040014     %
+ Max number of fires in a timestep:            0
+ Max number of fires in a timestep (% of fuel cells):    0.00000000     %
+ Simulation time =           38 s
+ Sub time fire:            1
+ Sub time plumes:           32
+ Sub time mass-consistency:            5
+ !==========================================
+ InitNewPlumes   :            0
+ MovePlumes      :           30
+ [CompPlumePull] :            0
+ [PullPlumeTog]  :           30
+ [TerrainPull]   :            0
+ MergePlumes     :            2
+ WPlume2QUGrid   :            0
+ UpdateAlphaDenom:            0
+ Divergence      :            0
+ SOR_Startup     :            0
+ SOR_InSimu      :            5
+ EulerUpdate     :            0
+ ConvElemToCanopy:            0
+ InjPlumes2Wind  :            0
+ InterpQU2FG     :            0
+ UpCanopy        :            0
+ UpFire          :            0
+ InitWorkArray   :            0
+ CompNewFire     :            1
+ UpBurnCenter    :            0
+ UpMoisture      :            0
+ DetActFireCells :            0
+ ChkProx2Bord    :            0
+ ChkFrontDist    :            0
+ !==========================================
+ Maximum plume height [m]:    29.7098045    
+ Maximum plume w [m/s]:    8.05976009    
+ Maximum domain height [m]:    249.999985    
+ !===== Max Numbers Per Output Period =====
+Time:    0->   60s, Max # Fires:  9658  Max # Plumes Pre-merge:  12231  Max # Plumes Post-merge:   1549
diff --git a/tests/data/samples/LineFire/Output/total_mass_burnt.csv b/tests/data/samples/LineFire/Output/total_mass_burnt.csv
new file mode 100644
index 0000000..f81e417
--- /dev/null
+++ b/tests/data/samples/LineFire/Output/total_mass_burnt.csv
@@ -0,0 +1,3 @@
+ Time [s],Mass burnt [kg],Mass burnt [% init]
+         0,     0.000000000    , 0.00
+        60,     18116.49609    ,16.50
diff --git a/tests/data/samples/LineFire/Output/totalinitialfuelheight.bin b/tests/data/samples/LineFire/Output/totalinitialfuelheight.bin
new file mode 100644
index 0000000..d39256f
Binary files /dev/null and b/tests/data/samples/LineFire/Output/totalinitialfuelheight.bin differ
diff --git a/tests/data/samples/LineFire/QFire_Advanced_User_Inputs.inp b/tests/data/samples/LineFire/QFire_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..9485541
--- /dev/null
+++ b/tests/data/samples/LineFire/QFire_Advanced_User_Inputs.inp
@@ -0,0 +1,14 @@
+0.05			! N/A, fraction of the cells on fire that will launch a firebrand
+40.			! N/A, scaling factor of the radius represented by the firebrands launched
+1				! s, time step for the firebrands trajectory calculation
+10				! s, how often to launch firebrands
+500			! N/A, number of firebrands distributed over the landing area
+20.			! N/A, FB_FRACTION_LAUNCHED_to_RT_ratio
+50.			! N/A, min_b_value_coef
+0.75			! N/A, fb_frac_of_max_size
+180			! s, germination_delay
+5.				! N/A, fraction of the cell on fire (to scale w)
+50				! N/A, minimum number of ignitions via firebrands at a point
+100			! N/A, maximum number of ignitions via firebrands at a point
+0.523598		! rad, min_theta_value (pi/6)
+0.03        ! m, fb thickness
\ No newline at end of file
diff --git a/tests/data/samples/LineFire/QFire_Bldg_Advanced_User_Inputs.inp b/tests/data/samples/LineFire/QFire_Bldg_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..83c79bb
--- /dev/null
+++ b/tests/data/samples/LineFire/QFire_Bldg_Advanced_User_Inputs.inp
@@ -0,0 +1,9 @@
+1			! N/A, flag to convert QUIC-URB buildings to fuel (0 = no, 1 = yes)
+0.5		! kg/m3, thin fuel density within buildings (if no fuel is specified)
+2.			! N/A, attenuation coefficient within buildings
+0.1	   ! m, surface roughness within buildings
+1			! N/A, flag to convert fuel to canopy for winds (0 = no, 1 = yes)
+1			! N/A, update canopy winds when fuel is consumed (0 = no, 1 = yes)
+1			! N/A, attenuation coefficient within fuel for the Cionco profile (default = 1)
+0.1	   ! m, surface roughness within fuel (default = 0.1 m)
+
diff --git a/tests/data/samples/LineFire/QFire_Plume_Advanced_User_Inputs.inp b/tests/data/samples/LineFire/QFire_Plume_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..f12f227
--- /dev/null
+++ b/tests/data/samples/LineFire/QFire_Plume_Advanced_User_Inputs.inp
@@ -0,0 +1,16 @@
+85000				! N/A, max number of plume at each time step
+0.1				! m/s, minimum vertical velocity of a plume. If wc is below minimum, the plume is eliminated (default 0.1 m/s)
+100				! m/s, maximum vertical velocity of a plume (default 100 m/s)
+0.1				! N/A, minimum speed ratio (plume vertical velocity/wind speed). If below, the plume is eliminated (default 0.1)
+0.					! 1/s2, brunt vaisala frequency squared
+1					! N/A creeping flag: 0 = off, 1 = on
+0					! Flag time step (0 = constant, 1 = adaptive)
+1              ! s, flag = 0: plumes time step plume, flag = 1: minimum plumes time step
+1              ! sor option (0 = reset lambda at each call, 1 = keep)
+10             ! alpha 2 - plume centerline
+2              ! alpha 2 - plume edges
+30.            ! deg, max angle between plumes to merging
+0.7            ! N/A, fraction of overlap of plumes for merging 
+1              ! N/A, which wplume-to-grid scheme: 0 = new, 1 = old
+10             ! N/A, number of points per edge for new scheme (only new scheme)
+1              ! N/A, w computation: 1 = max(w), 0 = sum(w^3)
diff --git a/tests/data/samples/LineFire/QP_buildout.inp b/tests/data/samples/LineFire/QP_buildout.inp
new file mode 100755
index 0000000..e69de29
diff --git a/tests/data/samples/LineFire/QUIC_fire.inp b/tests/data/samples/LineFire/QUIC_fire.inp
new file mode 100644
index 0000000..a4975be
--- /dev/null
+++ b/tests/data/samples/LineFire/QUIC_fire.inp
@@ -0,0 +1,55 @@
+1					! Fire flag: 1 = run fire; 0 = no fire
+222				! Random number generator: -1: use time and date, any other integer > 0 is used as the seed
+! FIRE TIMES
+1653321600		! When the fire is ignited in Unix Epoch time (integer seconds since 1970/1/1 00:00:00). Must be greater or equal to the time of the first wind
+60				! Total simulation time for the fire [s]
+1					! time step for the fire simulation [s]
+1					! Number of fire time steps done before updating the quic wind field (integer, >= 1)
+60					! After how many fire time steps to print out fire-related files (excluding emissions and radiation)
+60					! After how many quic updates to print out wind-related files
+60					! After how many fire time steps to average emissions and radiation
+60					! After how many quic updates to print out averaged wind-related files
+! FIRE GRID
+1					! Number of vertical layers of fire grid cells (integer)
+0					! Vertical stretching flag: 0 = uniform dz, 1 = custom
+1.             ! m, dz
+! FILE PATH
+""
+1              ! Fuel types are in separate files
+1              ! File is stream (1) or headers (2)
+! FUEL
+1					! fuel density flag: 1 = uniform; 2 = provided thru QF_FuelDensity.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.7            ! kg/m3, fuel density
+1					! fuel moisture flag: 1 = uniform; 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.05           ! gwater/gfuel, fuel moisture
+1              ! fuel height flag: 1 = uniform
+1.             ! m, fuel height
+0              ! size scale flag: 0 = default, 1 = uniform, 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation), 5 = Fastfuels
+0              ! patch & gap flag: 0 = default (0,0), 1 = uniform(supply patch and gap value in meters [following two lines]), 2 = provided thru patch.dat & gap.dat (no interpolation)
+! IGNITION LOCATIONS
+2					! 1 = rectangle, 2 = square ring, 3 = circular ring, 4 = file (QF_Ignitions.inp), 5 = time-dependent ignitions (QF_IgnitionPattern.inp), 6 = ignite.dat (firetech)
+50
+50
+200
+200
+10
+10
+2            ! number of ignitions
+! FIREBRANDS
+0				   ! 0 = off, 1 = on
+! OUTPUT FILES (formats depend on the grid type flag)
+1					! Output gridded energy-to-atmosphere (3D fire grid + extra layers)
+0					! Output compressed array reaction rate (fire grid)
+1					! Output compressed array fuel density (fire grid)
+0					! Output gridded wind (u,v,w,sigma) (3D fire grid)
+0					! Output gridded QU winds with fire effects, instantaneous (QUIC-URB grid)
+0					! Output gridded QU winds with fire effects, averaged (QUIC-URB grid)
+0					! Output plume trajectories (ONLY FOR DEBUG)
+0					! Output compressed array fuel moisture (fire grid)
+1					! Output vertically-integrated % mass burnt (fire grid)
+0					! Output trajectories firebrands
+0					! Output compressed array emissions (fire grid)
+0					! Output gridded thermal radiation (fire grid)
+1                   ! Output surface fire intensity at every fire time step
+! AUTOKILL
+0              ! Kill if the fire is out and there are no more ignitions or firebrands (0 = no, 1 = yes)
\ No newline at end of file
diff --git a/tests/data/samples/LineFire/QU_TopoInputs.inp b/tests/data/samples/LineFire/QU_TopoInputs.inp
new file mode 100644
index 0000000..0e8af68
--- /dev/null
+++ b/tests/data/samples/LineFire/QU_TopoInputs.inp
@@ -0,0 +1,11 @@
+Specify file name for custom topo (full path)
+""
+0     ! N/A, topo flag: 0 = flat, 1 = Gaussian hill, 2 = hill pass, 3 = slope mesa, 4 = canyon, 5 = custom, 6 = half circle, 7 = sinusoid, 8 = cos hill, 9 = QP_elevation.inp, 10 = terrainOutput.txt (ARA), 11 = terrain.dat (firetec)
+0     ! N/A, smoothing method (0 = none (default for idealized terrain), 1 = blur, 2 = David's)
+0     ! N/A, number of smoothing passes
+100   ! N/A, number of initial SOR iterations (only if fire is run)
+4     ! N/A, sor cycles
+1.3   ! N/A, sor relaxation parameter (default for flat is 1.78, ignored if there is no terrain)
+0     ! N/A, add slope flow correction (0 = no, 1 = yes)
+Command to run slope flow code (system specific)
+""
diff --git a/tests/data/samples/LineFire/QU_buildings.inp b/tests/data/samples/LineFire/QU_buildings.inp
new file mode 100755
index 0000000..db22cec
--- /dev/null
+++ b/tests/data/samples/LineFire/QU_buildings.inp
@@ -0,0 +1,4 @@
+!QUIC 6.26
+0.1			!Wall roughness length (m)
+0			!Number of Buildings
+0			!Number of Polygon Building Nodes
diff --git a/tests/data/samples/LineFire/QU_fileoptions.inp b/tests/data/samples/LineFire/QU_fileoptions.inp
new file mode 100755
index 0000000..2e6bd3b
--- /dev/null
+++ b/tests/data/samples/LineFire/QU_fileoptions.inp
@@ -0,0 +1,6 @@
+
+2   !output data file format flag (1=ascii, 2=binary, 3=both)
+0   !flag to write out non-mass conserved initial field (uofield.dat) (1=write,0=no write)
+0   !flag to write out the file uosensorfield.dat, the initial sensor velocity field (1=write,0=no write)
+0   !flag to write out the file QU_staggered_velocity.bin used by QUIC-Pressure(1=write,0=no write)
+0   !flag to generate startup files (0 = no, 1 = yes)
\ No newline at end of file
diff --git a/tests/data/samples/LineFire/QU_landuse.inp b/tests/data/samples/LineFire/QU_landuse.inp
new file mode 100755
index 0000000..edc8fe3
Binary files /dev/null and b/tests/data/samples/LineFire/QU_landuse.inp differ
diff --git a/tests/data/samples/LineFire/QU_metparams.inp b/tests/data/samples/LineFire/QU_metparams.inp
new file mode 100755
index 0000000..a41fb25
--- /dev/null
+++ b/tests/data/samples/LineFire/QU_metparams.inp
@@ -0,0 +1,7 @@
+!QUIC 6.26
+0 !Met input flag (0=QUIC,1=WRF,2=ITT MM5,3=HOTMAC)
+1 !Number of measuring sites
+1 !Maximum size of data points profiles
+sensor1 !Site Name 
+!File name
+sensor1.inp
diff --git a/tests/data/samples/LineFire/QU_movingcoords.inp b/tests/data/samples/LineFire/QU_movingcoords.inp
new file mode 100755
index 0000000..d349e21
--- /dev/null
+++ b/tests/data/samples/LineFire/QU_movingcoords.inp
@@ -0,0 +1,17 @@
+!QUIC 6.3
+0   !Moving coordinates flag (0=no, 1=yes)
+0   !Reference bearing of the ship relative to the non-rotated domain (degrees)
+!Time (Unix Time), Ship Speed (m/s), Ship Bearing (deg), Ocean Current Speed (m/s), Ocean Current Direction (deg)
+1488794400	0	0	0	0
+1488794450	0	0	0	0
+1488794500	0	0	0	0
+1488794550	0	0	0	0
+1488794600	0	0	0	0
+1488794650	0	0	0	0
+1488794700	0	0	0	0
+1488794750	0	0	0	0
+1488794800	0	0	0	0
+1488794850	0	0	0	0
+1488794900	0	0	0	0
+1488794950	0	0	0	0
+1488795000	0	0	0	0
diff --git a/tests/data/samples/LineFire/QU_simparams.inp b/tests/data/samples/LineFire/QU_simparams.inp
new file mode 100755
index 0000000..a8d5581
--- /dev/null
+++ b/tests/data/samples/LineFire/QU_simparams.inp
@@ -0,0 +1,60 @@
+!QUIC 6.26
+200 !nx - Domain Length(X) Grid Cells
+200 !ny - Domain Width(Y) Grid Cells
+25 ! NZ - domain height(Z) grid cells
+2 ! DX [m]
+2 ! DY [m]
+3 !Vertical stretching flag (0 = uniform, 1 = custom, 2 = parabolic Z, 3 = parabolic DZ, 4 = exponential)
+1.000000 ! Surface DZ [m]
+5 ! Number of uniform surface cells
+! DZ array [m]
+1.000000
+1.000000
+1.000000
+1.000000
+1.000000
+1.078399
+1.313596
+1.705591
+2.254385
+2.959976
+3.822366
+4.841553
+6.017539
+7.350323
+8.839905
+10.486285
+12.289463
+14.249439
+16.366213
+18.639786
+21.070156
+23.657325
+26.401292
+29.302056
+32.354352
+1 ! Number of time increments
+0 ! UTC conversion [hours]
+! Begining of time step in Unix epoch time [integer seconds since 1970/1/1 00:00:00]
+1653321600
+2 ! Rooftop flag (0 = none, 1 = log profile, 2 = vortex)
+3 ! Upwind cavity flag (0 = none, 1 = Rockle, 2 = MVP, 3 = HMVP)
+4 ! Street canyon flag (0 = none, 1 = Roeckle, 2 = CPB, 3 = exp. param. PKK, 4 = Roeckle w/ Fackrel)
+1 ! Street intersection flag (0 = off, 1 = on)
+4 ! Wake flag (0 = none, 1 = Rockle, 2 = modified Rockle, 3 = area scaled)
+1 ! Sidewall flag (0 = off, 1 = on)
+1 ! Individual tree wake flag
+2 ! Canopy flag (0 = off, 1 = Cionco w/o wakes, 2 = Cionco w/ wakes)
+1 ! Season flag (1 = Summer, 2 = Winter, 3 = Transition)
+10 ! Maximum number of iterations
+3 ! Residual reduction (Orders of Magnitude)
+0 ! Use diffusion algorithm (0 = off, 1 = on)
+20 ! Number of diffusion iterations
+0 ! Domain rotation relative to true north [degrees] (cw = +)
+0.0 ! UTMX of domain origin [m]
+0.0 ! UTMY of domain origin [m]
+1 ! UTM zone
+17 ! UTM zone leter (1 = A, 2 = B, etc.)
+0 ! QUIC-CFD Flag
+0 ! Explosive building damage flag (1 = on)
+0 ! Building Array Flag (1 = on)
diff --git a/tests/data/samples/LineFire/Reference/fire-energy_to_atmos-00000.npz b/tests/data/samples/LineFire/Reference/fire-energy_to_atmos-00000.npz
new file mode 100644
index 0000000..d10497f
Binary files /dev/null and b/tests/data/samples/LineFire/Reference/fire-energy_to_atmos-00000.npz differ
diff --git a/tests/data/samples/LineFire/Reference/fire-energy_to_atmos-00060.npz b/tests/data/samples/LineFire/Reference/fire-energy_to_atmos-00060.npz
new file mode 100644
index 0000000..b000560
Binary files /dev/null and b/tests/data/samples/LineFire/Reference/fire-energy_to_atmos-00060.npz differ
diff --git a/tests/data/samples/LineFire/Reference/fuels-dens-00000.npz b/tests/data/samples/LineFire/Reference/fuels-dens-00000.npz
new file mode 100644
index 0000000..843921b
Binary files /dev/null and b/tests/data/samples/LineFire/Reference/fuels-dens-00000.npz differ
diff --git a/tests/data/samples/LineFire/Reference/fuels-dens-00060.npz b/tests/data/samples/LineFire/Reference/fuels-dens-00060.npz
new file mode 100644
index 0000000..506949b
Binary files /dev/null and b/tests/data/samples/LineFire/Reference/fuels-dens-00060.npz differ
diff --git a/tests/data/samples/LineFire/Reference/groundfuelheight.npz b/tests/data/samples/LineFire/Reference/groundfuelheight.npz
new file mode 100644
index 0000000..f2d692c
Binary files /dev/null and b/tests/data/samples/LineFire/Reference/groundfuelheight.npz differ
diff --git a/tests/data/samples/LineFire/Reference/mburnt_integ-00000.npz b/tests/data/samples/LineFire/Reference/mburnt_integ-00000.npz
new file mode 100644
index 0000000..a2aa493
Binary files /dev/null and b/tests/data/samples/LineFire/Reference/mburnt_integ-00000.npz differ
diff --git a/tests/data/samples/LineFire/Reference/mburnt_integ-00060.npz b/tests/data/samples/LineFire/Reference/mburnt_integ-00060.npz
new file mode 100644
index 0000000..f7a68fd
Binary files /dev/null and b/tests/data/samples/LineFire/Reference/mburnt_integ-00060.npz differ
diff --git a/tests/data/samples/LineFire/Runtime_Advanced_User_Inputs.inp b/tests/data/samples/LineFire/Runtime_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..3d78e22
--- /dev/null
+++ b/tests/data/samples/LineFire/Runtime_Advanced_User_Inputs.inp
@@ -0,0 +1,2 @@
+8  ! max number of cpu threads used
+0  ! Use Adaptive Computation Window (0=Disabled 1=Enabled)
\ No newline at end of file
diff --git a/tests/data/samples/LineFire/sensor1.inp b/tests/data/samples/LineFire/sensor1.inp
new file mode 100755
index 0000000..56d5b24
--- /dev/null
+++ b/tests/data/samples/LineFire/sensor1.inp
@@ -0,0 +1,13 @@
+sensor1 !Site Name 
+0 !Upper level flag (1 = use this profile for upper level winds)
+50 !Upper level height (meters)
+1 !Site Coordinate Flag (1=QUIC, 2=UTM, 3=Lat/Lon)
+1 !X coordinate (meters)
+1 !Y coordinate (meters)
+1653321600 !Begining of time step in Unix Epoch time (integer seconds since 1970/1/1 00:00:00)
+1 !site boundary layer flag (1 = log, 2 = exp, 3 = urban canopy, 4 = discrete data points)
+0.1 !site zo
+0. ! 1/L (default = 0)
+!Height (m),Speed	(m/s), Direction (deg relative to true N)
+10. 6.	270
+
diff --git a/tests/data/samples/Sinusoid/QFire_Advanced_User_Inputs.inp b/tests/data/samples/Sinusoid/QFire_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..9485541
--- /dev/null
+++ b/tests/data/samples/Sinusoid/QFire_Advanced_User_Inputs.inp
@@ -0,0 +1,14 @@
+0.05			! N/A, fraction of the cells on fire that will launch a firebrand
+40.			! N/A, scaling factor of the radius represented by the firebrands launched
+1				! s, time step for the firebrands trajectory calculation
+10				! s, how often to launch firebrands
+500			! N/A, number of firebrands distributed over the landing area
+20.			! N/A, FB_FRACTION_LAUNCHED_to_RT_ratio
+50.			! N/A, min_b_value_coef
+0.75			! N/A, fb_frac_of_max_size
+180			! s, germination_delay
+5.				! N/A, fraction of the cell on fire (to scale w)
+50				! N/A, minimum number of ignitions via firebrands at a point
+100			! N/A, maximum number of ignitions via firebrands at a point
+0.523598		! rad, min_theta_value (pi/6)
+0.03        ! m, fb thickness
\ No newline at end of file
diff --git a/tests/data/samples/Sinusoid/QFire_Bldg_Advanced_User_Inputs.inp b/tests/data/samples/Sinusoid/QFire_Bldg_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..83c79bb
--- /dev/null
+++ b/tests/data/samples/Sinusoid/QFire_Bldg_Advanced_User_Inputs.inp
@@ -0,0 +1,9 @@
+1			! N/A, flag to convert QUIC-URB buildings to fuel (0 = no, 1 = yes)
+0.5		! kg/m3, thin fuel density within buildings (if no fuel is specified)
+2.			! N/A, attenuation coefficient within buildings
+0.1	   ! m, surface roughness within buildings
+1			! N/A, flag to convert fuel to canopy for winds (0 = no, 1 = yes)
+1			! N/A, update canopy winds when fuel is consumed (0 = no, 1 = yes)
+1			! N/A, attenuation coefficient within fuel for the Cionco profile (default = 1)
+0.1	   ! m, surface roughness within fuel (default = 0.1 m)
+
diff --git a/tests/data/samples/Sinusoid/QFire_Plume_Advanced_User_Inputs.inp b/tests/data/samples/Sinusoid/QFire_Plume_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..f12f227
--- /dev/null
+++ b/tests/data/samples/Sinusoid/QFire_Plume_Advanced_User_Inputs.inp
@@ -0,0 +1,16 @@
+85000				! N/A, max number of plume at each time step
+0.1				! m/s, minimum vertical velocity of a plume. If wc is below minimum, the plume is eliminated (default 0.1 m/s)
+100				! m/s, maximum vertical velocity of a plume (default 100 m/s)
+0.1				! N/A, minimum speed ratio (plume vertical velocity/wind speed). If below, the plume is eliminated (default 0.1)
+0.					! 1/s2, brunt vaisala frequency squared
+1					! N/A creeping flag: 0 = off, 1 = on
+0					! Flag time step (0 = constant, 1 = adaptive)
+1              ! s, flag = 0: plumes time step plume, flag = 1: minimum plumes time step
+1              ! sor option (0 = reset lambda at each call, 1 = keep)
+10             ! alpha 2 - plume centerline
+2              ! alpha 2 - plume edges
+30.            ! deg, max angle between plumes to merging
+0.7            ! N/A, fraction of overlap of plumes for merging 
+1              ! N/A, which wplume-to-grid scheme: 0 = new, 1 = old
+10             ! N/A, number of points per edge for new scheme (only new scheme)
+1              ! N/A, w computation: 1 = max(w), 0 = sum(w^3)
diff --git a/tests/data/samples/Sinusoid/QP_buildout.inp b/tests/data/samples/Sinusoid/QP_buildout.inp
new file mode 100755
index 0000000..fd084f4
--- /dev/null
+++ b/tests/data/samples/Sinusoid/QP_buildout.inp
@@ -0,0 +1,2 @@
+           0  ! total number of buildings
+           0  ! total number of vegitative canopies
diff --git a/tests/data/samples/Sinusoid/QUIC_fire.inp b/tests/data/samples/Sinusoid/QUIC_fire.inp
new file mode 100644
index 0000000..4688c2b
--- /dev/null
+++ b/tests/data/samples/Sinusoid/QUIC_fire.inp
@@ -0,0 +1,53 @@
+1					! Fire flag: 1 = run fire; 0 = no fire
+222				! Random number generator: -1: use time and date, any other integer > 0 is used as the seed
+! FIRE TIMES
+1653321600		! When the fire is ignited in Unix Epoch time (integer seconds since 1970/1/1 00:00:00). Must be greater or equal to the time of the first wind
+600				! Total simulation time for the fire [s]
+1					! time step for the fire simulation [s]
+1					! Number of fire time steps done before updating the quic wind field (integer, >= 1)
+50					! After how many fire time steps to print out fire-related files (excluding emissions and radiation)
+50					! After how many quic updates to print out wind-related files
+50					! After how many fire time steps to average emissions and radiation
+50					! After how many quic updates to print out averaged wind-related files
+! FIRE GRID
+1					! Number of vertical layers of fire grid cells (integer)
+0					! Vertical stretching flag: 0 = uniform dz, 1 = custom
+1.             ! m, dz
+! FILE PATH
+""
+1              ! Fuel types are in separate files
+1              ! File is stream (1) or headers (2)
+! FUEL
+1					! fuel density flag: 1 = uniform; 2 = provided thru QF_FuelDensity.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.7            ! kg/m3, fuel density
+1					! fuel moisture flag: 1 = uniform; 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.05           ! gwater/gfuel, fuel moisture
+1              ! fuel height flag: 1 = uniform
+1.             ! m, fuel height
+0              ! size scale flag: 0 = default, 1 = uniform, 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation), 5 = Fastfuels
+0              ! patch & gap flag: 0 = default (0,0), 1 = uniform(supply patch and gap value in meters [following two lines]), 2 = provided thru patch.dat & gap.dat (no interpolation)
+! IGNITION LOCATIONS
+1					! 1 = rectangle, 2 = square ring, 3 = circular ring, 4 = file (QF_Ignitions.inp), 5 = time-dependent ignitions (QF_IgnitionPattern.inp), 6 = ignite.dat (firetech)
+150            ! m, SW corner, x-dir
+100            ! m, SW corner, y-dir
+10             ! m, length in x-dir
+100            ! m, length in y-dir
+100            ! number of ignitions
+! FIREBRANDS
+0				   ! 0 = off, 1 = on
+! OUTPUT FILES (formats depend on the grid type flag)
+0					! Output gridded energy-to-atmosphere (3D fire grid + extra layers)
+0					! Output compressed array reaction rate (fire grid)
+1					! Output compressed array fuel density (fire grid)
+0					! Output gridded wind (u,v,w,sigma) (3D fire grid)
+1					! Output gridded QU winds with fire effects, instantaneous (QUIC-URB grid)
+0					! Output gridded QU winds with fire effects, averaged (QUIC-URB grid)
+0					! Output plume trajectories (ONLY FOR DEBUG)
+0					! Output compressed array fuel moisture (fire grid)
+1					! Output vertically-integrated % mass burnt (fire grid)
+0					! Output trajectories firebrands
+0					! Output compressed array emissions (fire grid)
+0					! Output gridded thermal radiation (fire grid)
+0              ! Output surface fire intensity at every fire time step
+! AUTOKILL
+0              ! Kill if the fire is out and there are no more ignitions or firebrands (0 = no, 1 = yes)
\ No newline at end of file
diff --git a/tests/data/samples/Sinusoid/QU_TopoInputs.inp b/tests/data/samples/Sinusoid/QU_TopoInputs.inp
new file mode 100644
index 0000000..e4d20d0
--- /dev/null
+++ b/tests/data/samples/Sinusoid/QU_TopoInputs.inp
@@ -0,0 +1,13 @@
+Specify file name for custom topo (full path)
+""
+7     ! N/A, topo flag: 0 = flat, 1 = Gaussian hill, 2 = hill pass, 3 = slope mesa, 4 = canyon, 5 = custom, 6 = half circle, 7 = sinusoid, 8 = cos hill, 9 = QP_elevation.inp, 10 = terrainOutput.txt (ARA), 11 = terrain.dat (firetec)
+20    ! m, mean
+80    ! m, amplitude
+0     ! N/A, smoothing method (0 = none (default for idealized terrain), 1 = blur, 2 = David's)
+0     ! N/A, number of smoothing passes
+100   ! N/A, number of initial SOR iterations (only if fire is run)
+4     ! N/A, sor cycles
+1.3   ! N/A, sor relaxation parameter (default for flat is 1.78, ignored if there is no terrain)
+0     ! N/A, add slope flow correction (0 = no, 1 = yes)
+Command to run slope flow code (system specific)
+""
\ No newline at end of file
diff --git a/tests/data/samples/Sinusoid/QU_buildings.inp b/tests/data/samples/Sinusoid/QU_buildings.inp
new file mode 100755
index 0000000..db22cec
--- /dev/null
+++ b/tests/data/samples/Sinusoid/QU_buildings.inp
@@ -0,0 +1,4 @@
+!QUIC 6.26
+0.1			!Wall roughness length (m)
+0			!Number of Buildings
+0			!Number of Polygon Building Nodes
diff --git a/tests/data/samples/Sinusoid/QU_fileoptions.inp b/tests/data/samples/Sinusoid/QU_fileoptions.inp
new file mode 100755
index 0000000..097b6fc
--- /dev/null
+++ b/tests/data/samples/Sinusoid/QU_fileoptions.inp
@@ -0,0 +1,6 @@
+
+2   !output data file format flag (1=ascii, 2=binary, 3=both)
+0   !flag to write out non-mass conserved initial field (uofield.dat) (1=write,0=no write)
+0   !flag to write out the file uosensorfield.dat, the initial sensor velocity field (1=write,0=no write)
+0   !flag to write out the file QU_staggered_velocity.bin used by QUIC-Pressure(1=write,0=no write)
+0   !flag to generate startup files
\ No newline at end of file
diff --git a/tests/data/samples/Sinusoid/QU_landuse.inp b/tests/data/samples/Sinusoid/QU_landuse.inp
new file mode 100755
index 0000000..edc8fe3
Binary files /dev/null and b/tests/data/samples/Sinusoid/QU_landuse.inp differ
diff --git a/tests/data/samples/Sinusoid/QU_metparams.inp b/tests/data/samples/Sinusoid/QU_metparams.inp
new file mode 100755
index 0000000..a41fb25
--- /dev/null
+++ b/tests/data/samples/Sinusoid/QU_metparams.inp
@@ -0,0 +1,7 @@
+!QUIC 6.26
+0 !Met input flag (0=QUIC,1=WRF,2=ITT MM5,3=HOTMAC)
+1 !Number of measuring sites
+1 !Maximum size of data points profiles
+sensor1 !Site Name 
+!File name
+sensor1.inp
diff --git a/tests/data/samples/Sinusoid/QU_movingcoords.inp b/tests/data/samples/Sinusoid/QU_movingcoords.inp
new file mode 100755
index 0000000..d349e21
--- /dev/null
+++ b/tests/data/samples/Sinusoid/QU_movingcoords.inp
@@ -0,0 +1,17 @@
+!QUIC 6.3
+0   !Moving coordinates flag (0=no, 1=yes)
+0   !Reference bearing of the ship relative to the non-rotated domain (degrees)
+!Time (Unix Time), Ship Speed (m/s), Ship Bearing (deg), Ocean Current Speed (m/s), Ocean Current Direction (deg)
+1488794400	0	0	0	0
+1488794450	0	0	0	0
+1488794500	0	0	0	0
+1488794550	0	0	0	0
+1488794600	0	0	0	0
+1488794650	0	0	0	0
+1488794700	0	0	0	0
+1488794750	0	0	0	0
+1488794800	0	0	0	0
+1488794850	0	0	0	0
+1488794900	0	0	0	0
+1488794950	0	0	0	0
+1488795000	0	0	0	0
diff --git a/tests/data/samples/Sinusoid/QU_simparams.inp b/tests/data/samples/Sinusoid/QU_simparams.inp
new file mode 100755
index 0000000..d772e37
--- /dev/null
+++ b/tests/data/samples/Sinusoid/QU_simparams.inp
@@ -0,0 +1,60 @@
+!QUIC 6.26
+400 !nx - Domain Length(X) Grid Cells
+300 !ny - Domain Width(Y) Grid Cells
+25 ! NZ - domain height(Z) grid cells
+2 ! DX [m]
+2 ! DY [m]
+3 !Vertical stretching flag (0 = uniform, 1 = custom, 2 = parabolic Z, 3 = parabolic DZ, 4 = exponential)
+1.000000 ! Surface DZ [m]
+5 ! Number of uniform surface cells
+! DZ array [m]
+1.000000
+1.000000
+1.000000
+1.000000
+1.000000
+1.078399
+1.313596
+1.705591
+2.254385
+2.959976
+3.822366
+4.841553
+6.017539
+7.350323
+8.839905
+10.486285
+12.289463
+14.249439
+16.366213
+18.639786
+21.070156
+23.657325
+26.401292
+29.302056
+32.354352
+1 ! Number of time increments
+0 ! UTC conversion [hours]
+! Begining of time step in Unix epoch time [integer seconds since 1970/1/1 00:00:00]
+1653321600
+2 ! Rooftop flag (0 = none, 1 = log profile, 2 = vortex)
+3 ! Upwind cavity flag (0 = none, 1 = Rockle, 2 = MVP, 3 = HMVP)
+4 ! Street canyon flag (0 = none, 1 = Roeckle, 2 = CPB, 3 = exp. param. PKK, 4 = Roeckle w/ Fackrel)
+1 ! Street intersection flag (0 = off, 1 = on)
+4 ! Wake flag (0 = none, 1 = Rockle, 2 = modified Rockle, 3 = area scaled)
+1 ! Sidewall flag (0 = off, 1 = on)
+1 ! Individual tree wake flag
+2 ! Canopy flag (0 = off, 1 = Cionco w/o wakes, 2 = Cionco w/ wakes)
+1 ! Season flag (1 = Summer, 2 = Winter, 3 = Transition)
+100 ! Maximum number of iterations
+3 ! Residual reduction (Orders of Magnitude)
+0 ! Use diffusion algorithm (0 = off, 1 = on)
+20 ! Number of diffusion iterations
+0 ! Domain rotation relative to true north [degrees] (cw = +)
+0.0 ! UTMX of domain origin [m]
+0.0 ! UTMY of domain origin [m]
+1 ! UTM zone
+17 ! UTM zone leter (1 = A, 2 = B, etc.)
+0 ! QUIC-CFD Flag
+0 ! Explosive building damage flag (1 = on)
+0 ! Building Array Flag (1 = on)
diff --git a/tests/data/samples/Sinusoid/Runtime_Advanced_User_Inputs.inp b/tests/data/samples/Sinusoid/Runtime_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..3d78e22
--- /dev/null
+++ b/tests/data/samples/Sinusoid/Runtime_Advanced_User_Inputs.inp
@@ -0,0 +1,2 @@
+8  ! max number of cpu threads used
+0  ! Use Adaptive Computation Window (0=Disabled 1=Enabled)
\ No newline at end of file
diff --git a/tests/data/samples/Sinusoid/sensor1.inp b/tests/data/samples/Sinusoid/sensor1.inp
new file mode 100755
index 0000000..d38d7b0
--- /dev/null
+++ b/tests/data/samples/Sinusoid/sensor1.inp
@@ -0,0 +1,13 @@
+sensor1 !Site Name 
+0 !Upper level flag (1 = use this profile for upper level winds)
+50 !Upper level height (meters)
+1 !Site Coordinate Flag (1=QUIC, 2=UTM, 3=Lat/Lon)
+1 !X coordinate (meters)
+1 !Y coordinate (meters)
+1653321600 !Begining of time step in Unix Epoch time (integer seconds since 1970/1/1 00:00:00)
+1 !site boundary layer flag (1 = log, 2 = exp, 3 = urban canopy, 4 = discrete data points)
+0.1 !site zo
+0. ! 1/L
+!Height (m),Speed	(m/s), Direction (deg relative to true N)
+10.	10.	270
+
diff --git a/tests/data/samples/SlopeMesa/QFire_Advanced_User_Inputs.inp b/tests/data/samples/SlopeMesa/QFire_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..9485541
--- /dev/null
+++ b/tests/data/samples/SlopeMesa/QFire_Advanced_User_Inputs.inp
@@ -0,0 +1,14 @@
+0.05			! N/A, fraction of the cells on fire that will launch a firebrand
+40.			! N/A, scaling factor of the radius represented by the firebrands launched
+1				! s, time step for the firebrands trajectory calculation
+10				! s, how often to launch firebrands
+500			! N/A, number of firebrands distributed over the landing area
+20.			! N/A, FB_FRACTION_LAUNCHED_to_RT_ratio
+50.			! N/A, min_b_value_coef
+0.75			! N/A, fb_frac_of_max_size
+180			! s, germination_delay
+5.				! N/A, fraction of the cell on fire (to scale w)
+50				! N/A, minimum number of ignitions via firebrands at a point
+100			! N/A, maximum number of ignitions via firebrands at a point
+0.523598		! rad, min_theta_value (pi/6)
+0.03        ! m, fb thickness
\ No newline at end of file
diff --git a/tests/data/samples/SlopeMesa/QFire_Bldg_Advanced_User_Inputs.inp b/tests/data/samples/SlopeMesa/QFire_Bldg_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..83c79bb
--- /dev/null
+++ b/tests/data/samples/SlopeMesa/QFire_Bldg_Advanced_User_Inputs.inp
@@ -0,0 +1,9 @@
+1			! N/A, flag to convert QUIC-URB buildings to fuel (0 = no, 1 = yes)
+0.5		! kg/m3, thin fuel density within buildings (if no fuel is specified)
+2.			! N/A, attenuation coefficient within buildings
+0.1	   ! m, surface roughness within buildings
+1			! N/A, flag to convert fuel to canopy for winds (0 = no, 1 = yes)
+1			! N/A, update canopy winds when fuel is consumed (0 = no, 1 = yes)
+1			! N/A, attenuation coefficient within fuel for the Cionco profile (default = 1)
+0.1	   ! m, surface roughness within fuel (default = 0.1 m)
+
diff --git a/tests/data/samples/SlopeMesa/QFire_Plume_Advanced_User_Inputs.inp b/tests/data/samples/SlopeMesa/QFire_Plume_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..f12f227
--- /dev/null
+++ b/tests/data/samples/SlopeMesa/QFire_Plume_Advanced_User_Inputs.inp
@@ -0,0 +1,16 @@
+85000				! N/A, max number of plume at each time step
+0.1				! m/s, minimum vertical velocity of a plume. If wc is below minimum, the plume is eliminated (default 0.1 m/s)
+100				! m/s, maximum vertical velocity of a plume (default 100 m/s)
+0.1				! N/A, minimum speed ratio (plume vertical velocity/wind speed). If below, the plume is eliminated (default 0.1)
+0.					! 1/s2, brunt vaisala frequency squared
+1					! N/A creeping flag: 0 = off, 1 = on
+0					! Flag time step (0 = constant, 1 = adaptive)
+1              ! s, flag = 0: plumes time step plume, flag = 1: minimum plumes time step
+1              ! sor option (0 = reset lambda at each call, 1 = keep)
+10             ! alpha 2 - plume centerline
+2              ! alpha 2 - plume edges
+30.            ! deg, max angle between plumes to merging
+0.7            ! N/A, fraction of overlap of plumes for merging 
+1              ! N/A, which wplume-to-grid scheme: 0 = new, 1 = old
+10             ! N/A, number of points per edge for new scheme (only new scheme)
+1              ! N/A, w computation: 1 = max(w), 0 = sum(w^3)
diff --git a/tests/data/samples/SlopeMesa/QP_buildout.inp b/tests/data/samples/SlopeMesa/QP_buildout.inp
new file mode 100755
index 0000000..e69de29
diff --git a/tests/data/samples/SlopeMesa/QUIC_fire.inp b/tests/data/samples/SlopeMesa/QUIC_fire.inp
new file mode 100644
index 0000000..e5ba04b
--- /dev/null
+++ b/tests/data/samples/SlopeMesa/QUIC_fire.inp
@@ -0,0 +1,53 @@
+1					! Fire flag: 1 = run fire; 0 = no fire
+222				! Random number generator: -1: use time and date, any other integer > 0 is used as the seed
+! FIRE TIMES
+1653321600		! When the fire is ignited in Unix Epoch time (integer seconds since 1970/1/1 00:00:00). Must be greater or equal to the time of the first wind
+100				! Total simulation time for the fire [s]
+1					! time step for the fire simulation [s]
+1					! Number of fire time steps done before updating the quic wind field (integer, >= 1)
+50					! After how many fire time steps to print out fire-related files (excluding emissions and radiation)
+50					! After how many quic updates to print out wind-related files
+50					! After how many fire time steps to average emissions and radiation
+50					! After how many quic updates to print out averaged wind-related files
+! FIRE GRID
+1					! Number of vertical layers of fire grid cells (integer)
+0					! Vertical stretching flag: 0 = uniform dz, 1 = custom
+1.             ! m, dz
+! FILE PATH
+""
+1              ! Fuel types are in separate files
+1              ! File is stream (1) or headers (2)
+! FUEL
+1					! fuel density flag: 1 = uniform; 2 = provided thru QF_FuelDensity.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.7            ! kg/m3, fuel density
+1					! fuel moisture flag: 1 = uniform; 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.05           ! gwater/gfuel, fuel moisture
+1              ! fuel height flag: 1 = uniform
+1.             ! m, fuel height
+0              ! size scale flag: 0 = default, 1 = uniform, 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation), 5 = Fastfuels
+0              ! patch & gap flag: 0 = default (0,0), 1 = uniform(supply patch and gap value in meters [following two lines]), 2 = provided thru patch.dat & gap.dat (no interpolation)
+! IGNITION LOCATIONS
+1					! 1 = rectangle, 2 = square ring, 3 = circular ring, 4 = file (QF_Ignitions.inp), 5 = time-dependent ignitions (QF_IgnitionPattern.inp), 6 = ignite.dat (firetech)
+150            ! m, SW corner, x-dir
+100            ! m, SW corner, y-dir
+10             ! m, length in x-dir
+100            ! m, length in y-dir
+100            ! number of ignitions
+! FIREBRANDS
+0				   ! 0 = off, 1 = on
+! OUTPUT FILES (formats depend on the grid type flag)
+0					! Output gridded energy-to-atmosphere (3D fire grid + extra layers)
+0					! Output compressed array reaction rate (fire grid)
+0					! Output compressed array fuel density (fire grid)
+0					! Output gridded wind (u,v,w,sigma) (3D fire grid)
+0					! Output gridded QU winds with fire effects, instantaneous (QUIC-URB grid)
+0					! Output gridded QU winds with fire effects, averaged (QUIC-URB grid)
+0					! Output plume trajectories (ONLY FOR DEBUG)
+0					! Output compressed array fuel moisture (fire grid)
+1					! Output vertically-integrated % mass burnt (fire grid)
+0					! Output trajectories firebrands
+0					! Output compressed array emissions (fire grid)
+0					! Output gridded thermal radiation (fire grid)
+1                   ! Output surface fire intensity at every fire time step
+! AUTOKILL
+0              ! Kill if the fire is out and there are no more ignitions or firebrands (0 = no, 1 = yes)
\ No newline at end of file
diff --git a/tests/data/samples/SlopeMesa/QU_TopoInputs.inp b/tests/data/samples/SlopeMesa/QU_TopoInputs.inp
new file mode 100644
index 0000000..120d4fb
--- /dev/null
+++ b/tests/data/samples/SlopeMesa/QU_TopoInputs.inp
@@ -0,0 +1,14 @@
+Specify file name for custom topo (full path)
+""
+3     ! N/A, topo flag: 0 = flat, 1 = Gaussian hill, 2 = hill pass, 3 = slope mesa, 4 = canyon, 5 = custom, 6 = half circle, 7 = sinusoid, 8 = cos hill, 9 = QP_elevation.inp, 10 = terrainOutput.txt (ARA), 11 = terrain.dat (firetec)
+0     ! N/A, slope axis (0 = x, 1 = y)
+0.2   ! N/A, slope val (dh/dx)
+0.5   ! N/A, fraction of domain that is flat
+0     ! N/A, smoothing method (0 = none (default for idealized terrain), 1 = blur, 2 = David's)
+0     ! N/A, number of smoothing passes
+100   ! N/A, number of initial SOR iterations (only if fire is run)
+4     ! N/A, sor cycles
+1.3   ! N/A, sor relaxation parameter (default for flat is 1.78, ignored if there is no terrain)
+0     ! N/A, add slope flow correction (0 = no, 1 = yes)
+Command to run slope flow code (system specific)
+""
\ No newline at end of file
diff --git a/tests/data/samples/SlopeMesa/QU_buildings.inp b/tests/data/samples/SlopeMesa/QU_buildings.inp
new file mode 100755
index 0000000..db22cec
--- /dev/null
+++ b/tests/data/samples/SlopeMesa/QU_buildings.inp
@@ -0,0 +1,4 @@
+!QUIC 6.26
+0.1			!Wall roughness length (m)
+0			!Number of Buildings
+0			!Number of Polygon Building Nodes
diff --git a/tests/data/samples/SlopeMesa/QU_fileoptions.inp b/tests/data/samples/SlopeMesa/QU_fileoptions.inp
new file mode 100755
index 0000000..097b6fc
--- /dev/null
+++ b/tests/data/samples/SlopeMesa/QU_fileoptions.inp
@@ -0,0 +1,6 @@
+
+2   !output data file format flag (1=ascii, 2=binary, 3=both)
+0   !flag to write out non-mass conserved initial field (uofield.dat) (1=write,0=no write)
+0   !flag to write out the file uosensorfield.dat, the initial sensor velocity field (1=write,0=no write)
+0   !flag to write out the file QU_staggered_velocity.bin used by QUIC-Pressure(1=write,0=no write)
+0   !flag to generate startup files
\ No newline at end of file
diff --git a/tests/data/samples/SlopeMesa/QU_landuse.inp b/tests/data/samples/SlopeMesa/QU_landuse.inp
new file mode 100755
index 0000000..edc8fe3
Binary files /dev/null and b/tests/data/samples/SlopeMesa/QU_landuse.inp differ
diff --git a/tests/data/samples/SlopeMesa/QU_metparams.inp b/tests/data/samples/SlopeMesa/QU_metparams.inp
new file mode 100755
index 0000000..a41fb25
--- /dev/null
+++ b/tests/data/samples/SlopeMesa/QU_metparams.inp
@@ -0,0 +1,7 @@
+!QUIC 6.26
+0 !Met input flag (0=QUIC,1=WRF,2=ITT MM5,3=HOTMAC)
+1 !Number of measuring sites
+1 !Maximum size of data points profiles
+sensor1 !Site Name 
+!File name
+sensor1.inp
diff --git a/tests/data/samples/SlopeMesa/QU_movingcoords.inp b/tests/data/samples/SlopeMesa/QU_movingcoords.inp
new file mode 100755
index 0000000..d349e21
--- /dev/null
+++ b/tests/data/samples/SlopeMesa/QU_movingcoords.inp
@@ -0,0 +1,17 @@
+!QUIC 6.3
+0   !Moving coordinates flag (0=no, 1=yes)
+0   !Reference bearing of the ship relative to the non-rotated domain (degrees)
+!Time (Unix Time), Ship Speed (m/s), Ship Bearing (deg), Ocean Current Speed (m/s), Ocean Current Direction (deg)
+1488794400	0	0	0	0
+1488794450	0	0	0	0
+1488794500	0	0	0	0
+1488794550	0	0	0	0
+1488794600	0	0	0	0
+1488794650	0	0	0	0
+1488794700	0	0	0	0
+1488794750	0	0	0	0
+1488794800	0	0	0	0
+1488794850	0	0	0	0
+1488794900	0	0	0	0
+1488794950	0	0	0	0
+1488795000	0	0	0	0
diff --git a/tests/data/samples/SlopeMesa/QU_simparams.inp b/tests/data/samples/SlopeMesa/QU_simparams.inp
new file mode 100755
index 0000000..d772e37
--- /dev/null
+++ b/tests/data/samples/SlopeMesa/QU_simparams.inp
@@ -0,0 +1,60 @@
+!QUIC 6.26
+400 !nx - Domain Length(X) Grid Cells
+300 !ny - Domain Width(Y) Grid Cells
+25 ! NZ - domain height(Z) grid cells
+2 ! DX [m]
+2 ! DY [m]
+3 !Vertical stretching flag (0 = uniform, 1 = custom, 2 = parabolic Z, 3 = parabolic DZ, 4 = exponential)
+1.000000 ! Surface DZ [m]
+5 ! Number of uniform surface cells
+! DZ array [m]
+1.000000
+1.000000
+1.000000
+1.000000
+1.000000
+1.078399
+1.313596
+1.705591
+2.254385
+2.959976
+3.822366
+4.841553
+6.017539
+7.350323
+8.839905
+10.486285
+12.289463
+14.249439
+16.366213
+18.639786
+21.070156
+23.657325
+26.401292
+29.302056
+32.354352
+1 ! Number of time increments
+0 ! UTC conversion [hours]
+! Begining of time step in Unix epoch time [integer seconds since 1970/1/1 00:00:00]
+1653321600
+2 ! Rooftop flag (0 = none, 1 = log profile, 2 = vortex)
+3 ! Upwind cavity flag (0 = none, 1 = Rockle, 2 = MVP, 3 = HMVP)
+4 ! Street canyon flag (0 = none, 1 = Roeckle, 2 = CPB, 3 = exp. param. PKK, 4 = Roeckle w/ Fackrel)
+1 ! Street intersection flag (0 = off, 1 = on)
+4 ! Wake flag (0 = none, 1 = Rockle, 2 = modified Rockle, 3 = area scaled)
+1 ! Sidewall flag (0 = off, 1 = on)
+1 ! Individual tree wake flag
+2 ! Canopy flag (0 = off, 1 = Cionco w/o wakes, 2 = Cionco w/ wakes)
+1 ! Season flag (1 = Summer, 2 = Winter, 3 = Transition)
+100 ! Maximum number of iterations
+3 ! Residual reduction (Orders of Magnitude)
+0 ! Use diffusion algorithm (0 = off, 1 = on)
+20 ! Number of diffusion iterations
+0 ! Domain rotation relative to true north [degrees] (cw = +)
+0.0 ! UTMX of domain origin [m]
+0.0 ! UTMY of domain origin [m]
+1 ! UTM zone
+17 ! UTM zone leter (1 = A, 2 = B, etc.)
+0 ! QUIC-CFD Flag
+0 ! Explosive building damage flag (1 = on)
+0 ! Building Array Flag (1 = on)
diff --git a/tests/data/samples/SlopeMesa/Runtime_Advanced_User_Inputs.inp b/tests/data/samples/SlopeMesa/Runtime_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..3d78e22
--- /dev/null
+++ b/tests/data/samples/SlopeMesa/Runtime_Advanced_User_Inputs.inp
@@ -0,0 +1,2 @@
+8  ! max number of cpu threads used
+0  ! Use Adaptive Computation Window (0=Disabled 1=Enabled)
\ No newline at end of file
diff --git a/tests/data/samples/SlopeMesa/sensor1.inp b/tests/data/samples/SlopeMesa/sensor1.inp
new file mode 100755
index 0000000..1408cdf
--- /dev/null
+++ b/tests/data/samples/SlopeMesa/sensor1.inp
@@ -0,0 +1,13 @@
+sensor1 !Site Name 
+0 !Upper level flag (1 = use this profile for upper level winds)
+50 !Upper level height (meters)
+1 !Site Coordinate Flag (1=QUIC, 2=UTM, 3=Lat/Lon)
+1 !X coordinate (meters)
+1 !Y coordinate (meters)
+1653321600 !Begining of time step in Unix Epoch time (integer seconds since 1970/1/1 00:00:00)
+1 !site boundary layer flag (1 = log, 2 = exp, 3 = urban canopy, 4 = discrete data points)
+0.1 !site zo
+0. ! 1/L
+!Height (m),Speed	(m/s), Direction (deg relative to true N)
+10.	10.	180
+
diff --git a/tests/data/samples/TransientWinds/QFire_Advanced_User_Inputs.inp b/tests/data/samples/TransientWinds/QFire_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..9485541
--- /dev/null
+++ b/tests/data/samples/TransientWinds/QFire_Advanced_User_Inputs.inp
@@ -0,0 +1,14 @@
+0.05			! N/A, fraction of the cells on fire that will launch a firebrand
+40.			! N/A, scaling factor of the radius represented by the firebrands launched
+1				! s, time step for the firebrands trajectory calculation
+10				! s, how often to launch firebrands
+500			! N/A, number of firebrands distributed over the landing area
+20.			! N/A, FB_FRACTION_LAUNCHED_to_RT_ratio
+50.			! N/A, min_b_value_coef
+0.75			! N/A, fb_frac_of_max_size
+180			! s, germination_delay
+5.				! N/A, fraction of the cell on fire (to scale w)
+50				! N/A, minimum number of ignitions via firebrands at a point
+100			! N/A, maximum number of ignitions via firebrands at a point
+0.523598		! rad, min_theta_value (pi/6)
+0.03        ! m, fb thickness
\ No newline at end of file
diff --git a/tests/data/samples/TransientWinds/QFire_Bldg_Advanced_User_Inputs.inp b/tests/data/samples/TransientWinds/QFire_Bldg_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..83c79bb
--- /dev/null
+++ b/tests/data/samples/TransientWinds/QFire_Bldg_Advanced_User_Inputs.inp
@@ -0,0 +1,9 @@
+1			! N/A, flag to convert QUIC-URB buildings to fuel (0 = no, 1 = yes)
+0.5		! kg/m3, thin fuel density within buildings (if no fuel is specified)
+2.			! N/A, attenuation coefficient within buildings
+0.1	   ! m, surface roughness within buildings
+1			! N/A, flag to convert fuel to canopy for winds (0 = no, 1 = yes)
+1			! N/A, update canopy winds when fuel is consumed (0 = no, 1 = yes)
+1			! N/A, attenuation coefficient within fuel for the Cionco profile (default = 1)
+0.1	   ! m, surface roughness within fuel (default = 0.1 m)
+
diff --git a/tests/data/samples/TransientWinds/QFire_Plume_Advanced_User_Inputs.inp b/tests/data/samples/TransientWinds/QFire_Plume_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..f12f227
--- /dev/null
+++ b/tests/data/samples/TransientWinds/QFire_Plume_Advanced_User_Inputs.inp
@@ -0,0 +1,16 @@
+85000				! N/A, max number of plume at each time step
+0.1				! m/s, minimum vertical velocity of a plume. If wc is below minimum, the plume is eliminated (default 0.1 m/s)
+100				! m/s, maximum vertical velocity of a plume (default 100 m/s)
+0.1				! N/A, minimum speed ratio (plume vertical velocity/wind speed). If below, the plume is eliminated (default 0.1)
+0.					! 1/s2, brunt vaisala frequency squared
+1					! N/A creeping flag: 0 = off, 1 = on
+0					! Flag time step (0 = constant, 1 = adaptive)
+1              ! s, flag = 0: plumes time step plume, flag = 1: minimum plumes time step
+1              ! sor option (0 = reset lambda at each call, 1 = keep)
+10             ! alpha 2 - plume centerline
+2              ! alpha 2 - plume edges
+30.            ! deg, max angle between plumes to merging
+0.7            ! N/A, fraction of overlap of plumes for merging 
+1              ! N/A, which wplume-to-grid scheme: 0 = new, 1 = old
+10             ! N/A, number of points per edge for new scheme (only new scheme)
+1              ! N/A, w computation: 1 = max(w), 0 = sum(w^3)
diff --git a/tests/data/samples/TransientWinds/QP_buildout.inp b/tests/data/samples/TransientWinds/QP_buildout.inp
new file mode 100755
index 0000000..fd084f4
--- /dev/null
+++ b/tests/data/samples/TransientWinds/QP_buildout.inp
@@ -0,0 +1,2 @@
+           0  ! total number of buildings
+           0  ! total number of vegitative canopies
diff --git a/tests/data/samples/TransientWinds/QUIC_fire.inp b/tests/data/samples/TransientWinds/QUIC_fire.inp
new file mode 100644
index 0000000..b0930fe
--- /dev/null
+++ b/tests/data/samples/TransientWinds/QUIC_fire.inp
@@ -0,0 +1,53 @@
+1					! Fire flag: 1 = run fire; 0 = no fire
+222				! Random number generator: -1: use time and date, any other integer > 0 is used as the seed
+! FIRE TIMES
+1653321600		! When the fire is ignited in Unix Epoch time (integer seconds since 1970/1/1 00:00:00). Must be greater or equal to the time of the first wind
+300				! Total simulation time for the fire [s]
+1					! time step for the fire simulation [s]
+1					! Number of fire time steps done before updating the quic wind field (integer, >= 1)
+50					! After how many fire time steps to print out fire-related files (excluding emissions and radiation)
+50					! After how many quic updates to print out wind-related files
+50					! After how many fire time steps to average emissions and radiation
+50					! After how many quic updates to print out averaged wind-related files
+! FIRE GRID
+1					! Number of vertical layers of fire grid cells (integer)
+0					! Vertical stretching flag: 0 = uniform dz, 1 = custom
+1.             ! m, dz
+! FILE PATH
+""
+1              ! Fuel types are in separate files
+1              ! File is stream (1) or headers (2)
+! FUEL
+1					! fuel density flag: 1 = uniform; 2 = provided thru QF_FuelDensity.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.7            ! kg/m3, fuel density
+1					! fuel moisture flag: 1 = uniform; 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation)
+0.05           ! gwater/gfuel, fuel moisture
+1              ! fuel height flag: 1 = uniform
+1.             ! m, fuel height
+0              ! size scale flag: 0 = default, 1 = uniform, 2 = provided thru QF_FuelMoisture.inp, 3 = Firetech files for quic grid, 4 = Firetech files for different grid (need interpolation), 5 = Fastfuels
+0              ! patch & gap flag: 0 = default (0,0), 1 = uniform(supply patch and gap value in meters [following two lines]), 2 = provided thru patch.dat & gap.dat (no interpolation)
+! IGNITION LOCATIONS
+1					! 1 = rectangle, 2 = square ring, 3 = circular ring, 4 = file (QF_Ignitions.inp), 5 = time-dependent ignitions (QF_IgnitionPattern.inp), 6 = ignite.dat (firetech)
+150            ! m, SW corner, x-dir
+100            ! m, SW corner, y-dir
+10             ! m, length in x-dir
+100            ! m, length in y-dir
+100            ! number of ignitions
+! FIREBRANDS
+0				   ! 0 = off, 1 = on
+! OUTPUT FILES (formats depend on the grid type flag)
+0					! Output gridded energy-to-atmosphere (3D fire grid + extra layers)
+0					! Output compressed array reaction rate (fire grid)
+1					! Output compressed array fuel density (fire grid)
+0					! Output gridded wind (u,v,w,sigma) (3D fire grid)
+0					! Output gridded QU winds with fire effects, instantaneous (QUIC-URB grid)
+0					! Output gridded QU winds with fire effects, averaged (QUIC-URB grid)
+0					! Output plume trajectories (ONLY FOR DEBUG)
+0					! Output compressed array fuel moisture (fire grid)
+1					! Output vertically-integrated % mass burnt (fire grid)
+0					! Output trajectories firebrands
+0					! Output compressed array emissions (fire grid)
+0					! Output gridded thermal radiation (fire grid)
+0              ! Output surface fire intensity at every fire time step
+! AUTOKILL
+0              ! Kill if the fire is out and there are no more ignitions or firebrands (0 = no, 1 = yes)
\ No newline at end of file
diff --git a/tests/data/samples/TransientWinds/QU_TopoInputs.inp b/tests/data/samples/TransientWinds/QU_TopoInputs.inp
new file mode 100644
index 0000000..0e8af68
--- /dev/null
+++ b/tests/data/samples/TransientWinds/QU_TopoInputs.inp
@@ -0,0 +1,11 @@
+Specify file name for custom topo (full path)
+""
+0     ! N/A, topo flag: 0 = flat, 1 = Gaussian hill, 2 = hill pass, 3 = slope mesa, 4 = canyon, 5 = custom, 6 = half circle, 7 = sinusoid, 8 = cos hill, 9 = QP_elevation.inp, 10 = terrainOutput.txt (ARA), 11 = terrain.dat (firetec)
+0     ! N/A, smoothing method (0 = none (default for idealized terrain), 1 = blur, 2 = David's)
+0     ! N/A, number of smoothing passes
+100   ! N/A, number of initial SOR iterations (only if fire is run)
+4     ! N/A, sor cycles
+1.3   ! N/A, sor relaxation parameter (default for flat is 1.78, ignored if there is no terrain)
+0     ! N/A, add slope flow correction (0 = no, 1 = yes)
+Command to run slope flow code (system specific)
+""
diff --git a/tests/data/samples/TransientWinds/QU_buildings.inp b/tests/data/samples/TransientWinds/QU_buildings.inp
new file mode 100755
index 0000000..db22cec
--- /dev/null
+++ b/tests/data/samples/TransientWinds/QU_buildings.inp
@@ -0,0 +1,4 @@
+!QUIC 6.26
+0.1			!Wall roughness length (m)
+0			!Number of Buildings
+0			!Number of Polygon Building Nodes
diff --git a/tests/data/samples/TransientWinds/QU_fileoptions.inp b/tests/data/samples/TransientWinds/QU_fileoptions.inp
new file mode 100755
index 0000000..2e6bd3b
--- /dev/null
+++ b/tests/data/samples/TransientWinds/QU_fileoptions.inp
@@ -0,0 +1,6 @@
+
+2   !output data file format flag (1=ascii, 2=binary, 3=both)
+0   !flag to write out non-mass conserved initial field (uofield.dat) (1=write,0=no write)
+0   !flag to write out the file uosensorfield.dat, the initial sensor velocity field (1=write,0=no write)
+0   !flag to write out the file QU_staggered_velocity.bin used by QUIC-Pressure(1=write,0=no write)
+0   !flag to generate startup files (0 = no, 1 = yes)
\ No newline at end of file
diff --git a/tests/data/samples/TransientWinds/QU_landuse.inp b/tests/data/samples/TransientWinds/QU_landuse.inp
new file mode 100755
index 0000000..edc8fe3
Binary files /dev/null and b/tests/data/samples/TransientWinds/QU_landuse.inp differ
diff --git a/tests/data/samples/TransientWinds/QU_metparams.inp b/tests/data/samples/TransientWinds/QU_metparams.inp
new file mode 100755
index 0000000..a41fb25
--- /dev/null
+++ b/tests/data/samples/TransientWinds/QU_metparams.inp
@@ -0,0 +1,7 @@
+!QUIC 6.26
+0 !Met input flag (0=QUIC,1=WRF,2=ITT MM5,3=HOTMAC)
+1 !Number of measuring sites
+1 !Maximum size of data points profiles
+sensor1 !Site Name 
+!File name
+sensor1.inp
diff --git a/tests/data/samples/TransientWinds/QU_movingcoords.inp b/tests/data/samples/TransientWinds/QU_movingcoords.inp
new file mode 100755
index 0000000..d349e21
--- /dev/null
+++ b/tests/data/samples/TransientWinds/QU_movingcoords.inp
@@ -0,0 +1,17 @@
+!QUIC 6.3
+0   !Moving coordinates flag (0=no, 1=yes)
+0   !Reference bearing of the ship relative to the non-rotated domain (degrees)
+!Time (Unix Time), Ship Speed (m/s), Ship Bearing (deg), Ocean Current Speed (m/s), Ocean Current Direction (deg)
+1488794400	0	0	0	0
+1488794450	0	0	0	0
+1488794500	0	0	0	0
+1488794550	0	0	0	0
+1488794600	0	0	0	0
+1488794650	0	0	0	0
+1488794700	0	0	0	0
+1488794750	0	0	0	0
+1488794800	0	0	0	0
+1488794850	0	0	0	0
+1488794900	0	0	0	0
+1488794950	0	0	0	0
+1488795000	0	0	0	0
diff --git a/tests/data/samples/TransientWinds/QU_simparams.inp b/tests/data/samples/TransientWinds/QU_simparams.inp
new file mode 100755
index 0000000..d8f225e
--- /dev/null
+++ b/tests/data/samples/TransientWinds/QU_simparams.inp
@@ -0,0 +1,61 @@
+!QUIC 6.26
+200 !nx - Domain Length(X) Grid Cells
+200 !ny - Domain Width(Y) Grid Cells
+25 ! NZ - domain height(Z) grid cells
+2 ! DX [m]
+2 ! DY [m]
+3 !Vertical stretching flag (0 = uniform, 1 = custom, 2 = parabolic Z, 3 = parabolic DZ, 4 = exponential)
+1.000000 ! Surface DZ [m]
+5 ! Number of uniform surface cells
+! DZ array [m]
+1.000000
+1.000000
+1.000000
+1.000000
+1.000000
+1.078399
+1.313596
+1.705591
+2.254385
+2.959976
+3.822366
+4.841553
+6.017539
+7.350323
+8.839905
+10.486285
+12.289463
+14.249439
+16.366213
+18.639786
+21.070156
+23.657325
+26.401292
+29.302056
+32.354352
+2 ! Number of time increments
+0 ! UTC conversion [hours]
+! Begining of time step in Unix epoch time [integer seconds since 1970/1/1 00:00:00]
+1653321600
+1653321700
+2 ! Rooftop flag (0 = none, 1 = log profile, 2 = vortex)
+3 ! Upwind cavity flag (0 = none, 1 = Rockle, 2 = MVP, 3 = HMVP)
+4 ! Street canyon flag (0 = none, 1 = Roeckle, 2 = CPB, 3 = exp. param. PKK, 4 = Roeckle w/ Fackrel)
+1 ! Street intersection flag (0 = off, 1 = on)
+4 ! Wake flag (0 = none, 1 = Rockle, 2 = modified Rockle, 3 = area scaled)
+1 ! Sidewall flag (0 = off, 1 = on)
+1 ! Individual tree wake flag
+2 ! Canopy flag (0 = off, 1 = Cionco w/o wakes, 2 = Cionco w/ wakes)
+1 ! Season flag (1 = Summer, 2 = Winter, 3 = Transition)
+10 ! Maximum number of iterations
+3 ! Residual reduction (Orders of Magnitude)
+0 ! Use diffusion algorithm (0 = off, 1 = on)
+20 ! Number of diffusion iterations
+0 ! Domain rotation relative to true north [degrees] (cw = +)
+0.0 ! UTMX of domain origin [m]
+0.0 ! UTMY of domain origin [m]
+1 ! UTM zone
+17 ! UTM zone leter (1 = A, 2 = B, etc.)
+0 ! QUIC-CFD Flag
+0 ! Explosive building damage flag (1 = on)
+0 ! Building Array Flag (1 = on)
diff --git a/tests/data/samples/TransientWinds/Runtime_Advanced_User_Inputs.inp b/tests/data/samples/TransientWinds/Runtime_Advanced_User_Inputs.inp
new file mode 100755
index 0000000..3d78e22
--- /dev/null
+++ b/tests/data/samples/TransientWinds/Runtime_Advanced_User_Inputs.inp
@@ -0,0 +1,2 @@
+8  ! max number of cpu threads used
+0  ! Use Adaptive Computation Window (0=Disabled 1=Enabled)
\ No newline at end of file
diff --git a/tests/data/samples/TransientWinds/sensor1.inp b/tests/data/samples/TransientWinds/sensor1.inp
new file mode 100755
index 0000000..398c091
--- /dev/null
+++ b/tests/data/samples/TransientWinds/sensor1.inp
@@ -0,0 +1,19 @@
+sensor1 !Site Name 
+0 !Upper level flag (1 = use this profile for upper level winds)
+50 !Upper level height (meters)
+1 !Site Coordinate Flag (1=QUIC, 2=UTM, 3=Lat/Lon)
+1 !X coordinate (meters)
+1 !Y coordinate (meters)
+1653321600 !Begining of time step in Unix Epoch time (integer seconds since 1970/1/1 00:00:00)
+1 !site boundary layer flag (1 = log, 2 = exp, 3 = urban canopy, 4 = discrete data points)
+0.1 !site zo
+0. ! 1/L (default = 0)
+!Height (m),Speed	(m/s), Direction (deg relative to true N)
+10. 6.	270
+1653321700 !Begining of time step in Unix Epoch time (integer seconds since 1970/1/1 00:00:00)
+1 !site boundary layer flag (1 = log, 2 = exp, 3 = urban canopy, 4 = discrete data points)
+0.1 !site zo
+0. ! 1/L (default = 0)
+!Height (m),Speed	(m/s), Direction (deg relative to true N)
+10. 6.	180
+
diff --git a/tests/test_input_module.py b/tests/test_inputs.py
similarity index 88%
rename from tests/test_input_module.py
rename to tests/test_inputs.py
index c916902..dfcfcef 100644
--- a/tests/test_input_module.py
+++ b/tests/test_inputs.py
@@ -1,8 +1,10 @@
 """
 Test module for the data module of the quicfire_tools package.
 """
+
 from __future__ import annotations
 
+import shutil
 import pytest
 from pydantic import ValidationError
 from pandera.errors import SchemaError
@@ -30,17 +32,29 @@
     WindSensor,
 )
 from quicfire_tools.ignitions import (
-    IgnitionType,
-    IgnitionSources,
+    Ignition,
+    IgnitionFlags,
     RectangleIgnition,
+    SquareRingIgnition,
+)
+from quicfire_tools.topography import (
+    Topography,
+    TopoFlags,
+    GaussianHillTopo,
+    CanyonTopo,
+    CosHillTopo,
+    HillPassTopo,
+    SinusoidTopo,
+    SlopeMesaTopo,
 )
-from quicfire_tools.topography import TopoType, TopoSources, GaussianHillTopo
 
 TEST_DIR = Path(__file__).parent
+TEST_DATA_DIR = TEST_DIR / "data" / "test-inputs"
+SAMPLES_DIR = TEST_DIR / "data" / "samples"
 TMP_DIR = TEST_DIR / "tmp"
+if TMP_DIR.exists():
+    shutil.rmtree(TMP_DIR)
 TMP_DIR.mkdir(exist_ok=True)
-TEST_DATA_DIR = TEST_DIR / "data" / "test-inputs"
-TEST_DATA_DIR.mkdir(exist_ok=True)
 
 
 class TestInputFile:
@@ -451,7 +465,7 @@ def test_init(self):
 
         # Test data type casting
         qu_simparams = QU_Simparams(
-            nx="100", ny=100, nz=26, dx=2, dy=2, quic_domain_height=5, wind_times=["0."]
+            nx="100", ny=100, nz=26, dx=2, dy=2, quic_domain_height=5, wind_times=[0.0]
         )
         assert qu_simparams.nx == 100
         assert qu_simparams.wind_times == [0]
@@ -742,7 +756,9 @@ def test_from_file(self):
         qu_simparams.to_file(TMP_DIR)
         test_object = QU_Simparams.from_file(TMP_DIR)
         assert isinstance(test_object, QU_Simparams)
-        assert qu_simparams == test_object
+        test_object_dict = test_object.to_dict()
+        qu_simparams_dict = qu_simparams.to_dict()
+        assert test_object_dict == qu_simparams_dict
 
         # # Test stretch grid flag = 0
         # qu_simparams = self.get_test_object()
@@ -964,9 +980,7 @@ def get_basic_test_object():
             nz=26,
             sim_time=60,
             time_now=1695311421,
-            ignition_type=RectangleIgnition(
-                x_min=20, y_min=20, x_length=10, y_length=160
-            ),
+            ignition=RectangleIgnition(x_min=20, y_min=20, x_length=10, y_length=160),
         )
 
     def test_init(self):
@@ -1069,17 +1083,12 @@ def test_to_dict(self):
         assert result_dict["patch_size"] == quic_fire.patch_size
         assert result_dict["gap_size"] == quic_fire.gap_size
         assert (
-            result_dict["ignition_type"]["ignition_flag"]
-            == quic_fire.ignition_type.ignition_flag
-        )
-        assert result_dict["ignition_type"]["x_min"] == quic_fire.ignition_type.x_min
-        assert result_dict["ignition_type"]["y_min"] == quic_fire.ignition_type.y_min
-        assert (
-            result_dict["ignition_type"]["x_length"] == quic_fire.ignition_type.x_length
-        )
-        assert (
-            result_dict["ignition_type"]["y_length"] == quic_fire.ignition_type.y_length
+            result_dict["ignition"]["ignition_flag"] == quic_fire.ignition.ignition_flag
         )
+        assert result_dict["ignition"]["x_min"] == quic_fire.ignition.x_min
+        assert result_dict["ignition"]["y_min"] == quic_fire.ignition.y_min
+        assert result_dict["ignition"]["x_length"] == quic_fire.ignition.x_length
+        assert result_dict["ignition"]["y_length"] == quic_fire.ignition.y_length
         assert result_dict["ignitions_per_cell"] == quic_fire.ignitions_per_cell
         assert result_dict["firebrand_flag"] == quic_fire.firebrand_flag
         assert result_dict["auto_kill"] == quic_fire.auto_kill
@@ -1158,7 +1167,7 @@ def test_to_file(self):
             for i in range(current_line, current_line + 4):
                 ignition_params.append(float(lines[i].strip().split("!")[0]))
             x_min, y_min, x_length, y_length = ignition_params
-            assert quic_fire.ignition_type == RectangleIgnition(
+            assert quic_fire.ignition == RectangleIgnition(
                 ignition_flag=ignition_flag,
                 x_min=x_min,
                 y_min=y_min,
@@ -1581,11 +1590,11 @@ def test_from_file(self):
 
 class TestQUTopoInputs:
     def get_default_test_object(self):
-        return QU_TopoInputs(topo_type=TopoType(topo_flag=0))
+        return QU_TopoInputs(topography=Topography(topo_flag=0))
 
     def get_complex_test_object(self):
         return QU_TopoInputs(
-            topo_type=GaussianHillTopo(
+            topography=GaussianHillTopo(
                 x_hilltop=100, y_hilltop=150, elevation_max=500, elevation_std=20
             ),
             smoothing_method=1,
@@ -1595,12 +1604,12 @@ def get_complex_test_object(self):
     def test_default_inputs(self):
         topoinputs = self.get_default_test_object()
         assert topoinputs.filename == "topo.dat"
-        assert isinstance(topoinputs.topo_type, TopoType)
+        assert isinstance(topoinputs.topography, Topography)
         assert topoinputs.smoothing_passes == 500
 
     def test_complex_inputs(self):
         topoinputs = self.get_complex_test_object()
-        assert topoinputs.topo_type.topo_flag.value == 1
+        assert topoinputs.topography.topo_flag.value == 1
         assert topoinputs._topo_lines == (
             "1\t\t! N/A, "
             "topo flag: 0 = flat, 1 = Gaussian hill, "
@@ -1628,15 +1637,15 @@ def test_to_dict(self):
 
         topoinputs = self.get_complex_test_object()
         test_dict = topoinputs.to_dict()
-        assert test_dict["topo_type"]["x_hilltop"] == topoinputs.topo_type.x_hilltop
-        assert test_dict["topo_type"]["y_hilltop"] == topoinputs.topo_type.y_hilltop
+        assert test_dict["topography"]["x_hilltop"] == topoinputs.topography.x_hilltop
+        assert test_dict["topography"]["y_hilltop"] == topoinputs.topography.y_hilltop
         assert (
-            test_dict["topo_type"]["elevation_max"]
-            == topoinputs.topo_type.elevation_max
+            test_dict["topography"]["elevation_max"]
+            == topoinputs.topography.elevation_max
         )
         assert (
-            test_dict["topo_type"]["elevation_std"]
-            == topoinputs.topo_type.elevation_std
+            test_dict["topography"]["elevation_std"]
+            == topoinputs.topography.elevation_std
         )
         assert test_dict["smoothing_method"] == topoinputs.smoothing_method
         assert test_dict["smoothing_passes"] == topoinputs.smoothing_passes
@@ -1656,7 +1665,7 @@ def test_to_file(self):
         with open(TMP_DIR / "QU_TopoInputs.inp", "r") as file:
             lines = file.readlines()
         topo_flag = int(lines[2].strip().split("!")[0])
-        assert topo_flag == topoinputs.topo_type.topo_flag.value
+        assert topo_flag == topoinputs.topography.topo_flag.value
         add_dict = {
             0: 0,
             1: 4,
@@ -1697,7 +1706,7 @@ def test_to_file(self):
         with open(TMP_DIR / "QU_TopoInputs.inp", "r") as file:
             lines = file.readlines()
         topo_flag = int(lines[2].strip().split("!")[0])
-        assert topo_flag == topoinputs.topo_type.topo_flag.value
+        assert topo_flag == topoinputs.topography.topo_flag.value
         add_dict = {
             0: 0,
             1: 4,
@@ -1713,13 +1722,13 @@ def test_to_file(self):
             11: 0,
         }
         add = add_dict.get(topo_flag)
-        assert float(lines[3].strip().split("!")[0]) == topoinputs.topo_type.x_hilltop
-        assert float(lines[4].strip().split("!")[0]) == topoinputs.topo_type.y_hilltop
+        assert float(lines[3].strip().split("!")[0]) == topoinputs.topography.x_hilltop
+        assert float(lines[4].strip().split("!")[0]) == topoinputs.topography.y_hilltop
         assert (
-            float(lines[5].strip().split("!")[0]) == topoinputs.topo_type.elevation_max
+            float(lines[5].strip().split("!")[0]) == topoinputs.topography.elevation_max
         )
         assert (
-            float(lines[6].strip().split("!")[0]) == topoinputs.topo_type.elevation_std
+            float(lines[6].strip().split("!")[0]) == topoinputs.topography.elevation_std
         )
         current_line = current_line = 3 + add
         assert (
@@ -1780,12 +1789,6 @@ def test_init(self):
         qu_moving = self.get_test_object()
         assert isinstance(qu_moving, QU_movingcoords)
 
-    def test_from_file(self):
-        qu_moving = self.get_test_object()
-        qu_moving.to_file(TMP_DIR)
-        test_object = QU_movingcoords.from_file(TMP_DIR)
-        assert qu_moving == test_object
-
 
 class TestQUbuildout:
     def get_test_object(self):
@@ -1795,12 +1798,6 @@ def test_init(self):
         qp_buildout = self.get_test_object()
         assert isinstance(qp_buildout, QP_buildout)
 
-    def test_from_file(self):
-        qp_buildout = self.get_test_object()
-        qp_buildout.to_file(TMP_DIR)
-        test_object = QP_buildout.from_file(TMP_DIR)
-        assert qp_buildout == test_object
-
 
 class Test_QU_metparams:
     def get_test_object(self):
@@ -2178,13 +2175,6 @@ def get_test_object():
             simulation_time=65,
         )
 
-    @staticmethod
-    def compare_simulation_inputs(a: SimulationInputs, b: SimulationInputs):
-        for a_input_file, b_input_file in zip(
-            a._input_files_dict.values(), b._input_files_dict.values()
-        ):
-            assert a_input_file == b_input_file
-
     def test_basic_inputs(self):
         sim_inputs = self.get_test_object()
         assert isinstance(sim_inputs, SimulationInputs)
@@ -2263,7 +2253,7 @@ def test_set_rectangle_ignition(self):
             x_length=10,
             y_length=110,
         )
-        assert sim_inputs.quic_fire.ignition_type == RectangleIgnition(
+        assert sim_inputs.quic_fire.ignition == RectangleIgnition(
             x_min=20,
             y_min=20,
             x_length=10,
@@ -2285,6 +2275,22 @@ def test_set_output_files(self):
         assert sim_inputs.quic_fire.surf_eng_out == 0
         assert sim_inputs.quic_fire.emissions_out == 2
 
+    def test_set_output_times(self):
+        sim_inputs = self.get_test_object()
+        sim_inputs.set_output_interval(60)
+        assert sim_inputs.quic_fire.out_time_fire == 60
+        assert sim_inputs.quic_fire.out_time_wind == 60
+        assert sim_inputs.quic_fire.out_time_wind_avg == 60
+        assert sim_inputs.quic_fire.out_time_emis_rad == 60
+
+        # Test with invalid intervals
+        with pytest.raises(ValueError):
+            sim_inputs.set_output_interval(-1)
+        with pytest.raises(ValueError):
+            sim_inputs.set_output_interval(0)
+        with pytest.raises(ValueError):
+            sim_inputs.set_output_interval("a")
+
     def test_set_custom_simulation(self):
         # Test default set custom simulation (no arguments)
         sim_inputs = self.get_test_object()
@@ -2294,10 +2300,8 @@ def test_set_custom_simulation(self):
         assert sim_inputs.quic_fire.fuel_height_flag == 3
         assert sim_inputs.quic_fire.size_scale_flag == 0  # Default
         assert sim_inputs.quic_fire.patch_and_gap_flag == 0  # Default
-        assert sim_inputs.quic_fire.ignition_type == IgnitionType(
-            ignition_flag=IgnitionSources(7)
-        )
-        assert sim_inputs.qu_topoinputs.topo_type == TopoType(topo_flag=TopoSources(5))
+        assert sim_inputs.quic_fire.ignition == Ignition(ignition_flag=IgnitionFlags(7))
+        assert sim_inputs.qu_topoinputs.topography == Topography(topo_flag=TopoFlags(5))
 
         # Test including size scale and patch/gap from set custom simulation
         sim_inputs = self.get_test_object()
@@ -2308,7 +2312,7 @@ def test_set_custom_simulation(self):
         # Test excluding topo flag from set custom simulation
         sim_inputs = self.get_test_object()
         sim_inputs.set_custom_simulation(topo=False)
-        assert sim_inputs.qu_topoinputs.topo_type == TopoType(topo_flag=TopoSources(0))
+        assert sim_inputs.qu_topoinputs.topography == Topography(topo_flag=TopoFlags(0))
 
     def test_write_inputs_v5(self):
         sim_inputs = self.get_test_object()
@@ -2500,7 +2504,30 @@ def test_from_directory(self):
         assert isinstance(test_object, SimulationInputs)
 
         # Check that the inputs are the same
-        self.compare_simulation_inputs(sim_inputs, test_object)
+        compare_simulation_inputs(sim_inputs, test_object)
+
+    def test_from_directory_optional_files(self):
+        sim_inputs = self.get_test_object()
+        sim_inputs.write_inputs(TMP_DIR)
+
+        # Remove optional files
+        gridlist_path = TMP_DIR / "gridlist"
+        gridlist_path.unlink()
+        raster_origin_path = TMP_DIR / "rasterorigin.txt"
+        raster_origin_path.unlink()
+
+        test_object = SimulationInputs.from_directory(TMP_DIR)
+        assert isinstance(test_object, SimulationInputs)
+        compare_simulation_inputs(sim_inputs, test_object)
+
+    def test_from_directory_missing_files(self):
+        sim_inputs = self.get_test_object()
+        sim_inputs.write_inputs(TMP_DIR)
+        quic_fire_path = TMP_DIR / "QUIC_fire.inp"
+        quic_fire_path.unlink()
+
+        with pytest.raises(FileNotFoundError):
+            SimulationInputs.from_directory(TMP_DIR)
 
     def test_to_dict(self):
         sim_inputs = self.get_test_object()
@@ -2513,7 +2540,7 @@ def test_from_dict(self):
         assert isinstance(test_obj, SimulationInputs)
 
         # Check that the inputs are the same
-        self.compare_simulation_inputs(sim_inputs, test_obj)
+        compare_simulation_inputs(sim_inputs, test_obj)
 
     def test_to_json(self):
         sim_inputs = self.get_test_object()
@@ -2526,4 +2553,195 @@ def test_from_json(self):
         assert isinstance(test_obj, SimulationInputs)
 
         # Check that the inputs are the same
-        self.compare_simulation_inputs(sim_inputs, test_obj)
+        compare_simulation_inputs(sim_inputs, test_obj)
+
+
+class TestSamples:
+    def test_canyon(self):
+        canyon_sim = SimulationInputs.from_directory(SAMPLES_DIR / "Canyon")
+
+        # Check topography
+        assert isinstance(canyon_sim.qu_topoinputs.topography, CanyonTopo)
+        assert canyon_sim.qu_topoinputs.topography.topo_flag == 4
+        assert canyon_sim.qu_topoinputs.topography.x_location == 300
+        assert canyon_sim.qu_topoinputs.topography.y_location == 150
+        assert canyon_sim.qu_topoinputs.topography.canyon_std == 100
+        assert canyon_sim.qu_topoinputs.topography.vertical_offset == 20
+
+        # Check ignition
+        assert isinstance(canyon_sim.quic_fire.ignition, RectangleIgnition)
+        assert canyon_sim.quic_fire.ignition.ignition_flag == 1
+        assert canyon_sim.quic_fire.ignition.x_min == 250
+        assert canyon_sim.quic_fire.ignition.y_min == 225
+        assert canyon_sim.quic_fire.ignition.x_length == 10
+        assert canyon_sim.quic_fire.ignition.y_length == 100
+
+        # Check I/O
+        canyon_sim.write_inputs(TMP_DIR)
+        canyon_sim.to_json(TMP_DIR / "sim.json")
+        canyon_sim_from_json = SimulationInputs.from_json(TMP_DIR / "sim.json")
+        compare_simulation_inputs(canyon_sim, canyon_sim_from_json)
+
+    def test_cos_hill(self):
+        cos_hill = SimulationInputs.from_directory(SAMPLES_DIR / "CosHill")
+
+        # Check topography
+        assert isinstance(cos_hill.qu_topoinputs.topography, CosHillTopo)
+        assert cos_hill.qu_topoinputs.topography.topo_flag == 8
+        assert cos_hill.qu_topoinputs.topography.aspect == 100
+        assert cos_hill.qu_topoinputs.topography.height == 10
+
+        # Check I/O
+        cos_hill.write_inputs(TMP_DIR)
+        cos_hill.to_json(TMP_DIR / "sim.json")
+        cos_hill_from_json = SimulationInputs.from_json(TMP_DIR / "sim.json")
+        compare_simulation_inputs(cos_hill, cos_hill_from_json)
+
+    def test_eglin_canopy(self):
+        eglin_canopy = SimulationInputs.from_directory(SAMPLES_DIR / "EglinCanopyTest")
+
+        # Check topography
+        assert isinstance(eglin_canopy.qu_topoinputs.topography, Topography)
+        assert eglin_canopy.qu_topoinputs.topography.topo_flag == 0
+
+        # Check fuel flags
+        assert eglin_canopy.quic_fire.fuel_density_flag == 5
+        assert eglin_canopy.quic_fire.fuel_moisture_flag == 5
+
+        # Check ignition
+        assert isinstance(eglin_canopy.quic_fire.ignition, Ignition)
+        assert eglin_canopy.quic_fire.ignition.ignition_flag == 7
+
+        # Check I/O
+        eglin_canopy.write_inputs(TMP_DIR)
+        eglin_canopy.to_json(TMP_DIR / "sim.json")
+        eglin_canopy_from_json = SimulationInputs.from_json(TMP_DIR / "sim.json")
+        compare_simulation_inputs(eglin_canopy, eglin_canopy_from_json)
+
+    def test_gauss_hill(self):
+        gauss_hill = SimulationInputs.from_directory(SAMPLES_DIR / "GaussHill")
+
+        # Check topography
+        assert isinstance(gauss_hill.qu_topoinputs.topography, GaussianHillTopo)
+        assert gauss_hill.qu_topoinputs.topography.topo_flag == 1
+        assert gauss_hill.qu_topoinputs.topography.x_hilltop == 400
+        assert gauss_hill.qu_topoinputs.topography.y_hilltop == 300
+        assert gauss_hill.qu_topoinputs.topography.elevation_max == 100
+        assert gauss_hill.qu_topoinputs.topography.elevation_std == 150
+
+        # Check I/O
+        gauss_hill.write_inputs(TMP_DIR)
+        gauss_hill.to_json(TMP_DIR / "sim.json")
+        gauss_hill_from_json = SimulationInputs.from_json(TMP_DIR / "sim.json")
+        compare_simulation_inputs(gauss_hill, gauss_hill_from_json)
+
+    def test_hill_pass(self):
+        hill_pass = SimulationInputs.from_directory(SAMPLES_DIR / "HillPass")
+
+        # Check topography
+        assert isinstance(hill_pass.qu_topoinputs.topography, HillPassTopo)
+        assert hill_pass.qu_topoinputs.topography.topo_flag == 2
+        assert hill_pass.qu_topoinputs.topography.max_height == 70
+        assert hill_pass.qu_topoinputs.topography.location_param == 5
+
+        # Check I/O
+        hill_pass.write_inputs(TMP_DIR)
+        hill_pass.to_json(TMP_DIR / "sim.json")
+        hill_pass_from_json = SimulationInputs.from_json(TMP_DIR / "sim.json")
+        compare_simulation_inputs(hill_pass, hill_pass_from_json)
+
+    def test_line_fire(self):
+        line_fire = SimulationInputs.from_directory(SAMPLES_DIR / "LineFire")
+
+        # Check parabolic quic grid
+        assert line_fire.qu_simparams.stretch_grid_flag == 3
+        assert line_fire.qu_simparams._dz_array[0] == 1
+        assert line_fire.qu_simparams._dz_array[4] == 1
+        assert line_fire.qu_simparams._dz_array[5] == 1.078399
+        assert line_fire.qu_simparams._dz_array[6] == 1.313596
+        assert line_fire.qu_simparams._dz_array[23] == 29.302056
+        assert line_fire.qu_simparams._dz_array[24] == 32.354352
+
+        # Check topography
+        assert isinstance(line_fire.qu_topoinputs.topography, Topography)
+        assert line_fire.qu_topoinputs.topography.topo_flag == 0
+
+        # Check ignition
+        assert isinstance(line_fire.quic_fire.ignition, SquareRingIgnition)
+        assert line_fire.quic_fire.ignition.ignition_flag == 2
+        assert line_fire.quic_fire.ignition.x_min == 50
+        assert line_fire.quic_fire.ignition.y_min == 50
+        assert line_fire.quic_fire.ignition.x_length == 200
+        assert line_fire.quic_fire.ignition.y_length == 200
+        assert line_fire.quic_fire.ignition.x_width == 10
+        assert line_fire.quic_fire.ignition.y_width == 10
+
+        # Check I/O
+        line_fire.write_inputs(TMP_DIR)
+        line_fire.to_json(TMP_DIR / "sim.json")
+        line_fire_from_json = SimulationInputs.from_json(TMP_DIR / "sim.json")
+        compare_simulation_inputs(line_fire, line_fire_from_json)
+
+    def test_sinusoid(self):
+        sinusoid = SimulationInputs.from_directory(SAMPLES_DIR / "Sinusoid")
+
+        # Check topography
+        assert isinstance(sinusoid.qu_topoinputs.topography, SinusoidTopo)
+        assert sinusoid.qu_topoinputs.topography.topo_flag == 7
+        assert sinusoid.qu_topoinputs.topography.period == 20
+        assert sinusoid.qu_topoinputs.topography.amplitude == 80
+
+        # Check I/O
+        sinusoid.write_inputs(TMP_DIR)
+        sinusoid.to_json(TMP_DIR / "sim.json")
+        sinusoid_from_json = SimulationInputs.from_json(TMP_DIR / "sim.json")
+        compare_simulation_inputs(sinusoid, sinusoid_from_json)
+
+    def test_slope_mesa(self):
+        slope_mesa = SimulationInputs.from_directory(SAMPLES_DIR / "SlopeMesa")
+
+        # Check topography
+        assert isinstance(slope_mesa.qu_topoinputs.topography, SlopeMesaTopo)
+        assert slope_mesa.qu_topoinputs.topography.topo_flag == 3
+        assert slope_mesa.qu_topoinputs.topography.slope_axis == 0
+        assert slope_mesa.qu_topoinputs.topography.slope_value == 0.2
+        assert slope_mesa.qu_topoinputs.topography.flat_fraction == 0.5
+
+        # Check I/O
+        slope_mesa.write_inputs(TMP_DIR)
+        slope_mesa.to_json(TMP_DIR / "sim.json")
+        slope_mesa_from_json = SimulationInputs.from_json(TMP_DIR / "sim.json")
+        compare_simulation_inputs(slope_mesa, slope_mesa_from_json)
+
+    def test_transient_winds(self):
+        transient_winds = SimulationInputs.from_directory(
+            SAMPLES_DIR / "TransientWinds"
+        )
+
+        # Check wind sensors
+        assert transient_winds.qu_simparams.wind_times == [1653321600, 1653321700]
+        assert transient_winds.qu_metparams.num_sensors == 1
+        assert len(transient_winds.windsensors) == 1
+        assert isinstance(transient_winds.windsensors.sensor1, WindSensor)
+        assert transient_winds.windsensors.sensor1.wind_times == [0, 100]
+        assert transient_winds.windsensors.sensor1.wind_speeds == [6, 6]
+        assert transient_winds.windsensors.sensor1.wind_directions == [270, 180]
+        assert transient_winds.windsensors.sensor1.sensor_height == 10.0
+
+        # Check I/O
+        transient_winds.write_inputs(TMP_DIR)
+        transient_winds.to_json(TMP_DIR / "sim.json")
+        transient_winds_from_json = SimulationInputs.from_json(TMP_DIR / "sim.json")
+        compare_simulation_inputs(transient_winds, transient_winds_from_json)
+
+
+def compare_simulation_inputs(a: SimulationInputs, b: SimulationInputs):
+    for a_input_file, b_input_file in zip(
+        a._input_files_dict.values(), b._input_files_dict.values()
+    ):
+        if isinstance(a_input_file, QU_Simparams) or isinstance(
+            b_input_file, QU_Simparams
+        ):
+            a_input_file._from_file_dz_array = None
+            b_input_file._from_file_dz_array = None
+        assert a_input_file == b_input_file
diff --git a/tests/test_outputs.py b/tests/test_outputs.py
new file mode 100644
index 0000000..56f3d11
--- /dev/null
+++ b/tests/test_outputs.py
@@ -0,0 +1,1037 @@
+# Core imports
+from __future__ import annotations
+from pathlib import Path
+
+# Internal imports
+from quicfire_tools.utils import read_dat_file
+from quicfire_tools.inputs import SimulationInputs
+from quicfire_tools.outputs import (
+    _process_grid_info,
+    _get_resolution_from_coords,
+    _process_fire_indexes,
+    _process_compressed_bin,
+    _process_gridded_bin,
+    SimulationOutputs,
+    OutputFile,
+)
+
+# External imports
+import zarr
+import numpy as np
+import netCDF4 as nc
+
+TEST_DIR = Path(__file__).parent
+TEST_DATA_DIR = TEST_DIR / "data"
+SAMPLE_DATA_DIR = TEST_DATA_DIR / "samples"
+LINE_FIRE_DIR = SAMPLE_DATA_DIR / "LineFire"
+EG_CANOPY_DIR = SAMPLE_DATA_DIR / "EglinCanopyTest"
+COS_HILL_DIR = SAMPLE_DATA_DIR / "CosHill"
+TMP_DIR = TEST_DIR / "tmp"
+TMP_DIR.mkdir(exist_ok=True)
+
+
+class TestProcessGridInfo:
+    def test_read_grid_info_line_fire(self):
+        line_fire = SimulationInputs.from_directory(LINE_FIRE_DIR)
+        quic_nz, quic_grid, en2atmos_nz, en2atmos_grid = _process_grid_info(
+            LINE_FIRE_DIR / "Output" / "grid.bin",
+            line_fire.qu_simparams.ny,
+            line_fire.qu_simparams.nx,
+        )
+        assert quic_nz == line_fire.qu_simparams.nz
+        assert len(quic_grid) == line_fire.qu_simparams.nz + 2
+        quic_dz = _get_resolution_from_coords(quic_grid)
+        assert np.allclose(quic_dz[:-1], line_fire.qu_simparams._dz_array)
+
+        assert en2atmos_nz == 21
+        en2atmos_dz = _get_resolution_from_coords(en2atmos_grid)
+        assert en2atmos_dz == line_fire.quic_fire.dz
+
+    def test_read_grid_info_eglin_canopy(self):
+        eglin_canopy = SimulationInputs.from_directory(EG_CANOPY_DIR)
+        quic_nz, quic_grid, en2atmos_nz, en2atmos_grid = _process_grid_info(
+            EG_CANOPY_DIR / "Output" / "grid.bin",
+            eglin_canopy.qu_simparams.ny,
+            eglin_canopy.qu_simparams.nx,
+        )
+        assert quic_nz == eglin_canopy.qu_simparams.nz
+        assert len(quic_grid) == eglin_canopy.qu_simparams.nz + 2
+        quic_dz = _get_resolution_from_coords(quic_grid)
+        assert np.allclose(quic_dz[:-1], eglin_canopy.qu_simparams._dz_array)
+
+        assert en2atmos_nz == 43
+        en2atmos_dz = _get_resolution_from_coords(en2atmos_grid)
+        assert en2atmos_dz == eglin_canopy.quic_fire.dz
+
+
+class TestProcessedCompressedBin:
+    line_fire_sim = SimulationInputs.from_directory(LINE_FIRE_DIR)
+    line_fire_indexes = _process_fire_indexes(
+        LINE_FIRE_DIR / "Output" / "fire_indexes.bin"
+    )
+
+    eglin_canopy_sim = SimulationInputs.from_directory(EG_CANOPY_DIR)
+    eglin_canopy_indexes = _process_fire_indexes(
+        EG_CANOPY_DIR / "Output" / "fire_indexes.bin"
+    )
+
+    def test_line_fire_fuel_dens(self):
+        fuels_dens_start = _process_compressed_bin(
+            LINE_FIRE_DIR / "Output" / "fuels-dens-00000.bin",
+            (
+                self.line_fire_sim.quic_fire.nz,
+                self.line_fire_sim.qu_simparams.ny,
+                self.line_fire_sim.qu_simparams.nx,
+            ),
+            self.line_fire_indexes,
+        )
+        assert fuels_dens_start.shape == (
+            1,
+            self.line_fire_sim.quic_fire.nz,
+            self.line_fire_sim.qu_simparams.ny,
+            self.line_fire_sim.qu_simparams.nx,
+        )
+
+        # Load reference data for comparison
+        npz = np.load(LINE_FIRE_DIR / "Reference" / "fuels-dens-00000.npz")
+        reference_fuel_dens = npz["var"]
+        reference_fuel_dens = np.swapaxes(reference_fuel_dens, 0, 2)
+
+        assert np.allclose(fuels_dens_start[0], reference_fuel_dens)
+
+        # trim xy edges of the array
+        fuels_dens_start = fuels_dens_start[0, 0, 1:-1, 1:-1]
+        assert np.allclose(fuels_dens_start, self.line_fire_sim.quic_fire.fuel_density)
+
+        # Load the next time step
+        fuels_dens_end = _process_compressed_bin(
+            LINE_FIRE_DIR / "Output" / "fuels-dens-00060.bin",
+            (
+                self.line_fire_sim.quic_fire.nz,
+                self.line_fire_sim.qu_simparams.ny,
+                self.line_fire_sim.qu_simparams.nx,
+            ),
+            self.line_fire_indexes,
+        )
+
+        # Load reference data for comparison
+        npz = np.load(LINE_FIRE_DIR / "Reference" / "fuels-dens-00060.npz")
+        reference_fuel_dens = npz["var"]
+        reference_fuel_dens = np.swapaxes(reference_fuel_dens, 0, 2)
+        reference_fuel_dens = np.swapaxes(reference_fuel_dens, 1, 2)
+
+        assert np.allclose(fuels_dens_end[0, 0, ...], reference_fuel_dens)
+
+    def test_eglin_fuels_dens(self):
+        # Load the first time step model output of fuel density
+        fuels_dens_start = _process_compressed_bin(
+            EG_CANOPY_DIR / "Output" / "fuels-dens-00000.bin",
+            (
+                self.eglin_canopy_sim.quic_fire.nz,
+                self.eglin_canopy_sim.qu_simparams.ny,
+                self.eglin_canopy_sim.qu_simparams.nx,
+            ),
+            self.eglin_canopy_indexes,
+        )
+        assert fuels_dens_start.shape == (
+            1,
+            self.eglin_canopy_sim.quic_fire.nz,
+            self.eglin_canopy_sim.qu_simparams.ny,
+            self.eglin_canopy_sim.qu_simparams.nx,
+        )
+
+        # Load in the input .dat file for comparison
+        fuels_dens_dat = read_dat_file(
+            EG_CANOPY_DIR / "treesrhof.dat",
+            self.eglin_canopy_sim.quic_fire.nz,
+            self.eglin_canopy_sim.qu_simparams.ny,
+            self.eglin_canopy_sim.qu_simparams.nx,
+        )
+
+        # I have no idea why the edges are wonky, but the rest of the array
+        # is fine. Maybe there's some model interpolation going on?
+        assert np.allclose(
+            fuels_dens_start[0, :, 1:-1, 2:-1], fuels_dens_dat[:, 1:-1, 1:-2], atol=1e-3
+        )
+
+        # Load the first time step of the reference data
+        npz = np.load(EG_CANOPY_DIR / "Reference" / "fuels-dens-00000.npz")
+        reference_fuel_dens = npz["var"]
+        reference_fuel_dens = np.swapaxes(reference_fuel_dens, 0, 2)
+        reference_fuel_dens = np.swapaxes(reference_fuel_dens, 1, 2)
+
+        assert np.allclose(fuels_dens_start[0, ...], reference_fuel_dens)
+
+        # Load the next time step
+        fuels_dens_end = _process_compressed_bin(
+            EG_CANOPY_DIR / "Output" / "fuels-dens-00600.bin",
+            (
+                self.eglin_canopy_sim.quic_fire.nz,
+                self.eglin_canopy_sim.qu_simparams.ny,
+                self.eglin_canopy_sim.qu_simparams.nx,
+            ),
+            self.eglin_canopy_indexes,
+        )
+
+        # Load the next time step of the reference data
+        npz = np.load(EG_CANOPY_DIR / "Reference" / "fuels-dens-00600.npz")
+        reference_fuel_dens = npz["var"]
+        reference_fuel_dens = np.swapaxes(reference_fuel_dens, 0, 2)
+        reference_fuel_dens = np.swapaxes(reference_fuel_dens, 1, 2)
+
+        assert np.allclose(fuels_dens_end[0, ...], reference_fuel_dens)
+
+    def test_eglin_co_emissions(self):
+        # Load the model output
+        co_emissions = _process_compressed_bin(
+            EG_CANOPY_DIR / "Output" / "co_emissions-00600.bin",
+            (
+                self.eglin_canopy_sim.quic_fire.nz,
+                self.eglin_canopy_sim.qu_simparams.ny,
+                self.eglin_canopy_sim.qu_simparams.nx,
+            ),
+            self.eglin_canopy_indexes,
+        )
+
+        # Load the reference data
+        npz = np.load(EG_CANOPY_DIR / "Reference" / "co_emissions-00600.npz")
+        reference_co_emissions = npz["var"]
+        reference_co_emissions = np.swapaxes(reference_co_emissions, 0, 2)
+        reference_co_emissions = np.swapaxes(reference_co_emissions, 1, 2)
+
+        assert np.allclose(co_emissions[0, ...], reference_co_emissions)
+
+    def test_eglin_thermal_radiation(self):
+        # Load the model output
+        thermal_radiation = _process_compressed_bin(
+            EG_CANOPY_DIR / "Output" / "thermalradiation-00600.bin",
+            (
+                self.eglin_canopy_sim.quic_fire.nz,
+                self.eglin_canopy_sim.qu_simparams.ny,
+                self.eglin_canopy_sim.qu_simparams.nx,
+            ),
+            self.eglin_canopy_indexes,
+        )
+
+        # Load the reference data
+        npz = np.load(EG_CANOPY_DIR / "Reference" / "thermalradiation-00600.npz")
+        reference_thermal_radiation = npz["var"]
+        reference_thermal_radiation = np.swapaxes(reference_thermal_radiation, 0, 2)
+        reference_thermal_radiation = np.swapaxes(reference_thermal_radiation, 1, 2)
+
+        assert np.allclose(thermal_radiation[0, ...], reference_thermal_radiation)
+
+
+class TestProcessGriddedBin:
+    line_fire_sim = SimulationInputs.from_directory(LINE_FIRE_DIR)
+    line_fire_indexes = _process_fire_indexes(
+        LINE_FIRE_DIR / "Output" / "fire_indexes.bin"
+    )
+
+    eglin_canopy_sim = SimulationInputs.from_directory(EG_CANOPY_DIR)
+    eglin_canopy_indexes = _process_fire_indexes(
+        EG_CANOPY_DIR / "Output" / "fire_indexes.bin"
+    )
+
+    def test_line_fire_eng2atmos(self):
+        _, _, line_fire_eng2atmos_nz, _ = _process_grid_info(
+            LINE_FIRE_DIR / "Output" / "grid.bin",
+            self.line_fire_sim.qu_simparams.ny,
+            self.line_fire_sim.qu_simparams.nx,
+        )
+
+        # Load first time step
+        eng2atmos = _process_gridded_bin(
+            LINE_FIRE_DIR / "Output" / "fire-energy_to_atmos-00000.bin",
+            (
+                line_fire_eng2atmos_nz,
+                self.line_fire_sim.qu_simparams.ny,
+                self.line_fire_sim.qu_simparams.nx,
+            ),
+        )
+
+        # Load reference data for comparison
+        npz = np.load(LINE_FIRE_DIR / "Reference" / "fire-energy_to_atmos-00000.npz")
+        reference_eng2atmos = npz["var"]
+        reference_eng2atmos = np.swapaxes(reference_eng2atmos, 0, 2)
+        reference_eng2atmos = np.swapaxes(reference_eng2atmos, 1, 2)
+
+        assert np.allclose(eng2atmos[0], reference_eng2atmos)
+
+        # Load next time step
+        eng2atmos = _process_gridded_bin(
+            LINE_FIRE_DIR / "Output" / "fire-energy_to_atmos-00060.bin",
+            (
+                line_fire_eng2atmos_nz,
+                self.line_fire_sim.qu_simparams.ny,
+                self.line_fire_sim.qu_simparams.nx,
+            ),
+            self.line_fire_indexes,
+        )
+
+        # Load reference data for comparison
+        npz = np.load(LINE_FIRE_DIR / "Reference" / "fire-energy_to_atmos-00060.npz")
+        reference_eng2atmos = npz["var"]
+        reference_eng2atmos = np.swapaxes(reference_eng2atmos, 0, 2)
+        reference_eng2atmos = np.swapaxes(reference_eng2atmos, 1, 2)
+
+        assert np.allclose(eng2atmos[0], reference_eng2atmos)
+
+    def test_line_fire_surf_energy(self):
+        # Load first time step
+        surf_energy = _process_gridded_bin(
+            LINE_FIRE_DIR / "Output" / "surfEnergy00009.bin",
+            (
+                1,
+                self.line_fire_sim.qu_simparams.ny,
+                self.line_fire_sim.qu_simparams.nx,
+            ),
+        )
+
+        assert surf_energy.shape == (1, 1, 200, 200)
+        assert surf_energy.max() > 0
+
+    def test_line_fire_mburnt_integ(self):
+        # Load first time step
+        mburnt_integ = _process_gridded_bin(
+            LINE_FIRE_DIR / "Output" / "mburnt_integ-00000.bin",
+            (
+                1,
+                self.line_fire_sim.qu_simparams.ny,
+                self.line_fire_sim.qu_simparams.nx,
+            ),
+        )
+
+        # Load reference data for comparison
+        npz = np.load(LINE_FIRE_DIR / "Reference" / "mburnt_integ-00000.npz")
+        reference_mburnt_integ = npz["var"]
+        reference_mburnt_integ = np.swapaxes(reference_mburnt_integ, 0, 2)
+        reference_mburnt_integ = np.swapaxes(reference_mburnt_integ, 1, 2)
+
+        assert np.allclose(mburnt_integ[0], reference_mburnt_integ)
+
+        # Load next time step
+        mburnt_integ = _process_gridded_bin(
+            LINE_FIRE_DIR / "Output" / "mburnt_integ-00060.bin",
+            (
+                1,
+                self.line_fire_sim.qu_simparams.ny,
+                self.line_fire_sim.qu_simparams.nx,
+            ),
+            self.line_fire_indexes,
+        )
+
+        # Load reference data for comparison
+        npz = np.load(LINE_FIRE_DIR / "Reference" / "mburnt_integ-00060.npz")
+        reference_mburnt_integ = npz["var"]
+        reference_mburnt_integ = np.swapaxes(reference_mburnt_integ, 0, 2)
+        reference_mburnt_integ = np.swapaxes(reference_mburnt_integ, 1, 2)
+
+        assert np.allclose(mburnt_integ[0], reference_mburnt_integ)
+
+    def test_line_fire_groundfuelheight(self):
+        # Load model output
+        groundfuelheight = _process_gridded_bin(
+            LINE_FIRE_DIR / "Output" / "groundfuelheight.bin",
+            (
+                1,
+                self.line_fire_sim.qu_simparams.ny,
+                self.line_fire_sim.qu_simparams.nx,
+            ),
+        )
+
+        # Load reference data for comparison
+        npz = np.load(LINE_FIRE_DIR / "Reference" / "groundfuelheight.npz")
+        reference_groundfuelheight = npz["var"]
+
+        assert np.allclose(groundfuelheight[0], reference_groundfuelheight)
+
+    def test_eglin_eng2atmos(self):
+        _, _, eglin_eng2atmos_nz, _ = _process_grid_info(
+            EG_CANOPY_DIR / "Output" / "grid.bin",
+            self.eglin_canopy_sim.qu_simparams.ny,
+            self.eglin_canopy_sim.qu_simparams.nx,
+        )
+
+        # Load first time step
+        eng2atmos = _process_gridded_bin(
+            EG_CANOPY_DIR / "Output" / "fire-energy_to_atmos-00000.bin",
+            (
+                eglin_eng2atmos_nz,
+                self.eglin_canopy_sim.qu_simparams.ny,
+                self.eglin_canopy_sim.qu_simparams.nx,
+            ),
+        )
+
+        # Load reference data for comparison
+        npz = np.load(EG_CANOPY_DIR / "Reference" / "fire-energy_to_atmos-00000.npz")
+        reference_eng2atmos = npz["var"]
+        reference_eng2atmos = np.swapaxes(reference_eng2atmos, 0, 2)
+        reference_eng2atmos = np.swapaxes(reference_eng2atmos, 1, 2)
+
+        assert eng2atmos[0].shape == reference_eng2atmos.shape
+        assert np.allclose(eng2atmos[0], reference_eng2atmos)
+
+        # Load next time step
+        eng2atmos = _process_gridded_bin(
+            EG_CANOPY_DIR / "Output" / "fire-energy_to_atmos-00600.bin",
+            (
+                eglin_eng2atmos_nz,
+                self.eglin_canopy_sim.qu_simparams.ny,
+                self.eglin_canopy_sim.qu_simparams.nx,
+            ),
+            self.eglin_canopy_indexes,
+        )
+
+        # Load reference data for comparison
+        npz = np.load(EG_CANOPY_DIR / "Reference" / "fire-energy_to_atmos-00600.npz")
+        reference_eng2atmos = npz["var"]
+        reference_eng2atmos = np.swapaxes(reference_eng2atmos, 0, 2)
+        reference_eng2atmos = np.swapaxes(reference_eng2atmos, 1, 2)
+
+        assert eng2atmos[0].shape == reference_eng2atmos.shape
+        assert np.allclose(eng2atmos[0], reference_eng2atmos)
+
+    def test_eglin_qu_windu(self):
+        # Load first time step
+        qu_windu = _process_gridded_bin(
+            EG_CANOPY_DIR / "Output" / "qu_windu00600.bin",
+            (
+                self.eglin_canopy_sim.qu_simparams.nz,
+                self.eglin_canopy_sim.qu_simparams.ny,
+                self.eglin_canopy_sim.qu_simparams.nx,
+            ),
+        )
+
+        # Load reference data for comparison
+        npz = np.load(EG_CANOPY_DIR / "Reference" / "qu_windu00600.npz")
+        reference_qu_windu = npz["var"]
+        reference_qu_windu = np.swapaxes(reference_qu_windu, 0, 2)
+        reference_qu_windu = np.swapaxes(reference_qu_windu, 1, 2)
+        reference_qu_windu = reference_qu_windu[:-1, ...]
+
+        assert np.allclose(qu_windu[0], reference_qu_windu)
+
+    def test_eglin_windu(self):
+        # Load first time step
+        windu = _process_gridded_bin(
+            EG_CANOPY_DIR / "Output" / "windu00600.bin",
+            (
+                self.eglin_canopy_sim.quic_fire.nz,
+                self.eglin_canopy_sim.qu_simparams.ny,
+                self.eglin_canopy_sim.qu_simparams.nx,
+            ),
+        )
+
+        # Load reference data for comparison
+        npz = np.load(EG_CANOPY_DIR / "Reference" / "windu00600.npz")
+        reference_windu = npz["var"]
+        reference_windu = np.swapaxes(reference_windu, 0, 2)
+        reference_windu = np.swapaxes(reference_windu, 1, 2)
+        reference_windu = reference_windu[:-1, ...]
+
+        assert np.allclose(windu[0], reference_windu)
+
+    def test_eglin_mburnt_integ(self):
+        # Load first time step
+        mburnt_integ = _process_gridded_bin(
+            EG_CANOPY_DIR / "Output" / "mburnt_integ-00000.bin",
+            (
+                1,
+                self.eglin_canopy_sim.qu_simparams.ny,
+                self.eglin_canopy_sim.qu_simparams.nx,
+            ),
+        )
+
+        # Load reference data for comparison
+        npz = np.load(EG_CANOPY_DIR / "Reference" / "mburnt_integ-00000.npz")
+        reference_mburnt_integ = npz["var"]
+        reference_mburnt_integ = np.swapaxes(reference_mburnt_integ, 0, 2)
+        reference_mburnt_integ = np.swapaxes(reference_mburnt_integ, 1, 2)
+
+        assert np.allclose(mburnt_integ[0], reference_mburnt_integ)
+
+        # Load next time step
+        mburnt_integ = _process_gridded_bin(
+            EG_CANOPY_DIR / "Output" / "mburnt_integ-00600.bin",
+            (
+                1,
+                self.eglin_canopy_sim.qu_simparams.ny,
+                self.eglin_canopy_sim.qu_simparams.nx,
+            ),
+        )
+
+        # Load reference data for comparison
+        npz = np.load(EG_CANOPY_DIR / "Reference" / "mburnt_integ-00600.npz")
+        reference_mburnt_integ = npz["var"]
+        reference_mburnt_integ = np.swapaxes(reference_mburnt_integ, 0, 2)
+        reference_mburnt_integ = np.swapaxes(reference_mburnt_integ, 1, 2)
+
+        assert np.allclose(mburnt_integ[0], reference_mburnt_integ)
+
+    def test_eglin_groundfuelheight(self):
+        # Load model output
+        groundfuelheight = _process_gridded_bin(
+            EG_CANOPY_DIR / "Output" / "groundfuelheight.bin",
+            (
+                1,
+                self.eglin_canopy_sim.qu_simparams.ny,
+                self.eglin_canopy_sim.qu_simparams.nx,
+            ),
+        )
+
+        # Load reference data for comparison
+        npz = np.load(EG_CANOPY_DIR / "Reference" / "groundfuelheight.npz")
+        reference_groundfuelheight = npz["var"]
+
+        assert np.allclose(groundfuelheight[0], reference_groundfuelheight)
+
+
+class TestSimulationOutputs:
+    def test_line_fire(self):
+        line_fire = SimulationInputs.from_directory(LINE_FIRE_DIR)
+        outputs = SimulationOutputs(
+            LINE_FIRE_DIR / "Output",
+            line_fire.quic_fire.nz,
+            line_fire.qu_simparams.ny,
+            line_fire.qu_simparams.nx,
+            line_fire.qu_simparams.dy,
+            line_fire.qu_simparams.dx,
+        )
+
+        # Check that the grid info is correct
+        assert outputs.fire_nz == line_fire.quic_fire.nz
+        assert outputs.quic_nz == line_fire.qu_simparams.nz
+        assert outputs.fire_dz == line_fire.quic_fire.dz
+        assert np.allclose(outputs.quic_dz, line_fire.qu_simparams._dz_array[:-1])
+
+    def test_line_fire_georeferenced(self):
+        line_fire = SimulationInputs.from_directory(LINE_FIRE_DIR)
+        utm_zones = range(1, 16)
+        epsg_codes = [32600 + utm_zone for utm_zone in utm_zones]
+        for zone, epsg in zip(utm_zones, epsg_codes):
+            outputs = SimulationOutputs(
+                LINE_FIRE_DIR / "Output",
+                line_fire.quic_fire.nz,
+                line_fire.qu_simparams.ny,
+                line_fire.qu_simparams.nx,
+                line_fire.qu_simparams.dy,
+                line_fire.qu_simparams.dx,
+                utm_x=np.random.randint(1, 1e8),
+                utm_y=np.random.randint(1, 1e8),
+                utm_zone=zone,
+            )
+
+            assert outputs.crs == f"EPSG:{epsg}"
+
+    def test_line_fire_list_outputs(self):
+        line_fire = SimulationInputs.from_directory(LINE_FIRE_DIR)
+        outputs = SimulationOutputs(
+            LINE_FIRE_DIR / "Output",
+            line_fire.quic_fire.nz,
+            line_fire.qu_simparams.ny,
+            line_fire.qu_simparams.nx,
+            line_fire.qu_simparams.dy,
+            line_fire.qu_simparams.dx,
+        )
+        outputs_list = outputs.list_outputs()
+
+        assert "fire-energy_to_atmos" in outputs_list
+        assert "fuels-dens" in outputs_list
+        assert "groundfuelheight" in outputs_list
+        assert "mburnt_integ" in outputs_list
+        assert "surfEnergy" in outputs_list
+
+    def test_line_fire_get_output(self):
+        line_fire = SimulationInputs.from_directory(LINE_FIRE_DIR)
+        outputs = SimulationOutputs(
+            LINE_FIRE_DIR / "Output",
+            line_fire.quic_fire.nz,
+            line_fire.qu_simparams.ny,
+            line_fire.qu_simparams.nx,
+            line_fire.qu_simparams.dy,
+            line_fire.qu_simparams.dx,
+        )
+
+        fire_energy = outputs.get_output("fire-energy_to_atmos")
+        assert fire_energy.times == [0, 60]
+
+        fuels_dens = outputs.get_output("fuels-dens")
+        assert fuels_dens.times == [0, 60]
+
+        groundfuelheight = outputs.get_output("groundfuelheight")
+        assert groundfuelheight.times == [0]
+
+        mburnt_integ = outputs.get_output("mburnt_integ")
+        assert mburnt_integ.times == [0, 60]
+
+        surf_energy = outputs.get_output("surfEnergy")
+        assert surf_energy.times == [0, 9]
+
+    def test_line_fire_from_simulation(self):
+        # Create the SimulationOutputs object with the normal constructor
+        line_fire = SimulationInputs.from_directory(LINE_FIRE_DIR)
+        outputs = SimulationOutputs(
+            LINE_FIRE_DIR / "Output",
+            line_fire.quic_fire.nz,
+            line_fire.qu_simparams.ny,
+            line_fire.qu_simparams.nx,
+            line_fire.qu_simparams.dy,
+            line_fire.qu_simparams.dx,
+        )
+
+        # Create the SimulationOutputs object from the SimulationInputs object
+        outputs_from_sim_inputs = SimulationOutputs.from_simulation_inputs(
+            LINE_FIRE_DIR / "Output", line_fire
+        )
+
+        # The two objects should be the same
+        assert outputs == outputs_from_sim_inputs
+
+    def test_eglin_canopy(self):
+        eglin_canopy = SimulationInputs.from_directory(EG_CANOPY_DIR)
+        outputs = SimulationOutputs(
+            EG_CANOPY_DIR / "Output",
+            eglin_canopy.quic_fire.nz,
+            eglin_canopy.qu_simparams.ny,
+            eglin_canopy.qu_simparams.nx,
+            eglin_canopy.qu_simparams.dy,
+            eglin_canopy.qu_simparams.dx,
+        )
+
+        # Check that the grid info is correct
+        assert outputs.fire_nz == eglin_canopy.quic_fire.nz
+        assert outputs.quic_nz == eglin_canopy.qu_simparams.nz
+        assert outputs.fire_dz == eglin_canopy.quic_fire.dz
+        assert np.allclose(outputs.quic_dz, eglin_canopy.qu_simparams._dz_array[:-1])
+
+    def test_eglin_canopy_list_outputs(self):
+        eglin_canopy = SimulationInputs.from_directory(EG_CANOPY_DIR)
+        outputs = SimulationOutputs(
+            EG_CANOPY_DIR / "Output",
+            eglin_canopy.quic_fire.nz,
+            eglin_canopy.qu_simparams.ny,
+            eglin_canopy.qu_simparams.nx,
+            eglin_canopy.qu_simparams.dy,
+            eglin_canopy.qu_simparams.dx,
+        )
+
+        assert "co_emissions" in outputs.list_outputs()
+        assert "fire-energy_to_atmos" in outputs.list_outputs()
+        assert "fuels-dens" in outputs.list_outputs()
+        assert "groundfuelheight" in outputs.list_outputs()
+        assert "mburnt_integ" in outputs.list_outputs()
+        assert "qu_windu" in outputs.list_outputs()
+        assert "thermalradiation" in outputs.list_outputs()
+        assert "windu" in outputs.list_outputs()
+
+    def test_eglin_canopy_get_output(self):
+        eglin_canopy = SimulationInputs.from_directory(EG_CANOPY_DIR)
+        outputs = SimulationOutputs(
+            EG_CANOPY_DIR / "Output",
+            eglin_canopy.quic_fire.nz,
+            eglin_canopy.qu_simparams.ny,
+            eglin_canopy.qu_simparams.nx,
+            eglin_canopy.qu_simparams.dy,
+            eglin_canopy.qu_simparams.dx,
+        )
+
+        co_emissions = outputs.get_output("co_emissions")
+        assert co_emissions.times == [600]
+        assert co_emissions.shape == (outputs.fire_nz, outputs.ny, outputs.nx)
+
+        fire_energy = outputs.get_output("fire-energy_to_atmos")
+        assert fire_energy.times == [0, 600]
+        assert fire_energy.shape == (outputs.en2atmos_nz, outputs.ny, outputs.nx)
+
+        groundfuelheight = outputs.get_output("groundfuelheight")
+        assert groundfuelheight.times == [0]
+        assert groundfuelheight.shape == (1, outputs.ny, outputs.nx)
+
+        mburnt_integ = outputs.get_output("mburnt_integ")
+        assert mburnt_integ.times == [0, 600]
+        assert mburnt_integ.shape == (1, outputs.ny, outputs.nx)
+
+        qu_windu = outputs.get_output("qu_windu")
+        assert qu_windu.times == [600]
+        assert qu_windu.shape == (outputs.quic_nz, outputs.ny, outputs.nx)
+
+        thermal_rad = outputs.get_output("thermalradiation")
+        assert thermal_rad.times == [600]
+        assert thermal_rad.shape == (outputs.fire_nz, outputs.ny, outputs.nx)
+
+        wind = outputs.get_output("windu")
+        assert wind.times == [600]
+        assert wind.shape == (outputs.fire_nz, outputs.ny, outputs.nx)
+
+    def test_object_equality(self):
+        line_fire = SimulationInputs.from_directory(LINE_FIRE_DIR)
+        line_fire_outputs = SimulationOutputs(
+            LINE_FIRE_DIR / "Output",
+            line_fire.quic_fire.nz,
+            line_fire.qu_simparams.ny,
+            line_fire.qu_simparams.nx,
+            line_fire.qu_simparams.dy,
+            line_fire.qu_simparams.dx,
+        )
+
+        eglin_canopy = SimulationInputs.from_directory(EG_CANOPY_DIR)
+        eglin_canopy_outputs = SimulationOutputs(
+            EG_CANOPY_DIR / "Output",
+            eglin_canopy.quic_fire.nz,
+            eglin_canopy.qu_simparams.ny,
+            eglin_canopy.qu_simparams.nx,
+            eglin_canopy.qu_simparams.dy,
+            eglin_canopy.qu_simparams.dx,
+        )
+
+        assert line_fire_outputs != eglin_canopy_outputs
+
+
+class TestOutputFileToNumpy:
+    line_fire = SimulationInputs.from_directory(LINE_FIRE_DIR)
+    line_fire_outputs = SimulationOutputs.from_simulation_inputs(
+        LINE_FIRE_DIR / "Output", line_fire
+    )
+
+    eglin_canopy = SimulationInputs.from_directory(EG_CANOPY_DIR)
+    eglin_canopy_outputs = SimulationOutputs.from_simulation_inputs(
+        EG_CANOPY_DIR / "Output", eglin_canopy
+    )
+
+    @staticmethod
+    def _test_to_numpy(output: OutputFile, times, nz, ny, nx):
+        # Get all time steps
+        data_all = output.to_numpy()
+        assert data_all.shape == (len(times), nz, ny, nx)
+
+        # Get all time steps with range
+        data_all = output.to_numpy(range(len(times)))
+        assert data_all.shape == (len(times), nz, ny, nx)
+
+        # Get every other time step with range
+        data_all = output.to_numpy(range(0, len(times), 2))
+        assert data_all.shape == (max(len(times) // 2, 1), nz, ny, nx)
+
+        # Get the first time step
+        data_first = output.to_numpy(timestep=0)
+        assert data_first.shape == (1, nz, ny, nx)
+
+        # Get the last time step
+        data_last = output.to_numpy(timestep=-1)
+        assert data_last.shape == (1, nz, ny, nx)
+
+    def test_lf_eng2atmos(self):
+        eng2atmos = self.line_fire_outputs.get_output("fire-energy_to_atmos")
+        self._test_to_numpy(eng2atmos, eng2atmos.times, *eng2atmos.shape)
+
+    def test_lf_fuels_dens(self):
+        fuels_dens = self.line_fire_outputs.get_output("fuels-dens")
+        self._test_to_numpy(fuels_dens, fuels_dens.times, *fuels_dens.shape)
+
+    def test_lf_groundfuelheight(self):
+        groundfuelheight = self.line_fire_outputs.get_output("groundfuelheight")
+        self._test_to_numpy(
+            groundfuelheight,
+            groundfuelheight.times,
+            *groundfuelheight.shape,
+        )
+
+    def test_lf_mburnt_integ(self):
+        mburnt_integ = self.line_fire_outputs.get_output("mburnt_integ")
+        self._test_to_numpy(
+            mburnt_integ,
+            mburnt_integ.times,
+            *mburnt_integ.shape,
+        )
+
+    def test_lf_surf_energy(self):
+        surf_energy = self.line_fire_outputs.get_output("surfEnergy")
+        self._test_to_numpy(
+            surf_energy,
+            surf_energy.times,
+            *surf_energy.shape,
+        )
+
+    def test_ec_co_emissions(self):
+        co_emissions = self.eglin_canopy_outputs.get_output("co_emissions")
+        self._test_to_numpy(co_emissions, co_emissions.times, *co_emissions.shape)
+
+    def test_ec_eng2atmos(self):
+        eng2atmos = self.eglin_canopy_outputs.get_output("fire-energy_to_atmos")
+        self._test_to_numpy(eng2atmos, eng2atmos.times, *eng2atmos.shape)
+
+    def test_ec_fuel_dens(self):
+        fuels_dens = self.eglin_canopy_outputs.get_output("fuels-dens")
+        self._test_to_numpy(fuels_dens, fuels_dens.times, *fuels_dens.shape)
+
+    def test_ec_groundfuelheight(self):
+        groundfuelheight = self.eglin_canopy_outputs.get_output("groundfuelheight")
+        self._test_to_numpy(
+            groundfuelheight,
+            groundfuelheight.times,
+            *groundfuelheight.shape,
+        )
+
+    def test_ec_mburnt_integ(self):
+        mburnt_integ = self.eglin_canopy_outputs.get_output("mburnt_integ")
+        self._test_to_numpy(
+            mburnt_integ,
+            mburnt_integ.times,
+            *mburnt_integ.shape,
+        )
+
+    def test_ec_qu_windu_to_numpy(self):
+        qu_windu = self.eglin_canopy_outputs.get_output("qu_windu")
+        self._test_to_numpy(
+            qu_windu,
+            qu_windu.times,
+            *qu_windu.shape,
+        )
+
+    def test_ec_thermal_radiation_to_numpy(self):
+        thermal_radiation = self.eglin_canopy_outputs.get_output("thermalradiation")
+        self._test_to_numpy(
+            thermal_radiation,
+            thermal_radiation.times,
+            *thermal_radiation.shape,
+        )
+
+    def test_ec_windu_to_numpy(self):
+        windu = self.eglin_canopy_outputs.get_output("windu")
+        self._test_to_numpy(
+            windu,
+            windu.times,
+            *windu.shape,
+        )
+
+
+class TestOutputFileToNetCDF:
+    line_fire = SimulationInputs.from_directory(LINE_FIRE_DIR)
+    line_fire_outputs = SimulationOutputs.from_simulation_inputs(
+        LINE_FIRE_DIR / "Output", line_fire
+    )
+
+    eglin_canopy = SimulationInputs.from_directory(EG_CANOPY_DIR)
+    eglin_canopy_outputs = SimulationOutputs.from_simulation_inputs(
+        EG_CANOPY_DIR / "Output", eglin_canopy
+    )
+
+    @staticmethod
+    def _test_to_netcdf(output: OutputFile, directory: Path, times, nz, ny, nx):
+        # Get all time steps
+        data_all = output.to_numpy()
+        output.to_netcdf(directory)
+        test = nc.Dataset(directory / f"{output.name}.nc", "r")
+        data_all = test.variables[output.name]
+        assert data_all.shape == (len(times), nz, ny, nx)
+        test.close()
+
+        # Get all time steps with range
+        data_all = output.to_numpy(range(len(times)))
+        output.to_netcdf(directory, range(len(times)))
+        test = nc.Dataset(directory / f"{output.name}.nc", "r")
+        data_all = test.variables[output.name]
+        assert data_all.shape == (len(times), nz, ny, nx)
+        test.close()
+
+        # Get every other time step with range
+        data_all = output.to_numpy(range(0, len(times), 2))
+        output.to_netcdf(directory, range(0, len(times), 2))
+        test = nc.Dataset(directory / f"{output.name}.nc", "r")
+        data_all = test.variables[output.name]
+        assert data_all.shape == (max(len(times) // 2, 1), nz, ny, nx)
+        test.close()
+
+        # Get the first time step
+        data_first = output.to_numpy(timestep=0)
+        output.to_netcdf(directory, timestep=0)
+        test = nc.Dataset(directory / f"{output.name}.nc", "r")
+        data_first = test.variables[output.name]
+        assert data_first.shape == (1, nz, ny, nx)
+        test.close()
+
+        # Get the last time step
+        data_last = output.to_numpy(timestep=-1)
+        output.to_netcdf(directory, timestep=-1)
+        test = nc.Dataset(directory / f"{output.name}.nc", "r")
+        data_last = test.variables[output.name]
+        assert data_last.shape == (1, nz, ny, nx)
+        test.close()
+
+    def test_lf_eng2atmos(self):
+        eng2atmos = self.line_fire_outputs.get_output("fire-energy_to_atmos")
+        self._test_to_netcdf(eng2atmos, TMP_DIR, eng2atmos.times, *eng2atmos.shape)
+
+    def test_lf_fuels_dens(self):
+        fuels_dens = self.line_fire_outputs.get_output("fuels-dens")
+        self._test_to_netcdf(fuels_dens, TMP_DIR, fuels_dens.times, *fuels_dens.shape)
+
+    def test_lf_groundfuelheight(self):
+        groundfuelheight = self.line_fire_outputs.get_output("groundfuelheight")
+        self._test_to_netcdf(
+            groundfuelheight,
+            TMP_DIR,
+            groundfuelheight.times,
+            *groundfuelheight.shape,
+        )
+
+    def test_lf_mburnt_integ(self):
+        mburnt_integ = self.line_fire_outputs.get_output("mburnt_integ")
+        self._test_to_netcdf(
+            mburnt_integ,
+            TMP_DIR,
+            mburnt_integ.times,
+            *mburnt_integ.shape,
+        )
+
+    def test_lf_surf_energy(self):
+        surf_energy = self.line_fire_outputs.get_output("surfEnergy")
+        self._test_to_netcdf(
+            surf_energy,
+            TMP_DIR,
+            surf_energy.times,
+            *surf_energy.shape,
+        )
+
+    def test_ec_co_emissions(self):
+        co_emissions = self.eglin_canopy_outputs.get_output("co_emissions")
+        self._test_to_netcdf(
+            co_emissions, TMP_DIR, co_emissions.times, *co_emissions.shape
+        )
+
+    def test_ec_eng2atmos(self):
+        eng2atmos = self.eglin_canopy_outputs.get_output("fire-energy_to_atmos")
+        self._test_to_netcdf(eng2atmos, TMP_DIR, eng2atmos.times, *eng2atmos.shape)
+
+    def test_ec_fuel_dens(self):
+        fuels_dens = self.eglin_canopy_outputs.get_output("fuels-dens")
+        self._test_to_netcdf(fuels_dens, TMP_DIR, fuels_dens.times, *fuels_dens.shape)
+
+    def test_ec_groundfuelheight(self):
+        groundfuelheight = self.eglin_canopy_outputs.get_output("groundfuelheight")
+        self._test_to_netcdf(
+            groundfuelheight,
+            TMP_DIR,
+            groundfuelheight.times,
+            *groundfuelheight.shape,
+        )
+
+    def test_ec_mburnt_integ(self):
+        mburnt_integ = self.eglin_canopy_outputs.get_output("mburnt_integ")
+        self._test_to_netcdf(
+            mburnt_integ,
+            TMP_DIR,
+            mburnt_integ.times,
+            *mburnt_integ.shape,
+        )
+
+    def test_ec_qu_windu_to_numpy(self):
+        qu_windu = self.eglin_canopy_outputs.get_output("qu_windu")
+        self._test_to_netcdf(
+            qu_windu,
+            TMP_DIR,
+            qu_windu.times,
+            *qu_windu.shape,
+        )
+
+    def test_ec_thermal_radiation_to_numpy(self):
+        thermal_radiation = self.eglin_canopy_outputs.get_output("thermalradiation")
+        self._test_to_netcdf(
+            thermal_radiation,
+            TMP_DIR,
+            thermal_radiation.times,
+            *thermal_radiation.shape,
+        )
+
+    def test_ec_windu_to_numpy(self):
+        windu = self.eglin_canopy_outputs.get_output("windu")
+        self._test_to_netcdf(
+            windu,
+            TMP_DIR,
+            windu.times,
+            *windu.shape,
+        )
+
+
+class TestOutputFileToZarr:
+    def test_lf_fuel_dens(self):
+        line_fire = SimulationInputs.from_directory(LINE_FIRE_DIR)
+        line_fire_outputs = SimulationOutputs.from_simulation_inputs(
+            LINE_FIRE_DIR / "Output", line_fire
+        )
+
+        fuels_dens = line_fire_outputs.get_output("fuels-dens")
+        fuels_dens.to_zarr(TMP_DIR)
+
+        zroot = zarr.open(str(TMP_DIR / "fuels-dens.zarr"), mode="r")
+        assert np.allclose(zroot["time"][:], fuels_dens.times)
+        assert np.allclose(zroot["x"][:], fuels_dens.x_coords)
+        assert np.allclose(zroot["y"][:], fuels_dens.y_coords)
+        assert np.allclose(zroot["z"][:], fuels_dens.z_coords)
+
+        z_array = zroot["fuels-dens"]
+        assert np.allclose(z_array[:], fuels_dens.to_numpy())
+        assert z_array.chunks == (1, *fuels_dens.shape)
+
+    def test_lf_fuel_dens_custom_time_chunks(self):
+        line_fire = SimulationInputs.from_directory(LINE_FIRE_DIR)
+        line_fire_outputs = SimulationOutputs.from_simulation_inputs(
+            LINE_FIRE_DIR / "Output", line_fire
+        )
+
+        fuels_dens = line_fire_outputs.get_output("fuels-dens")
+        fuels_dens.to_zarr(TMP_DIR, chunk_size={"time": 100})
+
+        zroot = zarr.open(str(TMP_DIR / "fuels-dens.zarr"), mode="r")
+        z_array = zroot["fuels-dens"]
+        print(z_array.chunks)
+
+        assert z_array.chunks == (100, *fuels_dens.shape)
+
+    def test_ec_qu_windu(self):
+        eglin_canopy = SimulationInputs.from_directory(EG_CANOPY_DIR)
+        eglin_canopy_outputs = SimulationOutputs.from_simulation_inputs(
+            EG_CANOPY_DIR / "Output", eglin_canopy
+        )
+
+        qu_windu = eglin_canopy_outputs.get_output("qu_windu")
+        qu_windu.to_zarr(TMP_DIR)
+
+        zroot = zarr.open(str(TMP_DIR / "qu_windu.zarr"), mode="r")
+        assert np.allclose(zroot["time"][:], qu_windu.times)
+        assert np.allclose(zroot["x"][:], qu_windu.x_coords)
+        assert np.allclose(zroot["y"][:], qu_windu.y_coords)
+        assert np.allclose(zroot["z"][:], qu_windu.z_coords)
+
+        z_array = zroot["qu_windu"]
+        assert np.allclose(z_array[:], qu_windu.to_numpy())
+        assert z_array.chunks == (1, *qu_windu.shape)
+
+    def test_ec_qu_windu_custom_chunks(self):
+        eglin_canopy = SimulationInputs.from_directory(EG_CANOPY_DIR)
+        eglin_canopy_outputs = SimulationOutputs.from_simulation_inputs(
+            EG_CANOPY_DIR / "Output", eglin_canopy
+        )
+
+        qu_windu = eglin_canopy_outputs.get_output("qu_windu")
+        qu_windu.to_zarr(
+            TMP_DIR,
+            chunk_size={
+                "time": 10,
+                "z": qu_windu.shape[0] // 2,
+                "y": qu_windu.shape[1] // 2,
+                "x": qu_windu.shape[2] // 2,
+            },
+        )
+
+        zroot = zarr.open(str(TMP_DIR / "qu_windu.zarr"), mode="r")
+        z_array = zroot["qu_windu"]
+
+        assert z_array.chunks == (
+            10,
+            qu_windu.shape[0] // 2,
+            qu_windu.shape[1] // 2,
+            qu_windu.shape[2] // 2,
+        )
+        assert z_array.cdata_shape == (1, 2, 2, 2)