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

Issue:
Idea for redoing outputs module tests
@zacharycope0 this is what I'd like to do for the new outputs module test suite.

The basic idea is to write the simulations from the examples module to the test data directory. Then, a user can run QF to generate the output files. Lastly, we can update the outputs tests to use those output files. Since we're using the simulations from the examples module the data will be consistent across all users. 

At this point lets just plan on running tests locally, and we can talk about hosting output files on GitHub for testing with actions if we want to.

What do you think of this approach?


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml
index 5ad85f9..29cbd8d 100644
--- a/.github/workflows/build-docs.yml
+++ b/.github/workflows/build-docs.yml
@@ -13,5 +13,5 @@ jobs:
         with:
           python-version: 3.x
           cache: pip
-      - run: pip install mkdocs mkdocstrings mkdocstrings-python mkdocs-material
+      - run: pip install mkdocs mkdocstrings mkdocstrings-python mkdocs-material black
       - run: mkdocs gh-deploy --force --clean --verbose
diff --git a/.gitignore b/.gitignore
index eb08009..f03782a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -137,6 +137,11 @@ dmypy.json
 dev/
 tests/dev/
 tests/tmp/
+tests/data/test-output-zarr/
 
 # macOS
 .DS_Store
+
+# Test files
+test-simulation-line-fire/
+test-simulation-custom-fuels/
diff --git a/docs/explanation.md b/docs/explanation.md
new file mode 100644
index 0000000..6ab6aba
--- /dev/null
+++ b/docs/explanation.md
@@ -0,0 +1,79 @@
+## Inputs module
+
+The [`inputs`](reference.md#quicfire_tools.inputs) module in `quicfire_tools` is a central part of the package that handles the creation, modification,
+and reading of QUIC-Fire input file decks. It provides a programmatic interface to interact with the various input
+files required for a QUIC-Fire simulation.
+
+### SimulationInputs Class
+
+The [`SimulationInputs`](reference.md#quicfire_tools.inputs.SimulationInputs) class is the primary class in the
+`inputs` module.
+It represents a QUIC-Fire input file deck with attributes that represent the state of each input file in the deck.
+These attributes are instances of various [`InputFile`] subclasses, each corresponding to a specific input file.
+
+The `SimulationInputs` class provides several methods for creating and modifying simulations. For example, the
+`create_simulation` method is used to create a new simulation with default values for fuels, ignitions, and topography.
+To modify these default values, `SimulationInputs` offers `set_*` methods, such as `set_uniform_fuels` and
+`set_rectangle_ignition`, to modify common simulation parameters and maintain consistency across input files.
+
+The `write_inputs` method is also used to write all input files in the `SimulationInputs` object to a
+specified directory. This method translates the state stored in the `SimulationInputs` object into the
+text format used by QUIC-Fire input files. In addition, simulation state can be saved to a JSON file for sharing,
+version control, or modification by using the `to_json` method.
+
+The `SimulationInputs` class also provides methods for reading existing input file decks and converting
+`SimulationInputs` objects to and from JSON format. The `from_directory` method is used to initialize a
+`SimulationInputs` object from a directory containing a QUIC-Fire input file deck. The `to_json` and `from_json`
+methods are used to save and load `SimulationInputs` objects in JSON format.
+
+### InputFile Class
+
+Each input file in a QUIC-Fire input file deck has a unique [Pydantic](https://docs.pydantic.dev/latest/)
+Base Model class that represents the state of that input file. These classes are subclasses of the [`InputFile`]
+class, which provides common functionality for all input files.
+
+This common functionality includes methods for reading and writing input files to disk, as well as methods for
+converting input files to and from JSON format. The `InputFile` class also provides methods to output the documentation
+for a QUIC-Fire parameter in a Python dictionary format.
+
+#### InputFiles objects and Pydantic
+
+The `InputFile` class is a subclass of the Pydantic `BaseModel` class. Pydantic is a Python library that provides
+data validation and serialization for Python data structures. Pydantic is used in `quicfire_tools` to validate the
+state of input files and to serialize input files to and from JSON format.
+
+Valid data types, ranges, and defaults are provided for each input file's attributes in the Pydantic `BaseModel`
+subclass for that input file. This allows Pydantic to validate the state of input files and raise errors when
+invalid values are provided.
+
+## Outputs module
+
+The [`outputs`](reference.md#quicfire_tools.outputs)
+module in `quicfire-tools` is responsible for managing and processing simulation outputs produced by QUIC-Fire.
+It provides efficient ways to access, extract, and organize data from various output files, enabling further analysis
+and visualization of simulation results.
+
+### OutputFile class
+The [`OutputFile`](reference.md#quicfire_tools.outputs.OutputFile)
+class represents a single output variable from a QUIC-Fire simulation.
+It handles loading the data from disk into NumPy arrays for a given timestep.
+
+Key functionality includes:
+
+* Mapping compressed formats to dense NumPy arrays
+* Automatically detecting available timesteps
+* Extracting data for specific timesteps
+* Integration with SimulationOutputs class
+
+### SimulationOutputs class
+The [`SimulationOutputs`](reference.md#quicfire_tools.outputs.SimulationOutputs)
+class is the main interface for working with QUIC-Fire output data.
+It collects available outputs in an Outputs directory and constructs `OutputFile` instances for each output variable.
+
+Key functionality includes:
+
+* Detecting available output variables
+* Validating directory structure
+* Getting outputs as NumPy arrays
+* Writing outputs to Zarr files
+* Integration with Dask arrays
diff --git a/docs/how-to-guides.md b/docs/how-to-guides.md
new file mode 100644
index 0000000..44afc36
--- /dev/null
+++ b/docs/how-to-guides.md
@@ -0,0 +1,337 @@
+## Inputs
+
+QUIC-Fire is controlled by a deck of input files that specify fuel parameters, wind conditions, ignitions, topography, etc.
+The inputs module provides a simple interface to programatically create and modify new input file decks or read existing decks.
+The following guides provide step-by-step instructions for working with simple QUIC-Fire input decks.
+Please see [inputs](reference.md#quicfire_tools.inputs) for full documentation.
+
+### How to create a basic QUIC-Fire simulation
+
+QUIC-Fire input decks are created and modified using the [`SimulationInputs`](reference.md#quicfire_tools.inputs.SimulationInputs) class.
+
+```python
+from quicfire_tools.inputs import SimulationInputs
+```
+
+To create a simulation, use the [`create_simulation`](reference.md#quicfire_tools.inputs.SimulationInputs.create_simulation) method, and
+input basic information about the burn domain, initial weather conditions, and simulation parameters. In the following example, we are
+setting up a simulation that is 200x200 grid cells, with one vertical layer of fuels. The wind is blowing from the east at 1.7 m/s, and
+the simulation will run for 600 seconds of simulation time.
+
+```python
+simulation = SimulationInputs.create_simulation(
+    nx=200,
+    ny=200,
+    fire_nz=1,
+    wind_speed=1.7,
+    wind_direction=90,
+    simulation_time=600
+)
+```
+
+- **nx** and **ny** define the number of cells in the x and y axis of the simulation grid.
+- **fire_nz** determines the number of cells in the z-axis for the fire grid.
+- **wind_speed** and **wind_direction** set the initial wind conditions.
+- **simulation_time** specifies how long the simulation will run in seconds.
+
+This creates a basic input deck with default values for fuels, ignitions, and topography. This simulation can be run as-is, or modified using
+methods described below.
+
+### How to use set_* methods for common simulation parameters
+
+Once a simulation is created, it can be modified directly through methods in the `SimulationInputs` class.
+For common modifications, convenience methods starting with `set_*` are available. Guides for all `set_*` methods are below.
+
+#### How to set uniform fuel conditions
+
+To set and modify fuel parameters for uniform fuels, use the [`set_uniform_fuels`](reference.md#quicfire_tools.inputs.SimulationInputs.set_uniform_fuels) method.
+In the following example, we are setting surface fuel density to 0.7 kg/m^3, fuel moisture to 5% of its dry weight, and fuel height to 1 meter.
+
+```python
+simulation.set_uniform_fuels(
+    fuel_density=0.7,
+    fuel_moisture=0.10,
+    fuel_height=1.0
+)
+```
+
+- **fuel_density** sets the surface fuel density in kg/m^3.
+- **fuel_moisture** sets the surface fuel moisture content as a fraction of the fuel's dry weight.
+- **fuel_height** sets the surface fuel height in meters.
+
+#### How to set a rectangle ignition pattern
+
+By default, ignitions are set up perpendicular to the wind direction specified in `create_simulation`, spanning 80% of the domain
+edge length, 10% from either side. A different igntion line can be created using the [`set_rectangle_ignition`](reference.md#quicfire_tools.inputs.SimulationInputs.set_rectangle_ignition) method.
+In the following example, ignitions start 150m from the western edge of the domain (x=0) and 100m from the southern edge of the domain (y=0). The rectangle extends 10 meters east in the x-direction,
+and 100m north in the y-direction.
+
+```python
+simulation.set_rectangle_ignition(
+    x_min=150,
+    y_min=100,
+    x_length=10,
+    y_length=100
+)
+```
+
+- **x_min** and **y_min** set the coordinates of the bottom left corner of the ignition zone. These coordinates are specified in meters, not grid cells.
+- **x_length** and **y_length** set the length of the ignition zone in the x and y directions in meters.
+
+Ignition patterns other than rectangular can be specified using the [ignitions module](how-to-guides.md#define-ignitions-using-ignitionspy).
+Please see [ignitions](reference.md#quicfire_tools.ignitions) for a full list of available ignition patterns.
+
+#### How to specify which files to output
+
+Depending on the desired analyses, different files (.bin) may need to be output. To specify with files should be written, use the [`set_output_files`](reference.md#quicfire_tools.inputs.SimulationInputs.set_output_files) method and set the desired files to true. In the following example, we are specifying outputs for fuel density, various emissions, and wind grid components.
+
+```python
+simulation.set_output_files(
+    fuel_dens=True,
+    emissions=True,
+    qu_wind_inst=True
+)
+```
+
+- **fuel_dens** specifies a compressed array fuel density output.
+- **emissions** specifies CO, PM2.5, and water emissions outputs.
+- **qu_wind_inst** specifies gridded wind components (u, v, w) for the QUIC (wind) grid.
+
+By default, an unmodified simulation will output fuel density and QUIC winds. After using `set_output_files`, all output files not specified as `True` will be set to false.
+
+Please see the [`QUIC_fire`](reference.md#quicfire_tools.inputs.QUIC_fire) class for a full list of available output files.
+
+#### How to set custom fuels, ignitions, and topography from .dat files
+
+For more advanced simulations, it may be necessary to specify custom fuel parameters, ignition patterns, or topography using .dat files. These files must be created and provided by the user, but the [`set_custom_simulation`](reference.md#quicfire_tools.inputs.SimulationInputs.set_custom_simulation) method can be used to specify which custom .dat files should be used in the simulation.
+
+```python
+simulation.set_custom_simulation(
+    fuel = True,
+    ignition = True,
+    topo = True
+)
+```
+
+- **fuel** specifies that fuel density, moisture, and height are provided by treesrhof.dat, treesmoist.dat, and treesfueldepth.dat, respectively.
+- **ignition** specifies that ignitions are provided by ignite.dat
+- **topo** specifies that topography is provided by topo.dat
+
+Any parameter not set to `True` will not be specified by a custom .dat file.
+
+### How to directly modify input files
+
+Every QUIC-Fire input file is represented in an `InputFile` class in the [inputs](reference.md#quicfire_tools.inputs) module. These classes can be accessed as attributes of the `SimulationInputs` class, where their parameters can be modified. Some of the more commonly modified input files are below:
+
+- **QUIC_fire** contains parameters relating to the fire simulation, including the fire grid, fuels, ignitions, and output files.
+- **QU_TopoInputs** contains parameters relating to the underlying topography of the simulation, including topography type and smoothing parameters.
+- **Sensor1** contains parameters relating to the wind conditions throughout the simulation.
+- **Runtime_Adananced_User_Inputs** contains parameters relating to the internal processing of the simulation.
+
+#### QUIC_fire.inp
+
+The [`QUIC_fire`](reference.md#quicfire_tools.inputs.QUIC_fire) input file class contains many parameters relating to the fire simulation. Once a simulation is created, these parameters can be accessed and modified through [`SimulationInputs.quic_fire`](reference.md#quicfire_tools.inputs.SimulationInputs.quic_fire). In the following example, some parameters not accessed by the `set_*` methods are modified.
+
+```python
+simulation.quic_fire.random_seed = 47
+simulation.quic_fire.out_time_fire = 60
+simulation.quic_fire.ignitions_per_cell = 5
+simulation.quic_fire.auto_kill = 1
+```
+
+Please see [`QUIC_fire`](reference.md#quicfire_tools.inputs.QUIC_fire) for a full list of parameters associated with the QUIC_fire.inp input file.
+
+#### QU_TopoInputs.inp
+
+The [`QU_TopoInputs`](reference.md#quicfire_tools.inputs.QU_TopoInputs) input file class contains parameters relating to the underlying topography of the simulation. Once a simulation is created, these parameters can be accessed and modified throught [`QU_TopoInputs`](reference.md#quicfire_tools.inputs.SimulationInputs.qu_topoinputs). For information on setting custom topography using built-in methods in the [topography](reference.md#quicfire_tools.topography) module, see [Set custom topography](how-to-guides.md#define-topography-using-topographypy). In the following example, some parameters not accessed by the `set_*` methods or the `topography` module are modified.
+
+```python
+simulation.qu_topoinputs.smoothing_passes = 500
+simulation.qu_topoinputs.sor_iteration = 300
+```
+
+Please see [`QU_TopoInputs`](reference.md#quicfire_tools.inputs.QU_TopoInputs) for a full list of parameters associated with the QUIC_fire.inp input file.
+
+#### sensor1.inp
+The ['Sensor1](reference.md#quicfire_tools.inputs.Sensor1) input file class contains parameters defining wind conditions throughout the simulation. Once a simulation is created, these parameters can be accessed and modified through [`SimulationInputs.sensor1`](reference.md#quicfire_tools.inputs.SimulationInputs.sensor1).
+
+```python
+simulation.sensor1.sensor_height = 10
+```
+
+Please see [`Sensor1`](reference.md#quicfire_tools.inputs.Senor1) for a full list of parameters associated with the sensor1.inp input file.
+
+#### Runtime_Advanced_User_Inputs.inp
+
+The [`Runtime_Advanced_User_Inputs`](reference.md#quicfire_tools.inputs.Runtime_Advanced_User_Inputs) input file class condtains two parameters relating to the internal processing of the simulation. Once a simulation is created, these parameters can be accessed and modified throught [`Runtime_Advanced_User_Inputs`](reference.md#quicfire_tools.inputs.SimulationInputs.runtime_advanced_user_inputs). In the following example, the number of CPUs/threads is specifed, along with whether or not to use the adaptive computational window.
+
+```python
+simulation.runtime_advanced_user_inputs.num_cpus = 1
+simulation.runtime_advanced_user_inputs.use_acw = 1
+```
+
+### How to define topography using topography.py
+
+In addition to flat topography (the default) and [custom topography](how-to-guides.md#set-custom-fuels-ignitions-and-topography-from-dat-files), there are various built-in topography types, all of which can be set using classes in the [topopgraphy](reference.md#quicfire_tools.topography) module. The following example creates and sets Gaussian hill topography.
+
+```python
+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
+)
+
+# Next, assign it to the topo_type attribute of the qu_topoinputs InputFile
+simulation.qu_topoinputs.topo_type = topo
+```
+
+Please see [topopgraphy](reference.md#quicfire_tools.topography) for a full list of topography types.
+
+### How to define ignitions using ignitions.py
+
+In addition to [rectangle ignitions](how-to-guides.md#set-rectangle-ignition-patterns) (the default) and [custom ignitions](how-to-guides.md#set-custom-fuels-ignitions-and-topography-from-dat-files), there are various build-in ignition patterns, all of which can be set using class in the [ignnitions](reference.md#quicfire_tools.ignitions) module. The following example creates and sets a circular ring ignition.
+
+```python
+from quicfire_tools.ignitions import CurcularRingIgnition
+
+# First, create an IgnitionType class
+ignition = CircularRingIgnition(
+    x_min = 50,
+    y_min = 50,
+    x_length = 20,
+    y_length = 20,
+    ring_width = 10
+)
+
+# Next, assign it to the ignition_type attribute of the quic_fire InputFile
+simulation.quic_fire.ignition_type
+```
+
+Please see [igntions](reference.md#quicfire_tools.ignitions) for a full list of ignition patterns.
+
+### Set weather conditions
+
+What goes here?
+
+### How to read and write input file decks
+
+#### How to write a SimulationInputs object to an input deck
+
+Once a `SimulationInputs` object has been created and modified, it can be used to write all the necessary input files to a directory containing the QUIC-Fire executable.
+This is done using the [`write_inputs`](reference.md#quicfire_tools.inputs.SimulationInputs.write_inputs) method.
+
+```python
+simulation.write_inputs("path/to/directory")
+```
+
+#### How to load an existing input deck
+
+Input decks that already exist may be read in as a `SimulationInputs` object. The [`from_directory`](reference.md#quicfire_tools.inputs.SimulationInputs.from_directory) method
+is used for this purpose.
+
+```python
+simulation = SimulationInputs.from_directory("path/to/directory")
+```
+
+#### How to write a simulation to JSON
+
+All the information in a `SimulationInputs` object may be saved in JSON format using the [`to_json`](reference.md#quicire_tools.inputs.SimulationInputs.to_json) method.
+
+```python
+simulation.to_json("path/to/directory")
+```
+
+#### How to load a simulation from a JSON file
+
+Input decks saved in JSON format by quicfire-tools can be loaded into a `SimulationInputs` oject using the [`from_json`](reference.md#quicfire_tools.inputs.SimulationInputs.from_json) method.
+
+```python
+simulation = SimulationInputs.from_json("path/to/directory")
+```
+
+## Outputs
+
+### How to create a SimulationOutputs object from a directory containing QUIC-Fire output files
+
+To read and process QUIC-Fire output files, use the [`SimulationOutputs`](reference.md#quicfire_tools.outputs.SimulationOutputs) class.
+You need to specify the path to the directory containing the output files, as well as the number of cells in the z, y, and x directions of the simulation grid.
+
+```python
+from quicfire_tools.outputs import SimulationOutputs
+
+output_directory = "/path/to/output/directory"
+nz = 56  # number of z cells
+ny = 100  # number of y cells
+nx = 100  # number of x cells
+
+simulation_outputs = SimulationOutputs(output_directory, nz, ny, nx)
+```
+
+### How to get an OutputFile object from a SimulationOutputs object
+
+Once you have a `SimulationOutputs` object, you can use it to get an [`OutputFile`](reference.md#quicfire_tools.outputs.OutputFile) object for a specific output file.
+To do this, use the [`get_output_file`](reference.md#quicfire_tools.outputs.SimulationOutputs.get_output_file) method of the `SimulationOutputs` class.
+
+```python
+from quicfire_tools.outputs import SimulationOutputs
+simulation_outputs = SimulationOutputs("/path/to/output/directory", 56, 100, 100)
+
+output_name = "fire-energy_to_atmos"  # replace with the name of the output you are interested in
+output_file = simulation_outputs.get_output(output_name)
+```
+
+### How to get a numpy array from an OutputFile object at a specific timestep
+
+Once you have an `OutputFile` object, you can use it to get a numpy array for the output data using the
+[`to_numpy`](reference.md#quicfire_tools.outputs.OutputFile.to_numpy) method of the `OutputFile` instance. You can
+specify the timestep(s) you are interested in. If you don't provide a timestep, all timesteps will be returned:
+
+```python
+from quicfire_tools.outputs import SimulationOutputs
+simulation_outputs = SimulationOutputs("/path/to/output/directory", 56, 100, 100)
+output_file = simulation_outputs.get_output("fire-energy_to_atmos")
+
+timestep = 0  # replace with the timestep you are interested in
+output_data = output_file.to_numpy(timestep)
+```
+
+#### How to get the data across all timesteps
+
+By not specifying a timestep, you can get the data across all timesteps. This will return a 4D numpy array with the
+shape (nt, nz, ny, nx), where nt is the number of timesteps.
+
+_caution:_ please be aware of the memory requirements your data. This approach may not be appropriate for large simulations
+or for computers with limited memory.
+
+```python
+from quicfire_tools.outputs import SimulationOutputs
+simulation_outputs = SimulationOutputs("/path/to/output/directory", 56, 100, 100)
+output_file = simulation_outputs.get_output("fire-energy_to_atmos")
+
+output_data = output_file.to_numpy()
+```
+
+### How to get a dask array from a QUIC-Fire output file
+
+You can use the [`to_dask`](reference.md#quicfire_tools.outputs.SimulationOutputs.to_dask) method of the `SimulationOutputs` class to
+get a dask array for the desired output file. This method returns a
+[`dask.array`](https://docs.dask.org/en/latest/array.html) object, which can be used to read and process data in parallel,
+perform lazy computations, and work with data that is too large to fit in memory.
+
+Note that this method returns a dask array, not a numpy array. To get a numpy array, you can use the
+[`compute`](https://docs.dask.org/en/latest/array-api.html#dask.array.Array.compute) method of the dask array to perform
+the computations and load data into memory.
+
+The dask array returned by this method has the same format as the numpy array returned by the
+[`to_numpy`](reference.md#quicfire_tools.outputs.OutputFile.to_numpy) method, a 4D array with the shape
+(nt, nz, ny, nx), where nt is the number of timesteps.
+
+```python
+from quicfire_tools.outputs import SimulationOutputs
+simulation_outputs = SimulationOutputs("/path/to/output/directory", 56, 100, 100)
+dask_array = simulation_outputs.to_dask("fire-energy_to_atmos")
+```
diff --git a/docs/tutorials.md b/docs/tutorials.md
new file mode 100644
index 0000000..8eb0f8f
--- /dev/null
+++ b/docs/tutorials.md
@@ -0,0 +1,254 @@
+## Creating a QUIC-Fire Simulation with quicfire-tools.
+
+Welcome to the tutorial on how to create a QUIC-Fire simulation using quicfire-tools, a Python package designed to
+streamline the process of managing QUIC-Fire input file decks and processing output files. This tutorial will take you
+through the necessary steps to set up a basic QUIC-Fire simulation, focusing on using the SimulationInputs class from
+the inputs module.
+
+### Prerequisites
+
+Before starting this tutorial, ensure that you have:
+
+- Python 3.8 or higher installed on your system.
+- The quicfire-tools package installed on your system. If you do not have quicfire-tools installed, please see the
+  [installation instructions](index.md#installation) in the documentation.
+
+### Step 1: Import the SimulationInputs class
+
+Start by importing the SimulationInputs class from the quicfire_tools.inputs module:
+
+```python
+from quicfire_tools.inputs import SimulationInputs
+```
+
+### Step 2: Creating a Uniform Line Fire Simulation
+
+In this step, we will create a function to set up a basic QUIC-Fire simulation. This involves initializing the
+simulation, defining fuel characteristics, setting the ignition area, and specifying output files.
+
+#### Step 2.1: Initialize the simulation
+
+First, we initialize the simulation with the grid size, simulation time, and wind conditions. The
+[`create_simulation`](reference.md#quicfire_tools.inputs.SimulationInputs.create_simulation) method of
+[`SimulationInputs`](reference.md#quicfire_tools.inputs.SimulationInputs) is used for this purpose.
+
+```python
+simulation = SimulationInputs.create_simulation(
+    nx=200,
+    ny=200,
+    fire_nz=1,
+    wind_speed=1.7,
+    wind_direction=90,
+    simulation_time=600
+)
+```
+
+- **nx** and **ny** define the number of cells in the x and y axis of the simulation grid.
+- **fire_nz** determines the number of cells in the z-axis for the fire grid.
+- **wind_speed** and **wind_direction** set the initial wind conditions.
+- **simulation_time** specifies how long the simulation will run in seconds.
+
+#### Step 2.2: Define Uniform Fuel Characteristics
+
+Next, we define the characteristics of the fuel. The [`set_uniform_fuels`](reference.md#quicfire_tools.inputs.SimulationInputs.set_uniform_fuels)
+method sets the fuel density, moisture, and height uniformly across the simulation grid.
+
+```python
+simulation.set_uniform_fuels(
+    fuel_density=0.7,
+    fuel_moisture=0.10,
+    fuel_height=1.0
+)
+```
+
+- **fuel_density** sets the surface fuel density in kg/m^3.
+- **fuel_moisture** sets the surface fuel moisture content as a fraction of the fuel's dry weight.
+- **fuel_height** sets the surface fuel height in meters.
+
+#### Step 2.3: Set the Ignition Area
+
+Now, we specify the area where the fire will start. The [`set_rectangle_ignition`](reference.md#quicfire_tools.inputs.SimulationInputs.set_rectangle_ignition)
+method is used to create a rectangular ignition zone simulating a line fire ignition pattern.
+
+```python
+simulation.set_rectangle_ignition(
+    x_min=150,
+    y_min=100,
+    x_length=10,
+    y_length=100
+)
+```
+
+- **x_min** and **y_min** set the coordinates of the bottom left corner of the ignition zone. These coordinates are specified in meters, not grid cells.
+- **x_length** and **y_length** set the length of the ignition zone in the x and y directions in meters.
+
+#### Step 2.4: Specify Output Files
+
+Finally, we specify the types of output files the simulation will generate.
+The [`set_output_files`](reference.md#quicfire_tools.inputs.SimulationInputs.set_output_files)
+method allows you to choose which data to output.
+
+```python
+simulation.set_output_files(
+    fuel_dens=True,
+    emissions=True,
+    qu_wind_inst=True
+)
+```
+
+In this example:
+- **fuel_dens** specifies a compressed array fuel density output.
+- **emissions** specifies CO, PM2.5, and water emissions outputs.
+- **qu_wind_inst** specifies gridded wind components (u, v, w) for the QUIC (wind) grid.
+
+### Step 3: Write the Simulation to a Directory
+
+Now that we have created the simulation, we can write it to a directory containing a QUIC-Fire executable to run the
+fire model.
+The [`write_simulation`](reference.md#quicfire_tools.inputs.SimulationInputs.write_simulation)
+method of is used for this purpose.
+
+```python
+simulation.write_inputs("path/to/directory")
+```
+
+### Finished Code
+
+The following code block combines all the steps in this tutorial to create a basic QUIC-Fire simulation using
+quicfire-tools.
+
+```python
+from quicfire_tools.inputs import SimulationInputs
+
+simulation = SimulationInputs.create_simulation(
+    nx=200,
+    ny=200,
+    fire_nz=1,
+    wind_speed=1.7,
+    wind_direction=90,
+    simulation_time=600
+)
+
+simulation.set_uniform_fuels(
+    fuel_density=0.7,
+    fuel_moisture=0.10,
+    fuel_height=1.0
+)
+
+simulation.set_rectangle_ignition(
+    x_min=150,
+    y_min=100,
+    x_length=10,
+    y_length=100
+)
+
+simulation.set_output_files(
+    fuel_dens=True,
+    emissions=True,
+    qu_wind_inst=True
+)
+
+simulation.write_inputs("path/to/directory")
+```
+
+### Conclusion
+
+In this tutorial, we have learned how to create a basic QUIC-Fire simulation using the SimulationInputs class from the
+inputs module. We have also learned how to write the simulation to a directory containing a QUIC-Fire executable to run
+the fire model.
+
+quicfire-tools is designed to set up simulations in a quick, easy, and repeatable manner. The scope of the inputs module
+ends at writing the simulation to a directory. Running a simulation and understanding the relevant inputs are the
+responsibility of the user.
+
+For more information about setting up a QUIC-Fire simulation, please see the [How-to-Guide](how-to-guides.md)
+for more examples of working with the inputs module,
+and the [Reference](reference.md#quicfire_tools.inputs) page for a complete list of available methods and attributes.
+
+## Reading QUIC-Fire Output Files with quicfire-tools.
+
+In this tutorial, we will learn how to read QUIC-Fire output files using quicfire-tools, a Python package designed to
+streamline the process of managing QUIC-Fire input file decks and processing output files. This tutorial will take you
+through the necessary steps to read a QUIC-Fire output file into a numpy array, focusing on using the OutputFile class
+from the outputs module.
+
+### Prerequisites
+
+Before starting this tutorial, ensure that you have:
+
+- Python 3.8 or higher installed on your system.
+- The quicfire-tools package installed on your system. If you do not have quicfire-tools installed, please see the
+  [installation instructions](index.md#installation) in the documentation.
+- An Outputs directory containing the output files from a QUIC-Fire simulation. quicfire-tools does not handle running
+  QUIC-Fire simulations and it is the responsibility of the user to run the fire model and understand the relevant
+  inputs.
+
+### Step 1: Import the necessary modules
+
+Start by importing the SimulationInputs class from the quicfire_tools.outputs module:
+
+```python
+from quicfire_tools.outputs import SimulationOutputs
+```
+
+### Step 2: Create a SimulationOutputs object
+
+Next, create a SimulationOutputs object. The SimulationOutputs class is used to read and process QUIC-Fire outputs.
+You can create a SimulationOutputs object by passing the path to the Outputs directory containing the output files to
+the constructor.
+
+In addition to the path to the Outputs directory, you also need to specify the number of cells in the z, y, and x
+directions of the simulation grid. This information is used to reshape the output files into a 3D numpy array.
+
+```python
+output_directory = "/path/to/output/directory"
+nz = 56  # number of z cells
+ny = 100  # number of y cells
+nx = 100  # number of x cells
+
+simulation_outputs = SimulationOutputs(output_directory, nz, ny, nx)
+```
+
+### Step 3: Get an Output File
+
+You can use the SimulationOutputs object to get the `OutputFile` object for the output that you want to read data from.
+To do this, use the [`get_output_file`](reference.md#quicfire_tools.outputs.SimulationOutputs.get_output_file)
+method of the SimulationOutputs class.
+
+```python
+output_name = "fire-energy_to_atmos"  # replace with the name of the output you are interested in
+output_file = simulation_outputs.get_output(output_name)
+```
+
+### Step 4: Get the output data as a numpy array
+
+Finally, you can get a numpy array for the output data using the
+[`to_numpy`](reference.md#quicfire_tools.outputs.OutputFile.to_numpy) method of the `OutputFile` instance. You can
+specify the timestep(s) you are interested in. If you don't provide a timestep, all timesteps will be returned:
+
+```python
+timestep = 0  # replace with the timestep you are interested in
+output_data = output_file.to_numpy(timestep)
+```
+
+### Finished Code
+
+The following code block combines all the steps in this tutorial to read a QUIC-Fire output file into a numpy array
+using quicfire-tools.
+
+```python
+from quicfire_tools.outputs import SimulationOutputs
+
+output_directory = "/path/to/output/directory"
+nz = 56  # number of z cells
+ny = 100  # number of y cells
+nx = 100  # number of x cells
+
+simulation_outputs = SimulationOutputs(output_directory, nz, ny, nx)
+
+output_name = "fire-energy_to_atmos"  # replace with the name of the output you are interested in
+output_file = simulation_outputs.get_output(output_name)
+
+timestep = 0  # replace with the timestep you are interested in
+output_data = output_file.to_numpy(timestep)
+```
diff --git a/mkdocs.yml b/mkdocs.yml
index ffc98de..63e7ce7 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -3,12 +3,18 @@ site_name: quicfire-tools Documentation
 
 theme:
   name: "material"
+  features:
+    - content.code.copy
+
+markdown_extensions:
+  - pymdownx.highlight
+  - pymdownx.superfences
 
 nav:
   - Home: index.md
-  # - Explanation: explanation.md
-  # - Tutorials: tutorials.md
-  # - How-To Guides: how-to-guides.md
+  - Explanation: explanation.md
+  - Tutorials: tutorials.md
+  - How-To Guides: how-to-guides.md
   - Reference: reference.md
 
 repo_url: https://github.com/silvxlabs/quicfire-tools
diff --git a/quicfire_tools/examples.py b/quicfire_tools/examples.py
index b9fcf0d..03cb9ae 100644
--- a/quicfire_tools/examples.py
+++ b/quicfire_tools/examples.py
@@ -9,22 +9,22 @@ def create_line_fire() -> SimulationInputs:
         nx=200,
         ny=200,
         fire_nz=1,
-        simulation_time=60,
+        simulation_time=600,
         wind_speed=1.7,
         wind_direction=270,
     )
 
     # Set uniform fuel values
     sim_inputs.set_uniform_fuels(
-        fuel_density=0.5,
+        fuel_density=0.7,
         fuel_moisture=0.10,
         fuel_height=1.0,
     )
 
     # Set a line ignitions
     sim_inputs.set_rectangle_ignition(
-        x_min=150,
-        y_min=100,
+        x_min=10,
+        y_min=50,
         x_length=10,
         y_length=100,
     )
@@ -32,21 +32,17 @@ def create_line_fire() -> SimulationInputs:
     # Select which binary files to output
     sim_inputs.set_output_files(
         fuel_dens=True,
-        fuel_moist=True,
-        emissions=True,
+        mass_burnt=True,
+        qf_wind=True,
     )
 
-    # Make modifications to the QUIC_Fire input file
-    sim_inputs.quic_fire.random_seed = 222
-    sim_inputs.quic_fire.time_now = 1653321600
-    sim_inputs.quic_fire.sim_time = 300
-    sim_inputs.quic_fire.out_time_fire = 50
-    sim_inputs.quic_fire.out_time_wind = 50
-    sim_inputs.quic_fire.out_time_emis_rad = 50
-    sim_inputs.quic_fire.out_time_wind_avg = 50
-    sim_inputs.quic_fire.ignitions_per_cell = 100
-
     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 2f63bf5..6966b1c 100644
--- a/quicfire_tools/ignitions.py
+++ b/quicfire_tools/ignitions.py
@@ -11,6 +11,10 @@
 
 
 class IgnitionSources(int, Enum):
+    """
+    Enum class for all valid ignition source options in QUIC-Fire.
+    """
+
     rectangle = 1
     square_ring = 2
     circular_ring = 3
@@ -19,7 +23,8 @@ class IgnitionSources(int, Enum):
 
 class IgnitionType(BaseModel):
     """
-    Test docs
+    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
@@ -38,7 +43,7 @@ class RectangleIgnition(IgnitionType):
     """
     Represents a rectangle ignition source in QUIC-Fire.
 
-    Parameters
+    Attributes
     ----------
     x_min : float
         South-west corner in the x-direction [m]
@@ -51,10 +56,10 @@ class RectangleIgnition(IgnitionType):
     """
 
     ignition_flag: IgnitionSources = IgnitionSources(1)
-    x_min: int
-    y_min: int
-    x_length: int
-    y_length: int
+    x_min: float
+    y_min: float
+    x_length: float
+    y_length: float
 
     def __str__(self):
         flag_line = super().__str__()
@@ -71,7 +76,7 @@ class SquareRingIgnition(IgnitionType):
     """
     Represents a square ring ignition source in QUIC-Fire.
 
-    Parameters
+    Attributes
     ----------
     x_min : float
         South-west corner in the x-direction [m]
@@ -88,12 +93,12 @@ class SquareRingIgnition(IgnitionType):
     """
 
     ignition_flag: IgnitionSources = IgnitionSources(2)
-    x_min: int
-    y_min: int
-    x_length: int
-    y_length: int
-    x_width: int
-    y_width: int
+    x_min: float
+    y_min: float
+    x_length: float
+    y_length: float
+    x_width: float
+    y_width: float
 
     def __str__(self):
         flag_line = super().__str__()
@@ -112,7 +117,7 @@ class CircularRingIgnition(IgnitionType):
     """
     Represents a circular ring ignition source in QUIC-Fire.
 
-    Parameters
+    Attributes
     ----------
     x_min : float
         South-west corner in the x-direction [m]
@@ -127,11 +132,11 @@ class CircularRingIgnition(IgnitionType):
     """
 
     ignition_flag: IgnitionSources = IgnitionSources(3)
-    x_min: int
-    y_min: int
-    x_length: int
-    y_length: int
-    ring_width: int
+    x_min: float
+    y_min: float
+    x_length: float
+    y_length: float
+    ring_width: float
 
     def __str__(self):
         flag_line = super().__str__()
diff --git a/quicfire_tools/inputs.py b/quicfire_tools/inputs.py
index 7c193ad..2386e83 100644
--- a/quicfire_tools/inputs.py
+++ b/quicfire_tools/inputs.py
@@ -60,11 +60,9 @@
 
 class SimulationInputs:
     """
-    Class representing a QUIC-Fire input file deck.
-
-    This is the fundamental class in the quicfire_tools.data module. It is
-    used to create, modify, and write QUIC-Fire input file decks. It is also
-    used to read in existing QUIC-Fire input file decks.
+    Class representing a QUIC-Fire input file deck. This class is the primary
+    interface for building a QUIC-Fire input file deck and saving the input
+    files to a directory for running a simulation.
 
     Attributes
     ----------
@@ -163,32 +161,40 @@ def create_simulation(
         wind_speed: float,
         wind_direction: int,
         simulation_time: int,
-    ):
+    ) -> SimulationInputs:
         """
-        Creates a SimulationInputs object to build a QUIC-Fire input file deck
-        and run a simulation.
+        Creates a SimulationInputs object by taking in the mimum required
+        information to build a QUIC-Fire input file deck. Returns a
+        SimulationInputs object representing the complete state of the
+        QUIC-Fire simulation.
 
         Parameters
         ----------
         nx: int
-            Number of cells in the x-direction [-]
+            Number of cells in the x-direction [-]. Default cell size is 2m.
         ny: int
-            Number of cells in the y-direction [-]
+            Number of cells in the y-direction [-]. Default cell size is 2m.
         fire_nz: int
-            Number of cells in the z-direction for the fire grid [-]
+            Number of cells in the z-direction for the fire grid [-]. Default
+            cell size is 1m.
         wind_speed: float
-            Wind speed [m/s]
+            Wind speed [m/s].
         wind_direction: float
             Wind direction [deg]. 0 deg is north, 90 deg is east, etc. Must
             be in range [0, 360).
         simulation_time: int
-            Number of seconds to run the simulation for [s]
+            Number of seconds to run the simulation for [s].
 
         Returns
         -------
         SimulationInputs
-            Class containing the data to build a QUIC-Fire
-            input file deck and run a simulation using default parameters.
+            Class containing the data to build a QUIC-Fire input file deck and
+            run a simulation using default parameters.
+
+        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)
         """
         # Initialize default input files
         rasterorigin = RasterOrigin()
@@ -241,7 +247,10 @@ def create_simulation(
     def from_directory(cls, directory: str | Path) -> SimulationInputs:
         """
         Initializes a SimulationInputs object from a directory containing a
-        QUIC-Fire input file deck.
+        QUIC-Fire input file deck. The function looks for each input file in the
+        QUIC-Fire input file deck, reads in the file to an object, and compiles
+        the objects to a SimulationInputs object that represents the complete
+        state of the QUIC-Fire simulation.
 
         Parameters
         ----------
@@ -252,6 +261,12 @@ def from_directory(cls, directory: str | Path) -> SimulationInputs:
         -------
         SimulationInputs
             Class containing the input files in the QUIC-Fire input file deck.
+
+        Examples
+        --------
+        >>> from quicfire_tools import SimulationInputs
+        >>> simulation_path = "path/to/simulation/directory"
+        >>> sim_inputs = SimulationInputs.from_directory(simulation_path)
         """
         if isinstance(directory, str):
             directory = Path(directory)
@@ -280,13 +295,20 @@ def from_directory(cls, directory: str | Path) -> SimulationInputs:
     @classmethod
     def from_dict(cls, data: dict) -> SimulationInputs:
         """
-        Initializes a SimulationInputs object from a dictionary containing
-        input file data.
+        Initializes a SimulationInputs object from a dictionary.
 
         Parameters
         ----------
         data: dict
             Dictionary containing input file data.
+
+        Examples
+        --------
+        >>> from quicfire_tools import SimulationInputs
+        >>> json_path = "path/to/json/object"
+        >>> sim_inputs = SimulationInputs.from_json(json_path)
+        >>> sim_dict = sim_inputs.to_dict()
+        >>> new_sim_inputs = SimulationInputs.from_dict(sim_dict)
         """
         return cls(
             rasterorigin=RasterOrigin.from_dict(data["rasterorigin"]),
@@ -323,6 +345,12 @@ def from_json(cls, path: str | Path) -> SimulationInputs:
         ----------
         path: str | Path
             Path to the JSON file.
+
+        Examples
+        --------
+        >>> from quicfire_tools import SimulationInputs
+        >>> json_path = "path/to/json/object"
+        >>> sim_inputs = SimulationInputs.from_json(json_path)
         """
         if isinstance(path, str):
             path = Path(path)
@@ -335,7 +363,7 @@ def write_inputs(self, directory: str | Path, version: str = "latest") -> None:
         Write all input files in the SimulationInputs object to a specified
         directory.
 
-        This method is the core method of the SimulationInputs class. It
+        This method is a core method of the SimulationInputs class. It
         is the principle way to translate a SimulationInputs object into a
         QUIC-Fire input file deck.
 
@@ -345,6 +373,12 @@ def write_inputs(self, directory: str | Path, version: str = "latest") -> None:
             Directory to write the input files to.
         version: str
             Version of the input files to write. Default is "latest".
+
+        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.write_inputs("path/to/simulation/directory")
         """
         if isinstance(directory, str):
             directory = Path(directory)
@@ -372,25 +406,37 @@ def write_inputs(self, directory: str | Path, version: str = "latest") -> None:
 
     def to_dict(self) -> dict:
         """
-        Convert the object to a dictionary representation. The SimulationInputs
-        object is represented as a nest dictionary, with the name of each
-        input file as a key to that input file's dictionary representation.
+        Convert the state of the SimulationInputs object to a dictionary.
+        The name of each input file in the SimulationInputs object is a key
+        to that input file's dictionary form.
 
-        Returns:
-        --------
+        Returns
+        -------
         dict
             Dictionary representation of the object.
+
+        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_dict = sim_inputs.to_dict()
         """
         return {key: value.to_dict() for key, value in self._input_files_dict.items()}
 
-    def to_json(self, path: str | Path):
+    def to_json(self, path: str | Path) -> None:
         """
-        Write the object to a JSON file.
+        Write the SimulationInputs object to a JSON file.
 
         Parameters
         ----------
         path : str | Path
             Path to write the JSON file to.
+
+        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.to_json("path/to/json/object")
         """
         if isinstance(path, str):
             path = Path(path)
@@ -402,7 +448,34 @@ def set_custom_simulation(
         fuel: bool = True,
         ignition: bool = True,
         topo: bool = True,
-    ):
+    ) -> None:
+        """
+        Sets the simulation to use custom fuel, ignition, and topography
+        settings.
+
+        This function can be useful for setting up simulations that use .dat
+        files to define custom fuel, topography, or ignition inputs.
+
+        Parameters
+        ----------
+        fuel : bool, optional
+            If True, sets the simulation to use custom fuel settings
+            (fuel flag 3). Default is True.
+        ignition : bool, optional
+            If True, sets the simulation to use a custom ignition source
+            (ignition flag 6). Default is True.
+        topo : bool, optional
+            If True, sets the simulation to use custom topography settings
+            (topography flag 5). Default is True.
+
+        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_custom_simulation(fuel=True, ignition=True, topo=True)
+        >>> sim_inputs.quic_fire.fuel_flag
+        3
+        """
         if fuel:
             self.quic_fire.fuel_flag = 3
             self.quic_fire.fuel_density = None
@@ -421,14 +494,61 @@ def set_uniform_fuels(
         fuel_moisture: float,
         fuel_height: float,
     ):
+        """
+        Sets the simulation to use uniform fuel settings. This function updates
+        the fuel flag to 1 and sets the fuel density, fuel moisture, and fuel
+        height to the specified values.
+
+        Parameters
+        ----------
+        fuel_density: float
+            Fuel bulk density [kg/m^3]. Note: This is the fuel bulk density, so
+            the fuel load should be normalized by the height of the fuel bed.
+        fuel_moisture: float
+            Fuel moisture content [%].
+        fuel_height: float
+            Fuel bed height [m].
+
+        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_uniform_fuels(fuel_density=0.5, fuel_moisture=25, fuel_height=1)
+        >>> sim_inputs.quic_fire.fuel_flag
+        1
+        >>> sim_inputs.quic_fire.fuel_density
+        0.5
+        """
         self.quic_fire.fuel_flag = 1
         self.quic_fire.fuel_density = fuel_density
         self.quic_fire.fuel_moisture = fuel_moisture
         self.quic_fire.fuel_height = fuel_height
 
     def set_rectangle_ignition(
-        self, x_min: int, y_min: int, x_length: int, y_length: int
-    ):
+        self, x_min: float, y_min: float, x_length: float, y_length: float
+    ) -> None:
+        """
+        Sets the simulation to use a rectangle ignition source. This function
+        updates the ignition flag to 1 and sets the ignition source to the
+        specified rectangle.
+
+        Parameters
+        ----------
+        x_min: float
+            South-west corner in the x-direction [m]
+        y_min: float
+            South-west corner in the y-direction [m]
+        x_length: float
+            Length in the x-direction [m]
+        y_length: float
+            Length in the y-direction [m]
+
+        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_rectangle_ignition(x_min=0, y_min=0, x_length=10, y_length=10)
+        """
         ignition = RectangleIgnition(
             x_min=x_min, y_min=y_min, x_length=x_length, y_length=y_length
         )
@@ -447,7 +567,48 @@ def set_output_files(
         emissions: bool = False,
         radiation: bool = False,
         surf_eng: bool = False,
-    ):
+        intensity: bool = False,
+    ) -> None:
+        """
+        Sets the simulation to output the specified files. Files set to True
+        will be output by the simulation, and files set to False will not be
+        output.
+
+        Parameters
+        ----------
+        eng_to_atm: bool, optional
+            If True, output the fire-energy_to_atmos.bin file. Default is False.
+        react_rate: bool, optional
+            If True, output the fire-reaction_rate.bin file. Default is False.
+        fuel_dens: bool, optional
+            If True, output the fuels-dens.bin file. Default is False.
+        qf_wind: bool, optional
+            If True, output the windu, windv, and windw .bin files.
+            Default is False.
+        qu_wind_inst: bool, optional
+            If True, output the quic_wind_inst.bin file. Default is False.
+        qu_wind_avg: bool, optional
+            If True, output the quic_wind_avg.bin file. Default is False.
+        fuel_moist: bool, optional
+            If True, output the fuels-moist.bin file. Default is False.
+        mass_burnt: bool, optional
+            If True, output the mburnt_integ.bin file. Default is False.
+        emissions: bool, optional
+            If True, output the co-emissions and pm-emissions .bin files.
+            Default is False.
+        radiation: bool, optional
+            If True, output the thermaldose and thermalradiation .bin files.
+            Default is False.
+        intensity: bool, optional
+            If True, output the fire-intensity.bin file. Default is False.
+
+        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_files(fuel_dens=True, mass_burnt=True)
+        """
+
         self.quic_fire.eng_to_atm_out = int(eng_to_atm)
         self.quic_fire.react_rate_out = int(react_rate)
         self.quic_fire.fuel_dens_out = int(fuel_dens)
@@ -490,43 +651,42 @@ class InputFile(BaseModel, validate_assignment=True):
 
     This base class provides a common interface for all input files in order to
     accomplish two main goals:
+
     1) Return documentation for each parameter in the input file.
-    2) Provide a method to write the input file to a specified directory.
+
+    2) Provide a method to write the input file to a directory.
     """
 
     name: str
     _extension: str
-    _param_info: dict = None
 
     @property
     def _filename(self):
         return f"{self.name}{self._extension}"
 
     @property
-    def param_info(self):
+    def documentation_dict(self) -> dict:
+        # Return the documentation dictionary
+        with open(DOCS_PATH / f"{self._filename}.json", "r") as f:
+            return json.load(f)
+
+    def list_parameters(self) -> list[str]:
         """
-        Return a dictionary of parameter information for the input file.
+        Get a list of the names of all parameters in the input file.
         """
-        if self._param_info is None:  # open the file if it hasn't been read in
-            with open(DOCS_PATH / f"{self._filename}.json", "r") as f:
-                self._param_info = json.load(f)
-        return self._param_info
-
-    def list_parameters(self):
-        """List all parameters in the input file."""
-        return list(self.param_info.keys())
+        return list(self.documentation_dict.keys())
 
-    def get_documentation(self, parameter: str = None):
+    def get_documentation(self, parameter: str = None) -> dict:
         """
         Retrieve documentation for a parameter. If no parameter is specified,
         return documentation for all parameters.
         """
         if parameter:
-            return self.param_info.get(parameter, {})
+            return self.documentation_dict.get(parameter, {})
         else:
-            return self.param_info
+            return self.documentation_dict
 
-    def print_documentation(self, parameter: str = None):
+    def print_documentation_table(self, parameter: str = None) -> None:
         """
         Print documentation for a parameter. If no parameter is specified,
         print documentation for all parameters.
@@ -539,7 +699,7 @@ def print_documentation(self, parameter: str = None):
             key = key.replace("_", " ").capitalize()
             print(f"- {key}: {value}")
 
-    def to_dict(self, include_private: bool = False):
+    def to_dict(self, include_private: bool = False) -> dict:
         """
         Convert the object to a dictionary, excluding attributes that start
         with an underscore.
@@ -550,7 +710,7 @@ def to_dict(self, include_private: bool = False):
             Dictionary representation of the object.
         """
         all_fields = self.model_dump(
-            exclude={"name", "_extension", "_filename", "param_info"}
+            exclude={"name", "_extension", "_filename", "documentation_dict"}
         )
         if include_private:
             return all_fields
@@ -772,6 +932,8 @@ class QU_Simparams(InputFile):
     Class representing the QU_simparams.inp file. This file contains the
     simulation parameters for the QUIC-Fire simulation.
 
+    Attributes
+    ----------
     nx : int
         Number of cells in the x-direction [-]. Recommended value: > 100
     ny : int
@@ -1239,7 +1401,7 @@ class QUIC_fire(InputFile):
     contains the parameters relating to the fire simulation and
     outputs.
 
-    Parameters
+    Attributes
     ----------
     fire_flag : Literal[0, 1]
         Fire flag, 1 = run fire; 0 = no fire
@@ -1368,8 +1530,8 @@ class QUIC_fire(InputFile):
     eng_to_atm_out: Literal[0, 1] = 0
     react_rate_out: Literal[0, 1] = 0
     fuel_dens_out: Literal[0, 1] = 1
-    qf_wind_out: Literal[0, 1] = 1
-    qu_wind_inst_out: Literal[0, 1] = 0
+    qf_wind_out: Literal[0, 1] = 0
+    qu_wind_inst_out: Literal[0, 1] = 1
     qu_wind_avg_out: Literal[0, 1] = 0
     fuel_moist_out: Literal[0, 1] = 0
     mass_burnt_out: Literal[0, 1] = 0
@@ -1532,7 +1694,8 @@ def from_file(cls, directory: str | Path):
         ignition_params = []
         current_line += 1
         for i in range(current_line, current_line + add):
-            ignition_params.append(int(lines[i].strip().split("!")[0]))
+            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(
@@ -1804,6 +1967,8 @@ class QU_TopoInputs(InputFile):
     Class representing the QU_TopoInputs.inp input file. This file
     contains advanced data pertaining to topography.
 
+    Attributes
+    ----------
     filename : str
         Path to the custom topo file (only used with option 5). Cannot be .bin. Use .dat or .inp
     topo_type : TopoType
diff --git a/quicfire_tools/outputs.py b/quicfire_tools/outputs.py
index fa9b73a..027abb3 100644
--- a/quicfire_tools/outputs.py
+++ b/quicfire_tools/outputs.py
@@ -12,6 +12,7 @@
 import numpy as np
 import dask.array as da
 from numpy import ndarray
+from shutil import rmtree
 
 FUELS_OUTPUTS = {
     "fire-energy_to_atmos": {
@@ -70,6 +71,16 @@
         "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": {
@@ -153,6 +164,35 @@
 
 
 class OutputFile:
+    """
+    A class representing a single output file. This class provides a common
+    interface for processing and retrieving data from output files.
+
+    Attributes
+    ----------
+    name: str
+        The name of the output file this object represents.
+    file_format: str
+        The format of the output file. Valid options are "gridded" and
+        "compressed".
+    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).
+    grid: str
+        The grid type of the output file. Valid options are "fire" and "quic".
+    delimiter: str
+        The delimiter used in the output file name.
+    extension: str
+        The file extension of the output file.
+    description: str
+        A description of the output file.
+    units: str
+        The units of the output file.
+    times: list[int]
+        A list of times corresponding to the timesteps.
+    """
+
     def __init__(
         self,
         name: str,
@@ -489,10 +529,12 @@ def to_numpy(self, key: str, timestep: None | int | list[int] = None) -> np.ndar
         >>> import quicfire_tools as qft
         >>> outputs = qft.SimulationOutputs("path/to/outputs", 50, 100, 100)
         >>> output_name = "fire-energy_to_atmos"
+        >>>
         >>> # Get all timesteps for the fire-energy_to_atmos output
         >>> fire_energy_all = outputs.to_numpy(output_name)
+        >>>
         >>> # Get the first timestep for the fire-energy_to_atmos output
-        >>> fire_energy_slice = outputs.to_numpy(output_name, timestep=0)
+        >>> fire_energy_first_time_step = outputs.to_numpy(output_name, timestep=0)
         """
         output = self._validate_output(key)
         return output.to_numpy(timestep)
@@ -518,8 +560,8 @@ def to_dask(self, key: str) -> da.Array:
         --------
         >>> import quicfire_tools as qft
         >>> outputs = qft.SimulationOutputs("path/to/outputs", 50, 100, 100)
-        >>> fire_energy = outputs.to_dask("fire-energy_to_atmos")
-        >>> fire_energy.compute()
+        >>> fire_energy_da = outputs.to_dask("fire-energy_to_atmos")
+        >>> fire_energy_np = fire_energy_da.compute()  # Retrieve the data
         """
         output = self._validate_output(key)
 
@@ -535,30 +577,42 @@ 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
-    ) -> zarr.hierarchy.Group:
+        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 zarr file to be written.
+            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
 
-        Returns
+        Builds
         -------
-        zarr.hierarchy.Group
-            The zarr file object.
+        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)
 
-        # Create the zarr file
-        zarr_file = zarr.open(str(fpath), mode="w")
+        if not fpath.exists():
+            fpath.mkdir(parents=True)
+
+        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:
@@ -571,20 +625,151 @@ def to_zarr(
             # 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, ...]
+            # 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
+                )
 
-        return zarr_file
+                # 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
+
+    # #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
 
 
 def _process_compressed_bin(filename, dim_zyx, *args) -> ndarray:
diff --git a/quicfire_tools/topography.py b/quicfire_tools/topography.py
index 5e9a05b..4b813cf 100644
--- a/quicfire_tools/topography.py
+++ b/quicfire_tools/topography.py
@@ -9,10 +9,14 @@
 # External Imports
 from typing import Literal
 
-from pydantic import BaseModel, Field, PositiveFloat, PositiveInt, SerializeAsAny
+from pydantic import BaseModel, Field, PositiveFloat, SerializeAsAny
 
 
 class TopoSources(int, Enum):
+    """
+    Enum class for all valid topography source options in QUIC-Fire.
+    """
+
     flat = 0
     gaussian_hill = 1
     hill_pass = 2
@@ -28,6 +32,11 @@ class TopoSources(int, Enum):
 
 
 class TopoType(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]
 
     def __str__(self):
@@ -43,10 +52,25 @@ def __str__(self):
 
 
 class GaussianHillTopo(TopoType):
+    """
+    Creates a Gaussian hill topography in QUIC-Fire.
+
+    Attributes
+    ----------
+    x_hilltop : PositiveFloat
+        Gaussian hill top location x-direction [m]
+    y_hilltop : PositiveFloat
+        Gaussian hill top location y-direction [m]
+    elevation_max : PositiveFloat
+        Maximum elevation of the Gaussian hill [m]
+    elevation_std : PositiveFloat
+        Standard deviation of the Gaussian hill [m]
+    """
+
     topo_flag: SerializeAsAny[TopoSources] = TopoSources(1)
-    x_hilltop: PositiveInt
-    y_hilltop: PositiveInt
-    elevation_max: PositiveInt
+    x_hilltop: PositiveFloat
+    y_hilltop: PositiveFloat
+    elevation_max: PositiveFloat
     elevation_std: PositiveFloat
 
     def __str__(self):
@@ -61,8 +85,19 @@ def __str__(self):
 
 
 class HillPassTopo(TopoType):
+    """
+    Creates a hill pass topography in QUIC-Fire.
+
+    Attributes
+    ----------
+    max_height : PositiveFloat
+        Maximum elevation of the hill pass [m]
+    location_param : PositiveFloat
+        Location parameter of the hill pass [m]
+    """
+
     topo_flag: SerializeAsAny[TopoSources] = TopoSources(2)
-    max_height: PositiveInt
+    max_height: PositiveFloat
     location_param: PositiveFloat
 
     def __str__(self):
@@ -75,6 +110,17 @@ def __str__(self):
 
 
 class SlopeMesaTopo(TopoType):
+    """
+    Creates a slope mesa topography in QUIC-Fire.
+
+    Attributes
+    ----------
+    slope_axis : Literal[0, 1]
+        Slope axis (0 = x, 1 = y)
+    slope_value : float
+        Slope in dh/dx or dh/dy
+    """
+
     topo_flag: SerializeAsAny[TopoSources] = TopoSources(3)
     slope_axis: Literal[0, 1]
     slope_value: PositiveFloat
@@ -91,9 +137,26 @@ def __str__(self):
 
 
 class CanyonTopo(TopoType):
+    """
+    Creates a canyon topography in QUIC-Fire.
+
+    Attributes
+    ----------
+    x_location: PositiveFloat
+        Canyon location in x-dir [m].
+    y_location: PositiveFloat
+        Canyon location in y-dir [m].
+    slope_value: PositiveFloat
+        Slope in dh/dx or dy/dy [-].
+    canyon_std: PositiveFloat
+        Canyon function standard deviation [m].
+    vertical_offset: PositiveFloat
+        Canyon vertical offset [m].
+    """
+
     topo_flag: SerializeAsAny[TopoSources] = TopoSources(4)
-    x_start: PositiveInt
-    y_center: PositiveInt
+    x_location: PositiveFloat
+    y_location: PositiveFloat
     slope_value: PositiveFloat
     canyon_std: PositiveFloat
     vertical_offset: PositiveFloat
@@ -101,8 +164,8 @@ class CanyonTopo(TopoType):
     def __str__(self):
         flag_line = super().__str__()
         params = (
-            f"\n{self.x_start}\t! m, x-start of canyon on slope\n"
-            f"{self.y_center}\t! m, y-center of canyon"
+            f"\n{self.x_location}\t! m, x-start of canyon on slope\n"
+            f"{self.y_location}\t! m, y-center of canyon"
             f"{self.slope_value}\t! N/A, slope\n"
             f"{self.canyon_std}\t! m, std\n"
             f"{self.vertical_offset}\t! m, height offset of the bottom of the canyon"
@@ -111,9 +174,22 @@ def __str__(self):
 
 
 class HalfCircleTopo(TopoType):
+    """
+    Creates a half-circle topography in QUIC-Fire.
+
+    Attributes
+    ----------
+    x_location: PositiveFloat
+        The x-coordinate of the center of the half-circle topography [m].
+    y_location: PositiveFloat
+        The y-coordinate of the center of the half-circle topography [m].
+    radius: PositiveFloat
+        The radius of the half-circle topography [m].
+    """
+
     topo_flag: SerializeAsAny[TopoSources] = TopoSources(6)
-    x_location: PositiveInt
-    y_location: PositiveInt
+    x_location: PositiveFloat
+    y_location: PositiveFloat
     radius: PositiveFloat
 
     def __str__(self):
@@ -127,22 +203,44 @@ def __str__(self):
 
 
 class SinusoidTopo(TopoType):
+    """
+    Creates a sinusoidal topography in QUIC-Fire.
+
+    Attributes
+    ----------
+    period: PositiveFloat
+        The period of the sinusoidal wave [m].
+    amplitude: PositiveFloat
+        The amplitude of the sinusoidal wave [m].
+    """
+
     topo_flag: SerializeAsAny[TopoSources] = TopoSources(7)
     period: PositiveFloat
     amplitude: PositiveFloat
 
     def __str__(self):
         flag_line = super().__str__()
-        params = f"\n{self.period}\t! m, mean\n" f"{self.amplitude}\t! m, amplitude"
+        params = f"\n{self.period}\t! m, period\n" f"{self.amplitude}\t! m, amplitude"
         return flag_line + params
 
 
 class CosHillTopo(TopoType):
+    """
+    Creates a cosine-shaped hill topography in QUIC-Fire.
+
+    Attributes
+    ----------
+    aspect: PositiveFloat
+        The aspect (or orientation) of the hill [-].
+    height: PositiveFloat
+        The height of the hill [m].
+    """
+
     topo_flag: SerializeAsAny[TopoSources] = TopoSources(8)
     aspect: PositiveFloat
-    height: PositiveInt
+    height: PositiveFloat
 
     def __str__(self):
         flag_line = super().__str__()
-        params = f"\n{self.aspect}\t! m, mean\n" f"{self.height}\t! m, height"
+        params = f"\n{self.aspect}\t! [0], aspect\n" f"{self.height}\t! m, height"
         return flag_line + params


Test patch:
diff --git a/tests/build_simulations_for_outputs_tests.py b/tests/build_simulations_for_outputs_tests.py
new file mode 100644
index 0000000..3b14f1e
--- /dev/null
+++ b/tests/build_simulations_for_outputs_tests.py
@@ -0,0 +1,17 @@
+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/test-output/fire-energy_to_atmos-00000.bin b/tests/data/test-output/fire-energy_to_atmos-00000.bin
deleted file mode 100644
index 49d83f0..0000000
Binary files a/tests/data/test-output/fire-energy_to_atmos-00000.bin and /dev/null differ
diff --git a/tests/data/test-output/fire-energy_to_atmos-00100.bin b/tests/data/test-output/fire-energy_to_atmos-00100.bin
deleted file mode 100644
index 1348793..0000000
Binary files a/tests/data/test-output/fire-energy_to_atmos-00100.bin and /dev/null differ
diff --git a/tests/data/test-output/fire_indexes.bin b/tests/data/test-output/fire_indexes.bin
deleted file mode 100644
index 12ce336..0000000
Binary files a/tests/data/test-output/fire_indexes.bin and /dev/null differ
diff --git a/tests/data/test-output/fuels-dens-00000.bin b/tests/data/test-output/fuels-dens-00000.bin
deleted file mode 100644
index e2c7b40..0000000
Binary files a/tests/data/test-output/fuels-dens-00000.bin and /dev/null differ
diff --git a/tests/data/test-output/fuels-dens-00100.bin b/tests/data/test-output/fuels-dens-00100.bin
deleted file mode 100644
index 96e343d..0000000
Binary files a/tests/data/test-output/fuels-dens-00100.bin and /dev/null differ
diff --git a/tests/data/test-output/groundfuelheight.bin b/tests/data/test-output/groundfuelheight.bin
deleted file mode 100644
index 07bf85e..0000000
Binary files a/tests/data/test-output/groundfuelheight.bin and /dev/null differ
diff --git a/tests/data/test-output/mburnt_integ-00000.bin b/tests/data/test-output/mburnt_integ-00000.bin
deleted file mode 100644
index 21ff5a2..0000000
Binary files a/tests/data/test-output/mburnt_integ-00000.bin and /dev/null differ
diff --git a/tests/data/test-output/mburnt_integ-00100.bin b/tests/data/test-output/mburnt_integ-00100.bin
deleted file mode 100644
index 49b943d..0000000
Binary files a/tests/data/test-output/mburnt_integ-00100.bin and /dev/null differ
diff --git a/tests/test_input_module.py b/tests/test_input_module.py
index 130b9b5..6629836 100644
--- a/tests/test_input_module.py
+++ b/tests/test_input_module.py
@@ -1542,9 +1542,9 @@ def test_complex_inputs(self):
             "8 = cos hill, 9 = QP_elevation.inp, "
             "10 = terrainOutput.txt (ARA), "
             "11 = terrain.dat (firetec)\n"
-            "100\t! m, x-center\n"
-            "150\t! m, y-center\n"
-            "500\t! m, max height\n"
+            "100.0\t! m, x-center\n"
+            "150.0\t! m, y-center\n"
+            "500.0\t! m, max height\n"
             "20.0\t! m, std"
         )
         assert topoinputs.smoothing_method == 1
@@ -1646,9 +1646,11 @@ def test_to_file(self):
             11: 0,
         }
         add = add_dict.get(topo_flag)
-        assert int(lines[3].strip().split("!")[0]) == topoinputs.topo_type.x_hilltop
-        assert int(lines[4].strip().split("!")[0]) == topoinputs.topo_type.y_hilltop
-        assert int(lines[5].strip().split("!")[0]) == topoinputs.topo_type.elevation_max
+        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[5].strip().split("!")[0]) == topoinputs.topo_type.elevation_max
+        )
         assert (
             float(lines[6].strip().split("!")[0]) == topoinputs.topo_type.elevation_std
         )
diff --git a/tests/test_output_module_zarr.py b/tests/test_output_module_zarr.py
new file mode 100644
index 0000000..46f3e4b
--- /dev/null
+++ b/tests/test_output_module_zarr.py
@@ -0,0 +1,106 @@
+from __future__ import annotations
+
+import zarr
+
+# import pytest
+# import numpy as np
+import xarray as xr
+
+# import dask.array as da
+
+# from scipy.io import FortranFile
+from pathlib import Path
+import os
+from quicfire_tools import outputs
+
+
+TEST_DIR = Path(os.path.abspath(__file__)).parent
+DATA_PATH = TEST_DIR / "data" / "test-output-zarr" / "linefire_example"
+OUTPUT_PATH = DATA_PATH / "Output"
+
+
+def main():
+    tso = TestSimulationOutputs()
+    tso.test_zarr_single_output()
+    tso.test_zarr_multiple_outputs()
+    tso.test_zarr_all_outputs()
+    tso.test_zarr_xarray_connection_single_output()
+
+
+class TestSimulationOutputs:
+    nz = 1
+    ny = 200
+    nx = 200
+    sut = outputs.SimulationOutputs(OUTPUT_PATH, nz, ny, nx)
+
+    def test_zarr_single_output(self, single_output_name="fuels-dens"):
+        """
+        Run a test to ensure that the zarr output contains a single output
+        """
+        self.sut.to_zarr(
+            OUTPUT_PATH / "zarr_outputs", outputs=single_output_name, over_write=True
+        )
+
+        assert self.sut.zarr_folder_path.exists()
+        for output_name in self.sut.list_available_outputs():
+            if output_name == single_output_name:
+                output = self.sut.get_output(output_name)
+                assert output.zarr_path.exists()
+                zarr_file = zarr.open(output.zarr_path, mode="r")
+                assert isinstance(zarr_file, zarr.hierarchy.Group)
+                assert isinstance(zarr_file["data"], zarr.core.Array)
+                output_data = output.to_numpy()
+                assert zarr_file["data"].shape == output_data.shape
+
+    def test_zarr_multiple_outputs(
+        self, multiple_output_names=["mburnt_integ", "fuels-dens"]
+    ):
+        """
+        Run a test to ensure that the zarr output contains multiple outputs
+        """
+        self.sut.to_zarr(
+            OUTPUT_PATH / "zarr_outputs", outputs=multiple_output_names, over_write=True
+        )
+        assert self.sut.zarr_folder_path.exists()
+        for output_name in self.sut.list_available_outputs():
+            if output_name in multiple_output_names:
+                output = self.sut.get_output(output_name)
+                assert output.zarr_path.exists()
+                zarr_file = zarr.open(output.zarr_path, mode="r")
+                assert isinstance(zarr_file, zarr.hierarchy.Group)
+                assert isinstance(zarr_file["data"], zarr.core.Array)
+                output_data = output.to_numpy()
+                assert zarr_file["data"].shape == output_data.shape
+
+    def test_zarr_all_outputs(self):
+        """
+        Run a test to ensure that the zarr output contains all outputs
+        """
+        self.sut.to_zarr(OUTPUT_PATH / "zarr_outputs", over_write=False)
+        assert self.sut.zarr_folder_path.exists()
+        for output_name in self.sut.list_available_outputs():
+            output = self.sut.get_output(output_name)
+            assert output.zarr_path.exists()
+            zarr_file = zarr.open(output.zarr_path, mode="r")
+            assert isinstance(zarr_file, zarr.hierarchy.Group)
+            assert isinstance(zarr_file["data"], zarr.core.Array)
+            output_data = output.to_numpy()
+            assert zarr_file["data"].shape == output_data.shape
+
+    def test_zarr_xarray_connection_single_output(
+        self, single_output_name="fuels-dens"
+    ):
+        output = self.sut.get_output(single_output_name)
+        ds = xr.open_zarr(
+            output.zarr_path, decode_times=False
+        )  # zarr path will only exist if you run .to_zarr()
+        assert ds.data.long_name == single_output_name
+        ds.info()
+        return ds
+
+    def test_zarr_rechunker(self):
+        pass
+
+
+if __name__ == "__main__":
+    main()