Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
939a8a9
WIP - extract production/modeled temperature data in preparation to s…
softwareengineerprogrammer Apr 20, 2026
d68206b
acceptable extraction of production data points from edited image
softwareengineerprogrammer Apr 20, 2026
47a2638
exclude temperature dips/ramp-ups caused by non-continuous operationa…
softwareengineerprogrammer Apr 20, 2026
77ba179
use caching client for FPC5 doc graph generation to avoid repeated runs
softwareengineerprogrammer Apr 20, 2026
fe90ac4
WIP - exclude dips/ramp-ups above 175C
softwareengineerprogrammer Apr 20, 2026
64c9eea
exclude operational dips/ramp-ups acceptably well
softwareengineerprogrammer Apr 20, 2026
a661cc6
Revert "use caching client for FPC5 doc graph generation to avoid rep…
softwareengineerprogrammer Apr 20, 2026
e17fca2
position legend below graph field
softwareengineerprogrammer Apr 20, 2026
70863ce
Tweak graph title/legend labels. mark WIP/TODO to add GEOPHIRES profi…
softwareengineerprogrammer Apr 20, 2026
43a8ddb
WIP - more prep to add GEOPHIRES profile data to graph
softwareengineerprogrammer Apr 20, 2026
08cb6b6
include GEOPHIRES-modeled temperature in graph
softwareengineerprogrammer Apr 20, 2026
3e9af76
exclude thermal conditioning
softwareengineerprogrammer Apr 20, 2026
d4811d4
adjust gradient and fracture parameters to match fervo-modeled temper…
softwareengineerprogrammer Apr 20, 2026
ec07279
WIP - include graph in Fervo_Project_Red docs page (WIP)
softwareengineerprogrammer Apr 20, 2026
cb2f189
incorporate into docs build and add to sidebar
softwareengineerprogrammer Apr 20, 2026
415ebd5
make GEOPHIRES series more visually distinguishable - larger green do…
softwareengineerprogrammer Apr 20, 2026
a817eed
zoom graph in
softwareengineerprogrammer Apr 20, 2026
19221a3
generate zoomed out and in versions
softwareengineerprogrammer Apr 20, 2026
ff2b867
set x-axis max to 1.75 years
softwareengineerprogrammer Apr 20, 2026
45d8774
regen Fervo_Project_Red-2026.out
softwareengineerprogrammer Apr 20, 2026
45ac2a6
test_fervo_project_red_2026_results_against_reference_values - ensure…
softwareengineerprogrammer Apr 20, 2026
84e41e5
calculate r^2 (WIP to incorporate in graphs/doc page)
softwareengineerprogrammer Apr 20, 2026
2df4bd8
address TODO for better label -> Transient Operations
softwareengineerprogrammer Apr 20, 2026
a5d3ef4
include stats alignment metrics in captions
softwareengineerprogrammer Apr 20, 2026
6663b34
Fervo_Project_Red.md content & styling
softwareengineerprogrammer Apr 20, 2026
bd9a7c8
image width style tweak
softwareengineerprogrammer Apr 20, 2026
953b2e2
Fervo_Project_Red.md copy edits, integration into examples doc page
softwareengineerprogrammer Apr 20, 2026
82fb83a
increase time steps per year 100 (GEOPHIRES max, for now, TODO to inc…
softwareengineerprogrammer Apr 20, 2026
46248ec
fix cold bias reference
softwareengineerprogrammer Apr 20, 2026
25ed65c
Add documentation link to .txt
softwareengineerprogrammer Apr 20, 2026
31ed839
copy edits
softwareengineerprogrammer Apr 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ docs/hip_ra_x_parameters.rst
_site/
/docs/geophires_x.rstx
/docs/temperature.txt

src/geophires_docs/fervo_project_red-2026_graph-data-extraction.xcf

/geophires_x.rst
/modules.rst
/Useful sites for Sphinx docstrings.txt
Expand Down
90 changes: 90 additions & 0 deletions docs/Fervo_Project_Red.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
.. raw:: html

<style type="text/css">
a.image-reference {
max-width: 66%;
}

a.image-reference > img, a.image-reference > img:active {
max-width: 100%;
}

a.image-reference {
display: block;
margin: auto;
align-content: center;
}
</style>


# Fervo Project Red: Evaluating the Gringarten Model against Empirical EGS Data

ℹ️ The GEOPHIRES (Gringarten) model parameters used in this evaluation can be explored and executed via the [Fervo_Project_Red-2026 example in the web interface](https://gtp.scientificwebservices.com/geophires/?geophires-example-id=Fervo_Project_Red-2026).

---

This document[^author] evaluates the accuracy of the analytical GEOPHIRES (Gringarten) reservoir model by comparing it against real-world Enhanced Geothermal Systems (EGS) empirical data from the Fervo Project Red site.
For comparative context, it also includes the predictive temperature curve generated by Fervo's proprietary modeling.

[^author]: Author: Jonathan Pezzino, Scientific Web Services LLC. GitHub profile: [softwareengineerprogrammer](https://github.com/softwareengineerprogrammer).

**Data Source & Methodology Notes:** The empirical production data evaluated here is derived from Figure 5 of Fervo Energy's report, [Enhanced Geothermal Has Been Proven at Scale: Here’s What Two Years of Production Data Show](https://fervoenergy.com/enhanced-geothermal-has-been-proven-at-scale-heres-what-two-years-of-production-data-show/).
It should be noted that this analysis contains inherent limitations: the data points were semi-manually extracted from
the published chart using image processing techniques, which introduces minor digitization artifacts.

Additionally, aligning the data for statistical analysis requires establishing a threshold between the initial
thermal conditioning phase and steady-state operations.
This boundary is an analytical judgment call necessitated by structural differences in the models: the Fervo curve
appears to assume an idealized steady-state flow from inception, omitting the early thermal ramp-up phase entirely.
Conversely, while the GEOPHIRES (Gringarten) model does account for early transient heat transfer, its precision
during this rapid ramp-up is inherently constrained by its temporal resolution (100 time steps per year).

## Production Temperature: Measured vs. Modeled

The charts below plot the measured flowing temperature over a roughly two-year period. Data points captured during early thermal conditioning and transient operations (e.g., shut-ins, flow-rate testing) are rendered in gray and excluded from the steady-state statistical alignment.

![](_images/fervo_project_red-2026_production-temperature-data-vs-modeling-1.png)

*Detail view of the steady-state temperature plateau (175°C–185°C):*
![](_images/fervo_project_red-2026_production-temperature-data-vs-modeling-2.png)

## Statistical Alignment Analysis

The variance analysis (results displayed in legend captions) evaluates the predictive accuracy of both models against the measured steady-state data (excluding the initial thermal conditioning/ramp-up period).

Both models demonstrate high predictive fidelity, tracking steady-state flowing temperatures within 1.5°C of the empirical data.

* **Overall Fit:** GEOPHIRES mathematically achieves a tighter overall fit, yielding a lower Root Mean Square Error (RMSE) and a higher coefficient of determination (R²).
* **Systematic Bias:** The Fervo model exhibits slightly less systemic underestimation, with a cold bias of -0.50°C compared to the GEOPHIRES cold bias of -0.70°C.
* **R² Context:** The relatively low R² values for both models are expected statistical artifacts. Because the steady-state temperature profile is essentially a flat plateau, natural sensor variance and minor reservoir oscillations account for a disproportionately large portion of the total sum of squares, suppressing the R² score despite the low absolute error.

## Modeling Assumptions and Power Production Discrepancies

While the Gringarten model accurately predicts the reservoir's thermal drawdown, translating that thermal energy into net electrical power introduces additional variables.
Users comparing GEOPHIRES power production estimates to Fervo's published net generation may notice discrepancies.
These arise from fundamental differences between an idealized techno-economic model and a real-world, non-commercial operational plant:

* **Commercial Optimization:** Fervo has explicitly noted that Project Red is a non-commercial, non-optimized learning facility designed to prove EGS viability at scale. Its empirical power generation figures reflect this experimental testing phase rather than the maximized output modeled by GEOPHIRES for mature commercial operations.
* **Capacity Factor and Transients:** While the GEOPHIRES model utilizes a 90% capacity factor, this derating is applied continuously across the simulation. Real-world output is subject to discrete transient operational events (such as thermal conditioning, testing, and maintenance shut-ins) which inherently causes the time-averaged power production to diverge temporally from an uninterrupted analytical model.
* **ORC Efficiency:** The power generation results rely on the GEOPHIRES built-in supercritical ORC efficiency correlation, which assumes the selection of an optimal working fluid for each specific geofluid temperature. For further details, please refer to the [Surface Plant section in the Theoretical Basis for GEOPHIRES](Theoretical-Basis-for-GEOPHIRES.html#surface-plant). Real-world net generation is constrained by the specific, fixed surface equipment installed at the site, which may operate below this theoretical optimum. This limitation may be addressed in the future by [FGEM integration](https://github.com/NatLabRockies/GEOPHIRES-X/issues/395).

---

## Previous Versions

1. [Fervo_Norbeck_Latimer_2023](https://gtp.scientificwebservices.com/geophires/?geophires-example-id=Fervo_Norbeck_Latimer_2023)

---

## Disclaimer: Independent Analysis

This case study is an independent techno-economic evaluation developed by the author and contributors to the GEOPHIRES
open-source project. It is not affiliated with, sponsored by, or endorsed by Fervo Energy.
The author and contributors are not employees or agents of Fervo Energy, and this work has not been reviewed or
approved by the company. All modeling assumptions, including those derived from public data sources, represent the
independent interpretation of the author and the GEOPHIRES open-source community and do not constitute proprietary
information or official company projections.

---

## Footnotes
4 changes: 4 additions & 0 deletions docs/GEOPHIRES-Examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ or in the [web interface](https://gtp.scientificwebservices.com/geophires) under
## Case Study: 500 MW EGS Project Modeled on Fervo Cape Station

See documentation: [Case Study: 500 MWe EGS Project Modeled on Fervo Cape Station](Fervo_Project_Cape-5.html).

## Fervo Project Red: Evaluating the Gringarten Model against Empirical EGS Data

See documentation: [Fervo Project Red: Evaluating the Gringarten Model against Empirical EGS Data](Fervo_Project_Red.html).
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Contents

GEOPHIRES-Examples
Fervo_Project_Cape-5
Fervo_Project_Red


.. reference/index
Expand Down
1 change: 1 addition & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ sphinx>=1.3
sphinx-py3doc-enhanced-theme
m2r2
Jinja2
opencv-python
6 changes: 5 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ def read(*names, **kwargs):
# eg:
# "rst": ["docutils>=0.11"],
# ":python_version=="2.6"": ["argparse"],
'development': ['bumpversion', 'sphinx_py3doc_enhanced_theme']
'development': [
'bumpversion',
'sphinx_py3doc_enhanced_theme',
'opencv-python', # generate_fervo_project_red_2026_docs
],
},
)
30 changes: 30 additions & 0 deletions src/geophires_docs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from __future__ import annotations

import json
import os
from pathlib import Path
from typing import Any

from pint.facets.plain import PlainQuantity

from geophires_x_client import GeophiresInputParameters
from geophires_x_client import GeophiresXClient
from geophires_x_client import GeophiresXResult


def _get_file_path(file_name) -> Path:
Expand Down Expand Up @@ -75,3 +80,28 @@ def _get_input_parameters_dict( # TODO consolidate with FervoProjectCape5TestCa
# TODO preserve newlines

return ret


def _get_full_profile(
input_and_result: tuple[GeophiresInputParameters, GeophiresXResult], profile_key: str
) -> list[PlainQuantity]:
"""
:return: List of data points with length Time steps per year * Plant lifetime
"""

input_params: GeophiresInputParameters = input_and_result[0]
result = GeophiresXClient().get_geophires_result(input_params)

with open(result.json_output_file_path, encoding='utf-8') as f:
full_result_obj = json.load(f)

net_gen_obj = full_result_obj[profile_key]
net_gen_obj_unit = net_gen_obj['CurrentUnits'].replace('CELSIUS', 'degC')
profile = [PlainQuantity(it, net_gen_obj_unit) for it in net_gen_obj['value']]
return profile


def _get_full_production_temperature_profile(
input_and_result: tuple[GeophiresInputParameters, GeophiresXResult],
) -> list[PlainQuantity]:
return _get_full_profile(input_and_result, 'Produced Temperature')
2 changes: 2 additions & 0 deletions src/geophires_docs/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
if __name__ == '__main__':
from geophires_docs import generate_fervo_project_cape_5_docs
from geophires_docs import generate_fervo_project_red_2026_docs

generate_fervo_project_cape_5_docs.generate_fervo_project_cape_5_docs()
generate_fervo_project_red_2026_docs.generate_fervo_project_red_2026_docs()
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 2 additions & 20 deletions src/geophires_docs/generate_fervo_project_cape_5_graphs.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
from __future__ import annotations

import json
from math import ceil
from pathlib import Path
from typing import Any

import numpy as np
from matplotlib import pyplot as plt
from pint.facets.plain import PlainQuantity

from geophires_docs import _FPC5_INPUT_FILE_PATH
from geophires_docs import _FPC5_RESULT_FILE_PATH
from geophires_docs import _PROJECT_ROOT
from geophires_docs import _get_full_production_temperature_profile
from geophires_docs import _get_full_profile
from geophires_docs import _get_input_parameters_dict
from geophires_docs import _get_logger
from geophires_x_client import GeophiresInputParameters
from geophires_x_client import GeophiresXClient
from geophires_x_client import GeophiresXResult
from geophires_x_client import ImmutableGeophiresInputParameters

Expand All @@ -32,27 +31,10 @@ def _get_full_total_electricity_generation_profile(input_and_result: tuple[Geoph
return _get_full_profile(input_and_result, 'Total Electricity Production')


def _get_full_production_temperature_profile(input_and_result: tuple[GeophiresInputParameters, GeophiresXResult]):
return _get_full_profile(input_and_result, 'Produced Temperature')


def _get_full_thermal_drawdown_profile(input_and_result: tuple[GeophiresInputParameters, GeophiresXResult]):
return _get_full_profile(input_and_result, 'Thermal Drawdown')


def _get_full_profile(input_and_result: tuple[GeophiresInputParameters, GeophiresXResult], profile_key: str):
input_params: GeophiresInputParameters = input_and_result[0]
result = GeophiresXClient().get_geophires_result(input_params)

with open(result.json_output_file_path, encoding='utf-8') as f:
full_result_obj = json.load(f)

net_gen_obj = full_result_obj[profile_key]
net_gen_obj_unit = net_gen_obj['CurrentUnits'].replace('CELSIUS', 'degC')
profile = [PlainQuantity(it, net_gen_obj_unit) for it in net_gen_obj['value']]
return profile


def _get_redrilling_event_indexes(
input_and_result: tuple[GeophiresInputParameters, GeophiresXResult], threshold_degc: float = 0.5
) -> list[float]:
Expand Down
Loading