Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ docs/reference/geophires-request.json
docs/reference/parameters.rst
docs/Fervo_Project_Cape-5.md
docs/Fervo_Project_Red.md
docs/Fervo_Project_Cape_HIIP_Analysis.md
docs/_images/*.bak.png
docs/_images/singh-et-al-2025_fig-7_well-and-bench-spacing.xcf
docs/_images/chp-topping-diagram.xcf
Expand Down
104 changes: 104 additions & 0 deletions docs/Fervo_Project_Cape_HIIP_Analysis.md.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Project Cape HIIP Validation

**Overview:** This analysis evaluates the accuracy and methodology of GEOPHIRES' HIP-RA-X volumetric heat-in-place tool
by comparing its calculations against the DeGolyer and MacNaughton (D&M) Heat Initially In Place (HIIP)
[report](https://www.sec.gov/Archives/edgar/data/1853868/000162828026025821/exhibit991-sx1.htm) prepared
for Fervo Energy's Cape Station (filed with the SEC in June 2024).

The results confirm that the HIP-RA-X core volumetric methodology is mathematically aligned to the
D&M HIIP methodology. When appropriately parameterized to remove secondary thermodynamic recovery
constraints, HIP-RA-X produces results in agreements with the SEC filing's baseline thermal energy estimates.

**Disclaimer: Independent Analysis:** This is an independent 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, DeGolyer and MacNaughton,
the SEC, or any other party.
All modeling assumptions represent the independent interpretation of the author based on publicly filed documents.

---

## Methodology

### Geologic and Thermal Model

**SEC HIIP Claim:** The Project Cape Area targets a high-temperature geothermal anomaly in low-permeability Granitic
Basement rocks with little to no porosity. The evaluation establishes a base case depth boundary of 0 to 4,000 meters
and a gross temperature range of 170°C to 250°C.

**HIP-RA-X Evaluation:**
HIP-RA-X mathematically aligns with the SEC's thermal energy physics. Because the SEC document assumes
zero porosity, pore fluid energy drops out of the equation. To mirror this exact state, we calibrated the base
GEOPHIRES model with the following parameters derived from the filing:

{{ baseline_input_params_table_md }}

### Estimation Methodology

**SEC HIIP Claim:** The SEC HIIP estimates are raw, un-risked baselines. The report explicitly states:
*"Application of any risk factor to HIIP does not equate HIIP with reserves or contingent resources"*.
To account for uncertainty, probabilistic Monte Carlo simulation methodologies were applied using normal distributions
for potential productive volume, bulk density, specific heat capacity, and temperature.

**HIP-RA-X Evaluation:**
By default, HIP-RA-X acts as a resource assessment tool and bakes in a 75% rock heat recovery factor.
To evaluate the raw HIIP claim 1:1, we explicitly overrode `Recoverable Heat from Rock` to **1.0 (100%)**.

Furthermore, to mirror the SEC filing's probabilistic approach, GEOPHIRES's [Monte Carlo module](Monte-Carlo-User-Guide.html)
was utilized, supplying normal distributions for Reservoir Temperature, Reservoir Area, Reservoir Thickness,
Rock Heat Capacity, and Density of Reservoir Rock.

## Results

### Estimation of Heat Initially in Place

**SEC HIIP Claim:** The probabilistic evaluation yielded a "Low Estimate" (P90) Gross HIIP of 50,730 PJ and a
"Mean Estimate" of 63,560 PJ.

**HIP-RA-X Evaluation:**
Our evaluation captures both the deterministic proxy (using the 199°C ORC intake temperature) and the probabilistic
Mean (using the 1,000-iteration Monte Carlo simulation).

| Model | Evaluated Thermal Metric | Result (10<sup>15</sup> Joules) |
| :--- | :--- | :--- |
| **SEC Filing (D&M)** | Gross HIIP (Low Estimate) | **50,730** |
| **HIP-RA-X (Deterministic)** | Stored Heat (reservoir) | **{{ det_stored_heat_15j }}** |
| **SEC Filing (D&M)** | Gross HIIP (Mean Estimate) | **63,560** |
| **HIP-RA-X (Monte Carlo)**| Stored Heat (reservoir) Mean | **{{ mc_stored_heat_mean_15j }}** |

Because our deterministic run used a static 199°C input, sitting in the lower half of the SEC's 170°C to 250°C distribution,
it maps closely to the P90 "Low Estimate".
Meanwhile, the Monte Carlo simulation demonstrates that when supplied with identical bounding conditions and normal
distributions, HIP-RA-X mirrors the SEC HIIP probabilistic Mean.

**Stored Heat Distribution:**

![](_images/fpc_hiip_mc_Stored_Heat_reservoir.png)

### Electric Power Capacity

**SEC HIIP Claim:** The SEC HIIP converts thermal energy to electricity by taking the raw HIIP and applying a static
19.5% ORC plant efficiency and a 1.069 peak output correction factor over 30 years.
This yields a Mean Estimate Electric Power Capacity of 14,005 MW.

**HIP-RA-X Evaluation:**
This is where the two methodologies diverge. The SEC filing assumes that 19.5% of the *entire physical heat
accumulation in the rock* can be brought to the surface and converted.

HIP-RA-X operates under strict thermodynamic limits. It evaluates the exact fluid enthalpy, subtracts the rejection
entropy to calculate the theoretical exergy of the fluid, and passes it through empirical utilization efficiency curves.
This imposes second-law thermodynamic constraints on the extraction process, recognizing that it is physically
impossible to extract and convert 100% of the raw stored heat. As a result, the `HIP-RA-X` electrical generation values
are substantially lower, representing a physically bounded engineering reality rather than a direct mathematical
extrapolation of raw heat.

* **SEC HIIP Estimated Power Capacity (Mean):** 14,005 MW
* **HIP-RA-X Producible Electricity (MC Mean):** {{ mc_elec_mean_mw }} MW

**Producible Electricity Distribution:**

![](_images/fpc_hiip_mc_Producible_Electricity_reservoir.png)

---

## References

DeGolyer and MacNaughton. (2024, September 24). *Report as of June 30, 2024 on Heat Initially In Place associated with the Project Cape Area prepared for Fervo Energy*. Securities and Exchange Commission, Exhibit 99.1. [https://www.sec.gov/Archives/edgar/data/1853868/000162828026025821/exhibit991-sx1.htm](https://www.sec.gov/Archives/edgar/data/1853868/000162828026025821/exhibit991-sx1.htm)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_images/fpc_hiip_mc_Reservoir_Area.png
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.
Binary file added docs/_images/fpc_hiip_mc_Reservoir_Thickness.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_images/fpc_hiip_mc_Rock_Heat_Capacity.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_images/fpc_hiip_mc_Stored_Heat.png
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.
3 changes: 2 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ Contents

.. toctree::
:maxdepth: 3
:caption: Examples
:caption: Examples & Case Studies

GEOPHIRES-Examples
Fervo_Project_Cape-5
Fervo_Project_Red
Fervo_Project_Cape_HIIP_Analysis


.. reference/index
Expand Down
7 changes: 5 additions & 2 deletions src/geophires_docs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from geophires_x_client import GeophiresInputParameters
from geophires_x_client import GeophiresXClient
from geophires_x_client import GeophiresXResult
from hip_ra import HipRaInputParameters

_NON_BREAKING_SPACE = '\xa0'

Expand Down Expand Up @@ -63,7 +64,9 @@ def error(self, msg):


def _get_input_parameters_dict( # TODO consolidate with FervoProjectCape5TestCase._get_input_parameters
_params: GeophiresInputParameters, include_parameter_comments: bool = False, include_line_comments: bool = False
_params: GeophiresInputParameters | HipRaInputParameters,
include_parameter_comments: bool = False,
include_line_comments: bool = False,
) -> dict[str, Any]:
comment_idx = 0
ret: dict[str, Any] = {}
Expand All @@ -85,7 +88,7 @@ def _get_input_parameters_dict( # TODO consolidate with FervoProjectCape5TestCa
return ret


def _get_input_parameters_comments_dict(_params: GeophiresInputParameters) -> dict[str, str]:
def _get_input_parameters_comments_dict(_params: GeophiresInputParameters | HipRaInputParameters) -> dict[str, str]:
ret: dict[str, str] = {}

with open(_get_file_path('../geophires_x_schema_generator/geophires-request.json'), encoding='utf-8') as f:
Expand Down
142 changes: 142 additions & 0 deletions src/geophires_docs/generate_fpc_hiip_analysis_doc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import logging
import shutil
from pathlib import Path

from jinja2 import Environment
from jinja2 import FileSystemLoader
from tabulate import tabulate

from geophires_docs import _get_input_parameters_dict
from geophires_docs import _get_logger
from geophires_monte_carlo import GeophiresMonteCarloClient
from geophires_monte_carlo import MonteCarloRequest
from geophires_monte_carlo import MonteCarloResult
from geophires_monte_carlo import SimulationProgram
from hip_ra import HipRaInputParameters
from hip_ra_x import HipRaXClient
from hip_ra_x import HipRaXResult

_log = _get_logger(__name__)

_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
_BUILD_DIR = _PROJECT_ROOT / 'build' / 'fpc_hiip_analysis'
_IMAGES_DIR = _PROJECT_ROOT / 'docs' / '_images'


def _get_baseline_input_params_table_md(baseline_input_params):
params_dict = _get_input_parameters_dict(baseline_input_params, include_parameter_comments=True)

table = []

for k, v_c in params_dict.items():
v = v_c.split(',')[0].strip()
c = v_c.split(',')[1].replace(' -- ', '').strip()

# Prevent tabulate from trying to convert boolean strings to float
if v.lower() in ('true', 'false'):
v = f' {v}'

if c == '':
# c = ' .. N/A'
continue # omit commentless params for now

table.append([k, v, c])

return tabulate(table, ['Parameter', 'Value', 'Comment'], tablefmt='github', floatfmt='')


def generate_fpc_hiip_analysis_doc():
_BUILD_DIR.mkdir(parents=True, exist_ok=True)
_IMAGES_DIR.mkdir(parents=True, exist_ok=True)

base_input_path = (
_PROJECT_ROOT / 'tests' / 'hip_ra_x_tests' / 'examples' / 'Fervo_Project_Cape-HIIP-analysis-baseline.txt'
)

_log.info('Running deterministic HIP-RA-X baseline...')
client = HipRaXClient()
det_input_params: HipRaInputParameters = HipRaInputParameters(file_path_or_params_dict=base_input_path)
det_result: HipRaXResult = client.get_hip_ra_x_result(det_input_params)

det_stored_heat_kj = det_result.result['SUMMARY OF RESULTS']['Stored Heat (reservoir)']['value']
det_elec_mw = det_result.result['SUMMARY OF RESULTS']['Producible Electricity (reservoir)']['value']

# Convert kJ to 10^15 Joules (10^15 J = 10^12 kJ)
det_stored_heat_15j = det_stored_heat_kj / 1e12

# 2. Configure and Run Monte Carlo Simulation
mc_settings_path = _BUILD_DIR / 'fpc_hiip_mc_settings.txt'
mc_output_path = _BUILD_DIR / 'fpc_hiip_mc_results.txt'

with open(mc_settings_path, 'w') as f:
# The SEC HIIP methodology explicitly models productive volume (Area * Thickness),
# density, specific heat (Rock Heat Capacity), and temperature using normal distributions.
f.write('INPUT, Reservoir Temperature, normal, 210.0, 15.0\n')
f.write('INPUT, Reservoir Area, normal, 48.0, 2.4\n')
f.write('INPUT, Reservoir Thickness, normal, 4.0, 0.2\n')
f.write('INPUT, Rock Heat Capacity, normal, 2.212e12, 1.1e11\n')
f.write('INPUT, Density Of Reservoir Rock, normal, 2.8e12, 0.1e12\n')

f.write('OUTPUT, Stored Heat (reservoir)\n')
f.write('OUTPUT, Producible Electricity (reservoir)\n')
f.write('ITERATIONS, 1000\n')
f.write(f'MC_OUTPUT_FILE, {mc_output_path.absolute()}\n')

_log.info('Running Monte Carlo HIP-RA-X simulation...')

# Initialize the Monte Carlo Request
mc_request = MonteCarloRequest(
simulation_program=SimulationProgram.HIP_RA_X,
input_file=base_input_path.absolute(),
monte_carlo_settings_file=mc_settings_path.absolute(),
output_file=mc_output_path.absolute(),
)

# Execute the client
mc_client = GeophiresMonteCarloClient()
mc_result: MonteCarloResult = mc_client.get_monte_carlo_result(mc_request)

# 3. Read MC JSON Results directly from the result object
mc_stats: dict = mc_result.result['output']

mc_stored_heat_mean_kj = mc_stats['Stored Heat (reservoir)']['mean']
mc_stored_heat_mean_15j = mc_stored_heat_mean_kj / 1e12

mc_elec_mean_mw = mc_stats['Producible Electricity (reservoir)']['mean']

# Copy generated MC histogram images to the docs directory
mc_images = ['Stored Heat (reservoir).png', 'Producible Electricity (reservoir).png']

for img_name in mc_images:
src = _BUILD_DIR / img_name
dst = _IMAGES_DIR / f'fpc_hiip_mc_{img_name.replace(" ", "_").replace("(", "").replace(")", "")}'
if src.exists():
shutil.copy(src, dst)
_log.info(f'Copied {src.name} to docs/_images/')

# 4. Render Jinja Template
_log.info('Rendering Markdown documentation...')
docs_dir = _PROJECT_ROOT / 'docs'

baseline_input_params_table_md = _get_baseline_input_params_table_md(det_input_params)

template_values = {
'baseline_input_params_table_md': baseline_input_params_table_md,
'det_stored_heat_15j': f'{det_stored_heat_15j:,.0f}',
'det_elec_mw': f'{det_elec_mw:,.0f}',
'mc_stored_heat_mean_15j': f'{mc_stored_heat_mean_15j:,.0f}',
'mc_elec_mean_mw': f'{mc_elec_mean_mw:,.0f}',
}

env = Environment(loader=FileSystemLoader(docs_dir), autoescape=True)
template = env.get_template('Fervo_Project_Cape_HIIP_Analysis.md.jinja')
output = template.render(**template_values)

output_file = docs_dir / 'Fervo_Project_Cape_HIIP_Analysis.md'
output_file.write_text(output, encoding='utf-8')
_log.info(f'✓ Generated {output_file}')


if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
generate_fpc_hiip_analysis_doc()
4 changes: 4 additions & 0 deletions src/hip_ra/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ def as_file_path(self) -> Path:
def output_file_path(self) -> Path:
return self._output_file_path

def as_text(self):
with open(self.as_file_path(), encoding='UTF-8') as f:
return f.read()


class HipRaResult:
def __init__(self, output_file_path):
Expand Down
26 changes: 12 additions & 14 deletions src/hip_ra_x/hip_ra_x.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,45 +863,43 @@ def render_scientific(p: Parameter) -> str:
case_data_results = {self._SUMMARY_OF_RESULTS_OUTPUT_CATEGORY: summary_of_results}

with open(outputfile, 'w', encoding='UTF-8') as f:
nl = '\n'

f.write(f' *********************{nl}')
f.write(f' ***HIP CASE REPORT***{nl}')
f.write(f' *********************{nl}')
f.write(nl)
f.write(f' ***{self._SUMMARY_OF_INPUTS_OUTPUT_CATEGORY}***{nl}')
f.write(' *********************\n')
f.write(' ***HIP CASE REPORT***\n')
f.write(' *********************\n')
f.write('\n')
f.write(f' ***{self._SUMMARY_OF_INPUTS_OUTPUT_CATEGORY}***\n')

for k, v in case_data_inputs[self._SUMMARY_OF_INPUTS_OUTPUT_CATEGORY].items():
# align space between value and units to same column
kv_spaces = max(1, (24 - (len(v.split(' ')[0]) + len(k)))) * ' '

f.write(f' {k}:{kv_spaces}{v}{nl}')
f.write(f' {k}:{kv_spaces}{v}\n')

f.write(nl)
f.write(f' ***{self._SUMMARY_OF_RESULTS_OUTPUT_CATEGORY}***{nl}')
f.write('\n')
f.write(f' ***{self._SUMMARY_OF_RESULTS_OUTPUT_CATEGORY}***\n')
for k, v in case_data_results[self._SUMMARY_OF_RESULTS_OUTPUT_CATEGORY].items():
# align space between value and units to same column
kv_spaces = max(1, (24 - (len(v.split(' ')[0]) + len(k)))) * ' '

f.write(f' {k}:{kv_spaces}{v}{nl}')
f.write(f' {k}:{kv_spaces}{v}\n')

except FileNotFoundError as ex:
traceback_str = traceback.format_exc()
msg = f'Error: HIP_RA_X Failed to write the output file. Exiting....\n{traceback_str}'
msg = f'Error: HIP-RA-X Failed to write the output file. Exiting....\n{traceback_str}'
self.logger.critical(str(ex))
self.logger.critical(msg)
raise

except PermissionError as ex:
traceback_str = traceback.format_exc()
msg = f'Error: HIP_RA_X Failed to write the output file. Exiting....\n{traceback_str}'
msg = f'Error: HIP-RA-X Failed to write the output file. Exiting....\n{traceback_str}'
self.logger.critical(str(ex))
self.logger.critical(msg)
raise

except Exception as ex:
traceback_str = traceback.format_exc()
msg = f'Error: HIP_RA_X Failed to write the output file. Exiting....\n{traceback_str}'
msg = f'Error: HIP-RA-X Failed to write the output file. Exiting....\n{traceback_str}'
self.logger.critical(str(ex))
self.logger.critical(msg)
raise
Expand Down
Loading
Loading