diff --git a/.aiexclude b/.aiexclude new file mode 100644 index 000000000..03bd4129b --- /dev/null +++ b/.aiexclude @@ -0,0 +1 @@ +*.env diff --git a/.bumpversion.cfg b/.bumpversion.cfg index b16532369..94e51e4ad 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.10.24 +current_version = 3.11.2 commit = True tag = True diff --git a/.cookiecutterrc b/.cookiecutterrc index 56f2be8c4..4414e110d 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -54,7 +54,7 @@ default_context: sphinx_doctest: "no" sphinx_theme: "sphinx-py3doc-enhanced-theme" test_matrix_separate_coverage: "no" - version: 3.10.24 + version: 3.11.2 version_manager: "bump2version" website: "https://github.com/NREL" year_from: "2023" diff --git a/.editorconfig b/.editorconfig index 586c7367d..74879ed01 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,3 +18,7 @@ indent_size = 2 [*.tsv] indent_style = tab + +[*.jinja] +trim_trailing_whitespace = false +indent_size = unset diff --git a/.gitignore b/.gitignore index a922924be..1b686f1b4 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,11 @@ requirements_2025-08-11.txt .build .cache .eggs + .env +*.env +tests/regenerate-example-result.env + .installed.cfg .ve bin @@ -92,8 +96,10 @@ output/*/index.html # Sphinx/docs docs/_build +docs/temp.txt docs/reference/geophires-request.json docs/reference/parameters.rst +docs/Fervo_Project_Cape-5.md docs/geophires-request.json docs/parameters.rst docs/hip-ra-x-request.json diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0c2f9f01e..a4f2c94fa 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,10 +4,19 @@ Changelog GEOPHIRES v3 (2023-2025) ------------------------ +3.11 +^^^^ + +3.11.2: `SAM Economic Models ITC result output `__ | `release `__ + +3.11: `SAM Economic Models Project Payback Period fix `__ | `release `__ + 3.10 ^^^^ +3.10.25: `Add Number of Injection Wells per Production Well parameter `__ + 3.10: `SAM Economic Models: Multiple Construction Years; Number of Fractures per Stimulated Well parameter; Royalty Rate Escalation Start Year parameter `__ | `release `__ 3.9 diff --git a/MANIFEST.in b/MANIFEST.in index 3440150e6..86c6af67b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,6 +3,7 @@ graft src graft ci graft tests +include .aiexclude include .bumpversion.cfg include .cookiecutterrc include .coveragerc diff --git a/README.rst b/README.rst index ad9530116..a83884798 100644 --- a/README.rst +++ b/README.rst @@ -58,9 +58,9 @@ Free software: `MIT license `__ :alt: Supported implementations :target: https://pypi.org/project/geophires-x -.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.10.24.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.11.2.svg :alt: Commits since latest release - :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.10.24...main + :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.11.2...main .. |docs| image:: https://readthedocs.org/projects/GEOPHIRES-X/badge/?style=flat :target: https://nrel.github.io/GEOPHIRES-X @@ -168,6 +168,10 @@ Example-specific web interface deeplinks are listed in the Link column. - Input file - Case report file - Link + * - Case Study: 500 MWe EGS modeled on Fervo Cape Station (`documentation `__) + - `Fervo_Project_Cape-4.txt `__ + - `.out `__ + - `link `__ * - Example 1: EGS Electricity - `example1.txt `__ - `.out `__ @@ -288,10 +292,10 @@ Example-specific web interface deeplinks are listed in the Link column. - `Fervo_Project_Cape-3.txt `__ - `.out `__ - `link `__ - * - Case Study: 500 MWe EGS Project Modeled on Fervo Cape Station (`documentation `__) - - `Fervo_Project_Cape-4.txt `__ - - `.out `__ - - `link `__ + * - 100 MWe EGS modeled on Fervo Cape Station Phase I + - `Fervo_Project_Cape-5.txt `__ + - `.out `__ + - `link `__ * - Superhot Rock (SHR) Example 1 - `example_SHR-1.txt `__ - `.out `__ diff --git a/docs/Fervo_Project_Cape-4.md b/docs/Fervo_Project_Cape-4.md index 4c0c3f8ea..9aa975219 100644 --- a/docs/Fervo_Project_Cape-4.md +++ b/docs/Fervo_Project_Cape-4.md @@ -1,4 +1,9 @@ -# Case Study: 500 MWe EGS Project Modeled on Fervo Cape Station +# [Deprecated] Case Study: 500 MWe EGS Project Modeled on Fervo Cape Station + +**⚠️ This is a previous version of the case study. The case study has been updated since the release of this version.** +[Click here](Fervo_Project_Cape-5.html) to find the latest version. + +--- The GEOPHIRES example `Fervo_Project_Cape-4` is a case study of a 500 MWe EGS Project modeled on Fervo Cape Station with its April 2025-announced diff --git a/docs/Fervo_Project_Cape-5.md.jinja b/docs/Fervo_Project_Cape-5.md.jinja new file mode 100644 index 000000000..52ce627c3 --- /dev/null +++ b/docs/Fervo_Project_Cape-5.md.jinja @@ -0,0 +1,416 @@ +# GEOPHIRES Case Study: 500 MW EGS modeled on Fervo Cape Station + +## Introduction + +The GEOPHIRES example `Fervo_Project_Cape-5`[^author] is a case study of a 500 MWe EGS project modeled +on Phases I and II of [Fervo Energy's Cape Station](https://capestation.com/). + +[^author]: Author: Jonathan Pezzino (GitHub: [softwareengineerprogrammer](https://github.com/softwareengineerprogrammer)) + +Key case study results include LCOE = {{ '$' ~ lcoe_usd_per_mwh ~ '/MWh' }} and IRR = {{ irr_pct ~ '%' }}. ([Jump to Results section](#results)). + +[Click here](https://gtp.scientificwebservices.com/geophires/?geophires-example-id=Fervo_Project_Cape-5) to +interactively explore the case study in the GEOPHIRES web interface. + +### Modeling Overview: A Conservative, Second-of-a-Kind Analog + +This case study models a 500 MWe Enhanced Geothermal System (EGS) project designed to represent a "Second-of-a-Kind" ( +SOAK) deployment. +Rather than serving as an exact facsimile of Cape Station as built, this study estimates what a non-Fervo developer +could achieve on a geologically identical site, relying primarily on publicly available data and standardized +engineering estimates. +The model assumes the developer is a "fast follower": benefiting from the proof-of-concept established by Cape Station +Phase I but operating without access to Fervo’s private supply chain or proprietary optimization data. + +**Public Data Reliance:** Inputs utilize exact values for publicly available parameters, such as geothermal +gradient and reservoir density. +Where data is proprietary, values are inferred from public announcements or extrapolated from standard industry +correlations. + +**Conservative Constraints:** To ensure the model serves as a robust feasibility test, some inputs are intentionally +conservative compared to Fervo’s stated targets, such as drilling costs and water loss. + +**Fast Follower Advantage:** By entering the market after Fervo’s initial de-risking campaigns, the modeled developer +avoids the high "tuition costs" of early experimentation. +For example, while Fervo’s initial drilling costs at Cape Station ranged +from [$9.4M down to $4.8M per well](https://houston.innovationmap.com/fervo-energy-drilling-utah-project-2667300142.html) +as they climbed the learning curve, +this model assumes a developer can bypass those initial high-cost outliers, +instead initiating their campaign at a stabilized commercial baseline (modeled here +at {{ '$' ~ drilling_costs_per_well_musd ~ 'M/well' }}, aligned with the NREL ATB and 2025 cost curves). +This reflects a developer who capitalizes on established industry knowledge to skip the "First-of-a-Kind" (FOAK) +premiums but has not yet achieved the fully optimized learning rates of a mature "Nth-of-a-kind" operator. + +### Intended Use Cases + +This case study is designed to function as a public utility for the geothermal sector, serving two primary roles: + +**Industry Benchmark:** By relying primarily on verifiable public data and independent expert consensus, this model +establishes a transparent baseline for EGS viability. +It tests the premise that Fervo’s success at Cape Station is a replicable standard for the next-generation geothermal +industry. +The results serve as reference points for what is achievable using current technology in high-grade resources. + +**Template for Resource Assessment & Custom Modeling:** The example input file (`Fervo_Project_Cape-5.txt`) is intended +as customizable template for modeling other resources. +Users can input local geologic data (gradient, rock properties) into this template to evaluate how a Cape Station-style +design would perform in different geographies (e.g., Nevada vs. Utah vs. International). +Different plant sizes and performance targets can be modeled by adjusting the number of production wells, fractures per well, +and other technical & engineering parameters. +The model allows users to stress-test economic assumptions, such as the PPA price or Investment Tax Credit (ITC), to see +how policy changes impact the feasibility of replicating this design elsewhere. + +## Methodology + +The Inputs and Results tables document key assumptions, inputs, and a comparison of results with reference +values. +Note that these are not the exhaustive sets of inputs and results, which are available in source code and +the [web interface](https://gtp.scientificwebservices.com/geophires/?geophires-example-id=Fervo_Project_Cape-5). + +### Inputs + +See [Fervo_Project_Cape-5.txt](https://github.com/softwareengineerprogrammer/GEOPHIRES/blob/main/tests/examples/Fervo_Project_Cape-5.txt) +in source code for the full set of inputs. + +#### Reservoir Parameters + +{{ reservoir_parameters_table_md }} + +#### Well Bores Parameters + +{{ well_bores_parameters_table_md }} + +#### Surface Plant Parameters + +{{ surface_plant_parameters_table_md }} + +#### Construction Parameters + +{{ construction_parameters_table_md }} + + +#### Economic Parameters + +{{ economics_parameters_table_md }} + +### Calibration with Fervo-implemented Field Design + +[Designing the Record-Breaking Enhanced Geothermal System at Project Cape](https://www.resfrac.com/wp-content/uploads/2025/06/Singh-2025-Fervo-Project-Cape.pdf) (Singh et al., 2025) +describes reservoir modeling (ResFrac) that informed the Cape Station field implementation[^field-implementation-configuration-note]. + +[^field-implementation-configuration-note]: Note on Configuration: While the specific Bearskin and Gold pads (Phase II) utilize an inverted 2:3 ratio (3 injectors for 2 producers), this case study assumes the 3:2 ratio identified in the paper's optimization studies ("Study 1") represents the standard repeating module for the full-scale 400+ MWe system. The higher injector count in Phase II is interpreted as a transient requirement for field delineation and initial pressure support (boundary conditions) rather than the long-term commercial standard. + +An equivalent GEOPHIRES simulation was run using the case study's reservoir engineering parameters, with the following modifications to align with Singh et al.'s modeling scenario: + +{{ reservoir_engineering_reference_simulation_params_table_md }} + +The following table compares the average production temperature profile from the "700 ft bench spacing" scenario in Singh et al. with the GEOPHIRES simulation. +Note that both figures show temperature in Fahrenheit rather than Celsius. + +{# @formatter:off #} +| Fervo-implemented Design Simulation (Fig. 18.) | Case Study Equivalent GEOPHIRES Simulation | +|---|---| +| | | +{# @formatter:on #} + +While the initial and final (Year 15) temperatures are consistent, the production curves exhibit distinct profiles due to the different modeling approaches: + +1. Reference Simulation (Left): The Singh et al. (2025) curve reflects a fully coupled numerical simulation (ResFrac) that accounts for complex fracture heterogeneity, inter-well interference, and variable flow paths. The gradual decline starting around Year 3 indicates thermal dispersion, where cold injection fluid mixes with hot reservoir fluid along faster flow paths earlier in the project life. +1. GEOPHIRES Simulation (Right): The GEOPHIRES result utilizes the Gringarten (1975) analytical solution for flow in fractured rock. This model assumes a uniform thermal sweep across an idealized fracture surface. Consequently, it maintains a flat, maximum production temperature for a longer duration until the cold front reaches the production well (thermal breakthrough), resulting in a sharper, later decline. + +Despite these structural differences, the comparison validates the basis for the case study's reservoir engineering parameters, as the aggregate heat extraction and year-15 endpoint align closely with the numerical simulation baseline. + + +## Results + +See [Fervo_Project_Cape-5.out](https://github.com/softwareengineerprogrammer/GEOPHIRES/blob/main/tests/examples/Fervo_Project_Cape-5.out) +in source code for the complete results. + +### Economic Results + +Note that economic results are derived from the [SAM Single Owner PPA Economic Model](SAM-Economic-Models.html#sam-single-owner-ppa) pro-forma cash flow analysis. +The case study result's cash flow analysis can be viewed in the web interface and in the `Fervo_Project_Cape-5.out` result file in source code. + +| Metric | Result Value | Reference Value(s) | Reference Source | +|---------------|----------------|--------------------|------------------| +| LCOE | {{ '$' ~ lcoe_usd_per_mwh ~ '/MWh' }} | \$80/MWh | Horne et al, 2025. | +| After-tax IRR | {{ irr_pct ~ '%' }} | 15–25% | Typical levered returns for energy projects | +| NPV | {{ '$' ~ npv_musd ~ 'M' }} | | .. N/A | +| Project ROI | {{ project_moic }} | | .. N/A | +| Profitability Index | {{ project_vir }} | | .. N/A | +{# Note that the '.. N/A' entry in the last row is required for the table to render in HTML (presumable m2r2/sphinx build issue) #} + +Hover over the metric names to view the corresponding definitions. +See [GEOPHIRES output parameters documentation](parameters.html#economic-parameters) for more information. + +### Capital Costs + +{# @formatter:off #} +| Metric | Result Value | Reference Value(s) | Reference Source | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------|--------------------------------------------------|------------------| +| WACC | {{ wacc_pct ~ '%' }} | 8.3% | Fervo's target goal is to eventually achieve a "Solar Standard" WACC of 8.3% (Matson, 2024). | +| Exploration Costs | {{ '$' ~ exploration_cost_musd ~ 'M' }} | {{ '$' ~ drilling_costs_per_well_musd*5 ~ 'M' }} | 2024b ATB NF-EGS conservative scenario exploration assumption of 5 full-size wells (NREL, 2025). Case study result conservatively includes additional costs for geophysical survey, indirect costs, and contingency. | +| Well Drilling and Completion Costs | {{ '$' ~ drilling_costs_musd ~ 'M' }} total ({{ '$' ~ drilling_costs_per_well_musd ~ 'M/well' }}) | $<4M/well | Latimer, 2025. | +| Stimulation Costs | {{ '$' ~ stim_costs_musd ~ 'M' }} total ({{ '$' ~ stim_costs_per_well_musd ~ 'M/well' }}) | $4.65M/well | Based on 46%:54% drilling:stimulation cost ratio (Yusifov & Enriquez, 2025). | +| Surface Power Plant Costs | {{ '$' ~ surface_power_plant_costs_gusd ~ 'B' }} | | | +| Field Gathering System Costs | {{ '$' ~ field_gathering_cost_musd ~ 'M' }} ({{ field_gathering_cost_pct_occ ~ '%' }} of OCC) | 2% of OCC | Matson, 2024. | +| Overnight Capital Cost | {{ '$' ~ occ_gusd ~ 'B' }} | | | +| Total CAPEX | {{ '$' ~ total_capex_gusd ~ 'B' }} (OCC + interest and inflation during construction) | | | +| Total CAPEX: $/kW | {{ '$' ~ capex_usd_per_kw ~ '/kW' }} (based on maximum net electricity generation) | $5000/kW; $4500/kW; $3000–$6000/kW | McClure, 2024; Horne et al, 2025; Latimer, 2025. | +{# @formatter:on #} + + +### Technical & Engineering Results + +{# @formatter:off #} +| Metric | Result Value | Reference Value(s) | Reference Source | +|--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|---| +| Total fracture surface area per well | {{ total_fracture_surface_area_per_well_mm2 }}×10⁶ m² ({{ total_fracture_surface_area_per_well_mft2 }} million ft²) | Project Red: 2.787×10⁶ m² (30 million ft²) | Greater fracture surface area expected than Project Red (Fercho et al, 2025). | +| Reservoir Volume | {{ reservoir_volume_m3 }} m³ | | Calculated from fracture area × fracture separation × number of fractures per well × number of wells | +| Bottom-hole Temperature (BHT) | {{ bht_temp_degc ~ '℃' }} | 200–241℃ | Fercho et al., 2024; Singh et al., 2025. | +| Initial Production Temperature | {{ initial_production_temperature_degc ~ '℃' }} | 196–208℃ | Approximate range of initial production temperatures between shallower and deeper producers (Singh et al., 2025).| +| Average Production Temperature | {{ average_production_temperature_degc ~ '℃' }} | 199–209℃ | Approximate range of thermally conditioned production temperatures between shallower and deeper producers (Singh et al., 2025). | +| Maximum Total Electricity Generation | {{ max_total_generation_mwe }} MW | 600 MW | Combined nameplate capacity of 10×60 MWe Gen 2 ORCs. A total of 8×60 MWe Gen 2 ORCs have been announced for Phase II; 3 from Turboden and 5 from Baker Hughes (Turboden, 2025; Jacobs, 2025). This equates to 480 MW gross capacity for Phase II's 400 MW net capacity. An equivalent SOAK 500 MW project would therefore require 10 Gen 2 ORC units. (Note that the modular Gen 2 ORCs are not individually modeled in this case study, and are assumed to be combined into a single power plant). | +| Minimum Net Electricity Generation | {{ min_net_generation_mwe }} MW | 500 MW | The announced 500 MWe capacity (Fervo Energy, 2025) is interpreted to mean that the PPA penalizes Cape Station if net electricity generation falls below 500 MWe. | +| 2-year Average Net Power Production per Production Well | {{ two_year_avg_net_power_mwe_per_production_well }} MW | 7.6–11.5 MW | Figures 4 and 12 (Singh et al., 2025). | +| Injection Pumping Parasitic Load (Average Pumping Power/Average Total Electricity Generation) | {{ parasitic_loss_pct ~ '%' }} | Upper bound: 16.7% | Procurement of 480 MW of Gen 2 ORC units for 400 MW net capacity in Phase II allows for up to 16.7% total on-site consumption (80 MW; including injection pumping power). | +| Average Net Electricity Generation | {{ avg_net_generation_mwe }} MW | | | +| Maximum Net Electricity Generation | {{ max_net_generation_mwe}} MW | | | +| Number of times redrilling | {{ number_of_times_redrilling }} | 2–5 | Redrilling expected to be required within 5–10 years of project start | +| Total wells drilled over project lifetime | {{ total_wells_including_redrilling }} | 320 | Total wells permitted by environmental assessment (BLM, 2024). | +{# @formatter:on #} + + +#### Power Production Curve +{# TODO #} +{#![caption](_images/fervo_project_cape-5-production-temperature.png)#} + +![caption](_images/fervo_project_cape-5-net-power-production.png) + +The project's generation profile (as seen in the graph above) exhibits distinctive cyclical behavior driven by the interaction between wellbore physics, reservoir thermal evolution, and economic constraints: + +1. Thermal Conditioning (Years 1-5): The initial rise in net power production, peaking at approximately 540 MW, is driven by the thermal conditioning of the production wellbores. As hot geofluid continuously flows through the wells, the wellbore casing and surrounding rock heat up, reducing conductive heat loss as predicted by the Ramey wellbore model. +1. Reservoir Drawdown (Years 5-8): Following the conditioning peak, power output declines as the cold front from injection wells reaches the production zone (thermal breakthrough), reducing the produced fluid enthalpy. +1. Redrilling (Years 8, 16, 24): To ensure the facility meets its 500 MW net PPA obligation, the model triggers redrilling events when production drops near the contractual minimum (corresponding to production temperature declining below the threshold defined by the `Maximum Drawdown` parameter value, as a percentage of the initial production temperature). These events simulate the redrilling of the entire wellfield to restore temperature and output. Their cost is amortized as an operational cost over the project lifetime. + +### Sensitivity Analysis + +The following charts show the sensitivity of key metrics to various inputs. +Each chart shows the sensitivity of a single metric, such as LCOE, to the set of tested input values. +The leftmost chart column shows the parameter being tested and its baseline case study input value in parentheses. +The bars for each row show the deltas of the metric value from the baseline case study value for the values tested for +that parameter. +Green bars indicate favorable outcomes, such as lower LCOE or higher IRR, while gray bars indicate unfavorable outcomes, +such as higher LCOE or lower IRR. + +Click the bars to view the sensitivity analysis result for the input value in the web interface. + +Note that the sensitivity analysis scenarios do not necessarily conform to all constraints and assumptions documented in the case study methodology. +For example, scenarios for Bond Interest Rate have different weighted average cost of capital (WACC) values due to the effect of interest rate on WACC. +This is particularly relevant for technical parameters pertaining to reservoir engineering. +In a real-world design, these variables are physically coupled; for instance, targeting a higher production flow rate +would typically necessitate a larger fracture surface area to mitigate the resulting acceleration in thermal drawdown. +See the [discussion of flow rate below](#impact-of-flow-rate-on-project-economics). + +### LCOE + +.. raw:: html + + + + + + LCOE Sensitivity Analysis Chart + +#### Impact of PPA Price on LCOE + +The sensitivity analysis reveals a positive correlation between the Power Purchase +Agreement (PPA) price and the Levelized Cost of Electricity (LCOE). While counterintuitive, this is a function of SAM +Economic Models treating federal and state income taxes as operating cash outflows. + +In SAM Economic Models, the PPA price is a fixed input that determines project revenue. A higher PPA price generates +higher taxable income, which in turn increases the project's annual income tax liability (a negative cash flow). Because +the LCOE calculation aggregates all lifetime project costs, including the tax burden, the additional tax costs incurred +from higher revenues result in a higher calculated LCOE. Conversely, a lower PPA price reduces taxable income, lowers +tax liability, and decreases the resulting LCOE. + +### IRR + +.. raw:: html + + + + IRR Sensitivity Analysis Chart + +### NPV + +.. raw:: html + + + + NPV Sensitivity Analysis Chart + +Users may wish to perform their own sensitivity analysis +using [GEOPHIRES's Monte Carlo simulation module](Monte-Carlo-User-Guide.html) or other data analysis tools. + +### Impact of Flow Rate on Project Economics + +Higher flow rate per production well does not necessarily result in improved project economics (e.g. lower LCOE or higher IRR). +Higher flow rates result in increased generation in the short term, but also cause faster thermal decline. +Additional make-up wells may need to be drilled to compensate for increased thermal decline and maintain a minimum net generation (redrilling), +the cost of which may offset incremental revenue from increased generation. +This tradeoff was considered in reservoir modeling that guided Fervo's field implementation (Singh et al., 2025). + +## 100 MWe Model (Phase I) + +The case study also includes a 100 MWe model, `Fervo_Project_Cape-6`, with equivalent capacity to Phase I. +Note that like the 500 MWe model, `Fervo_Project_Cape-6` represents a SOAK project and not the real-world Phase I implementation as built by Fervo. +[Click here](https://gtp.scientificwebservices.com/geophires/?geophires-example-id=Fervo_Project_Cape-6) to view the +100 MWe model in the GEOPHIRES web interface. + +## Previous Versions + +Documentation is available for the following previous case study versions, which are deprecated in favor of the current version: + +1. [Fervo_Project_Cape-4](Fervo_Project_Cape-4.html) + +{# TODO others e.g. Fervo_Project_Cape-3... #} + +--- + +## References + +Akindipe, D. and Witter. E. (2025). "2025 Geothermal Drilling Cost Curves +Update". https://pangea.stanford.edu/ERE/db/GeoConf/papers/SGW/2025/Akindipe.pdf?t=1740084555 + +Baytex Energy. (2024). Eagle Ford Presentation. +https://www.baytexenergy.com/content/uploads/2024/04/24-04-Baytex-Eagle-Ford-Presentation.pdf + +Beckers, K., McCabe, K. (2019) GEOPHIRES v2.0: updated geothermal techno-economic simulation tool. Geotherm Energy +7,5. https://doi.org/10.1186/s40517-019-0119-6 + +Fercho, S., Matson, G., McConville, E., Rhodes, G., Jordan, R., Norbeck, J.. (2024, February 12). +Geology, Temperature, Geophysics, Stress Orientations, and Natural Fracturing in the Milford +Valley, UT Informed by the Drilling Results of the First Horizontal Wells at the Cape Modern +Geothermal Project. https://pangea.stanford.edu/ERE/db/GeoConf/papers/SGW/2024/Fercho.pdf + +Fercho, S., Norbeck, J., Dadi, S., Matson, G., Borell, J., McConville, E., Webb, S., Bowie, C., & Rhodes, G. (2025). +Update on the geology, temperature, fracturing, and resource potential at the Cape Geothermal Project informed by data +acquired from the drilling of additional horizontal EGS wells. Proceedings of the 50th Workshop on Geothermal Reservoir +Engineering, Stanford University, Stanford, CA. https://pangea.stanford.edu/ERE/pdf/IGAstandard/SGW/2025/Fercho.pdf + +Fervo Energy. (2023, September 19). Fervo’s Commercialization Plans for Enhanced Geothermal Systems ( +EGS). https://egi.utah.edu/wp-content/uploads/2023/09/09.45-Emma-McConville-Fervo_EGI_Sept-19-2023.pdf + +Fervo Energy. (2023, September 25). Fervo Energy Breaks Ground on the World’s Largest Next-gen Geothermal Project. +https://fervoenergy.com/fervo-energy-breaks-ground-on-the-worlds-largest-next-gen-geothermal-project/ + +Fervo Energy. (2024, September 10). Fervo Energy’s Record-Breaking Production Results Showcase Rapid Scale Up of +Enhanced +Geothermal. https://www.businesswire.com/news/home/20240910997008/en/Fervo-Energys-Record-Breaking-Production-Results-Showcase-Rapid-Scale-Up-of-Enhanced-Geothermal + +Fervo Energy. (2025, March 31). Geothermal Mythbusting: Water Use and +Impacts. https://fervoenergy.com/geothermal-mythbusting-water-use-and-impacts/ + +Fervo Energy. (2025, April 15). Fervo Energy Announces 31 MW Power Purchase Agreement with Shell +Energy. https://fervoenergy.com/fervo-energy-announces-31-mw-power-purchase-agreement-with-shell-energy/ + +Fervo Energy (2025, June 11). Fervo Energy Secures $206 Million In New Financing To Accelerate Cape Station Development. +https://fervoenergy.com/fervo-secures-new-financing-to-accelerate-development/ + +Gradl, C. (2018). Review of Recent Unconventional Completion Innovations and their Applicability to EGS Wells. Stanford +Geothermal Workshop. +https://pangea.stanford.edu/ERE/pdf/IGAstandard/SGW/2018/Gradl.pdf + +Horne, R., Genter, A., McClure, M. et al. (2025) Enhanced geothermal systems for clean firm energy generation. Nat. Rev. +Clean Technol. 1, 148–160. https://doi.org/10.1038/s44359-024-00019-9 + +Jacobs, Trent. (2024, September 16). Fervo and FORGE Report Breakthrough Test Results, Signaling More Progress for +Enhanced +Geothermal. https://jpt.spe.org/fervo-and-forge-report-breakthrough-test-results-signaling-more-progress-for-enhanced-geothermal + +Jacobs, Trent. (2025, September 5). Baker Hughes Nabs Award for Next Phase of Fervo Energy's Geothermal Power Plant in +Utah. +https://jpt.spe.org/baker-hughes-nabs-award-for-next-phase-of-fervo-energygeothermal-power-plant-in-utah + +Ko, S., Ghassemi, A., & Uddenberg, M. (2023). Selection and Testing of Proppants for EGS. +Proceedings, 48th Workshop on Geothermal Reservoir Engineering, Stanford University, Stanford, California. +https://pangea.stanford.edu/ERE/db/GeoConf/papers/SGW/2023/Ko.pdf + +Latimer, T. (2025, February 12). Catching up with enhanced geothermal (D. Roberts, +Interviewer). https://www.volts.wtf/p/catching-up-with-enhanced-geothermal + +Matson, M. (2024, September 11). Fervo Energy Technology Day 2024: Entering "the Geothermal Decade" with Next-Generation +Geothermal +Energy. https://www.linkedin.com/pulse/fervo-energy-technology-day-2024-entering-geothermal-decade-matson-n4stc/ + +McClure, M. (2024, September 12). Digesting the Bonkers, Incredible, Off-the-Charts, Spectacular Results from the Fervo +and FORGE Enhanced Geothermal Projects. ResFrac Corporation Blog. +https://www.resfrac.com/blog/digesting-the-bonkers-incredible-off-the-charts-spectacular-results-from-the-fervo-and-forge-enhanced-geothermal-projects + +NCEI. US Climate +Normals. https://www.ncei.noaa.gov/access/us-climate-normals/#dataset=normals-annualseasonal&timeframe=30&station=USC00425654 + +NREL. (2024). Annual Technology Baseline: Geothermal (2024). +https://atb.nrel.gov/electricity/2024/geothermal + +NREL. (2025, February 26). Annual Technology Baseline: Geothermal (2024b). +https://atb.nrel.gov/electricity/2024b/geothermal + +Norbeck, J., Gradl, C., Latimer, T. (2024, September 10). Deployment of Enhanced Geothermal System Technology Leads to +Rapid Cost Reductions and Performance Improvements. https://doi.org/10.31223/X5VH8C + +Norbeck J., Latimer T. (2023). Commercial-Scale Demonstration of a First-of-a-Kind Enhanced Geothermal +System. https://doi.org/10.31223/X52X0B + +Quantum Proppant Technologies. (2020). Well Completion Technology. World +Oil. https://quantumprot.com/uploads/images/2b8583e8ce8038681a19d5ad1314e204.pdf + +Shiozawa, S., & McClure, M. (2014). EGS Designs with Horizontal Wells, Multiple Stages, and Proppant. ResFrac. +https://www.resfrac.com/wp-content/uploads/2024/07/Shiozawa.pdf + +Singh, A., Galban, G., McClure, M. (2025, June 9). +Proceedings of the 2025 Unconventional Resources Technology Conference. +https://www.resfrac.com/wp-content/uploads/2025/06/Singh-2025-Fervo-Project-Cape.pdf + +Southern Utah University. (2024, October 23). Fervo Energy, Southern Utah University, and Elemental Impact Launch +Geothermal Drilling & Completions Apprenticeship Program. +https://www.suu.edu/news/2024/10/geothermal-energy-joint-campaign.html + +Turboden. (2025, October 2). Turboden selected to deliver 180 MW of Fervo’s Gen 2 ORC Power Plants at Cape Station in +Utah. https://www.turboden.com/company/media/press/press-releases/4881/turboden-selected-to-deliver-180-mw-of-fervos-gen-2-orc-power-plants-at-cape-station-in-utah + +U.S. Department of the Interior Bureau of Land Management. (2024, October). +Finding of No Significant Impact and Decision Record DOI-BLM-UT-C010-2024-0018-EA. +https://eplanning.blm.gov/public_projects/2033002/200625761/20120795/251020775/DOI-BLM-UT-C010-2024-0018-EA_FONSI_DR_%20Fervo%20EA_signed.pdf + +US DOE. (2021). Combined Heat and Power Technology Fact Sheet Series: Waste Heat to +Power. https://betterbuildingssolutioncenter.energy.gov/sites/default/files/attachments/Waste_Heat_to_Power_Fact_Sheet.pdf + +Xing, P., England, K., Moore, J., McLennan, J. (2025, February 10). +Analysis of the 2024 Circulation Tests at Utah FORGE and the Response of Fiber Optic Sensing +Data. +https://pangea.stanford.edu/ERE/pdf/IGAstandard/SGW/2025/Xing2.pdf + +Yearsley, E., Kombrink, H. (2024, November 6). +A critical look at Fervo dataset suggests lower output. +https://geoexpro.com/a-critical-look-at-fervo-dataset-suggests-lower-output/ + +Yusifov, M., & Enriquez, N. (2025, July). From Core to Code: Powering the Al Revolution with Geothermal Energy. +Project InnerSpace. https://projectinnerspace.org/resources/Powering-the-AI-Revolution.pdf + +--- + +## Footnotes diff --git a/docs/GEOPHIRES-Examples.md b/docs/GEOPHIRES-Examples.md index 02ac82876..cb549abb9 100644 --- a/docs/GEOPHIRES-Examples.md +++ b/docs/GEOPHIRES-Examples.md @@ -7,4 +7,4 @@ or in the [web interface](https://gtp.scientificwebservices.com/geophires) under ## Case Study: 500 MWe EGS Project Modeled on Fervo Cape Station -See [Case Study: 500 MWe EGS Project Modeled on Fervo Cape Station](Fervo_Project_Cape-4.html). +See documentation: [Case Study: 500 MWe EGS Project Modeled on Fervo Cape Station](Fervo_Project_Cape-5.html). diff --git a/docs/Monte-Carlo-User-Guide.md b/docs/Monte-Carlo-User-Guide.md index 816de8c76..54dc6b61e 100644 --- a/docs/Monte-Carlo-User-Guide.md +++ b/docs/Monte-Carlo-User-Guide.md @@ -1,4 +1,4 @@ -# GEOPHIRES Monte Carlo User Guide +# Monte Carlo User Guide ## Example Setup diff --git a/docs/SAM-Economic-Models.md b/docs/SAM-Economic-Models.md index b71470518..326679507 100644 --- a/docs/SAM-Economic-Models.md +++ b/docs/SAM-Economic-Models.md @@ -121,9 +121,9 @@ Output Parameters: ### Case Study: 500 MWe EGS Project Modeled on Fervo Cape Station -[Web interface link](https://gtp.scientificwebservices.com/geophires/?geophires-example-id=Fervo_Project_Cape-4) +[Web interface link](https://gtp.scientificwebservices.com/geophires/?geophires-example-id=Fervo_Project_Cape-5) -See [Case Study: 500 MWe EGS Project Modeled on Fervo Cape Station](Fervo_Project_Cape-4.html). +Documentation: [Case Study: 500 MWe EGS Project Modeled on Fervo Cape Station](Fervo_Project_Cape-4.html). ### SAM Single Owner PPA diff --git a/docs/_images/fervo_project_cape-5-net-power-production.png b/docs/_images/fervo_project_cape-5-net-power-production.png new file mode 100644 index 000000000..6d2f4ff2f Binary files /dev/null and b/docs/_images/fervo_project_cape-5-net-power-production.png differ diff --git a/docs/_images/fervo_project_cape-5-production-temperature-drawdown.png b/docs/_images/fervo_project_cape-5-production-temperature-drawdown.png new file mode 100644 index 000000000..5d624eedf Binary files /dev/null and b/docs/_images/fervo_project_cape-5-production-temperature-drawdown.png differ diff --git a/docs/_images/fervo_project_cape-5-production-temperature.png b/docs/_images/fervo_project_cape-5-production-temperature.png new file mode 100644 index 000000000..d0931cd65 Binary files /dev/null and b/docs/_images/fervo_project_cape-5-production-temperature.png differ diff --git a/docs/_images/fervo_project_cape-5-sensitivity-analysis-irr.png b/docs/_images/fervo_project_cape-5-sensitivity-analysis-irr.png new file mode 100644 index 000000000..7fa387d25 Binary files /dev/null and b/docs/_images/fervo_project_cape-5-sensitivity-analysis-irr.png differ diff --git a/docs/_images/fervo_project_cape-5-sensitivity-analysis-irr.svg b/docs/_images/fervo_project_cape-5-sensitivity-analysis-irr.svg new file mode 100644 index 000000000..3d9883dbb --- /dev/null +++ b/docs/_images/fervo_project_cape-5-sensitivity-analysis-irr.svg @@ -0,0 +1,2952 @@ + + + + + + + + 2026-01-16T11:45:33.862062 + image/svg+xml + + + Matplotlib v3.10.8, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_images/fervo_project_cape-5-sensitivity-analysis-lcoe.png b/docs/_images/fervo_project_cape-5-sensitivity-analysis-lcoe.png new file mode 100644 index 000000000..35f5a7567 Binary files /dev/null and b/docs/_images/fervo_project_cape-5-sensitivity-analysis-lcoe.png differ diff --git a/docs/_images/fervo_project_cape-5-sensitivity-analysis-lcoe.svg b/docs/_images/fervo_project_cape-5-sensitivity-analysis-lcoe.svg new file mode 100644 index 000000000..3a917a14b --- /dev/null +++ b/docs/_images/fervo_project_cape-5-sensitivity-analysis-lcoe.svg @@ -0,0 +1,3129 @@ + + + + + + + + 2026-01-16T11:45:36.341692 + image/svg+xml + + + Matplotlib v3.10.8, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_images/fervo_project_cape-5-sensitivity-analysis-project_npv.png b/docs/_images/fervo_project_cape-5-sensitivity-analysis-project_npv.png new file mode 100644 index 000000000..b4d98bed7 Binary files /dev/null and b/docs/_images/fervo_project_cape-5-sensitivity-analysis-project_npv.png differ diff --git a/docs/_images/fervo_project_cape-5-sensitivity-analysis-project_npv.svg b/docs/_images/fervo_project_cape-5-sensitivity-analysis-project_npv.svg new file mode 100644 index 000000000..ab2d4a4aa --- /dev/null +++ b/docs/_images/fervo_project_cape-5-sensitivity-analysis-project_npv.svg @@ -0,0 +1,3328 @@ + + + + + + + + 2026-01-16T11:45:36.155664 + image/svg+xml + + + Matplotlib v3.10.8, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_images/singh-et-al-2025_wht-700-ft-bench-spacing.png b/docs/_images/singh-et-al-2025_wht-700-ft-bench-spacing.png new file mode 100644 index 000000000..d027727ae Binary files /dev/null and b/docs/_images/singh-et-al-2025_wht-700-ft-bench-spacing.png differ diff --git a/docs/_images/singh_et_al_base_simulation-production-temperature.png b/docs/_images/singh_et_al_base_simulation-production-temperature.png new file mode 100644 index 000000000..f536c4f47 Binary files /dev/null and b/docs/_images/singh_et_al_base_simulation-production-temperature.png differ diff --git a/docs/conf.py b/docs/conf.py index 26674a995..e2a275de8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,7 @@ year = '2025' author = 'NREL' copyright = f'{year}, {author}' -version = release = '3.10.24' +version = release = '3.11.2' pygments_style = 'trac' templates_path = ['./templates'] @@ -38,6 +38,13 @@ html_use_smartypants = True html_last_updated_fmt = '%b %d, %Y' html_split_index = False +# Add jQuery as the first script - ensures it's available for sidebar.js +html_js_files = [ + ( + 'https://code.jquery.com/jquery-3.7.1.min.js', + {'integrity': 'sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=', 'crossorigin': 'anonymous'}, + ), +] html_sidebars = { '**': ['searchbox.html', 'globaltoc.html', 'sourcelink.html'], } diff --git a/docs/index.rst b/docs/index.rst index b145a94c3..5461acb6a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,8 +8,8 @@ Contents overview Theoretical-Basis-for-GEOPHIRES GEOPHIRES-Examples - Monte-Carlo-User-Guide SAM-Economic-Models + Monte-Carlo-User-Guide How-to-extend-GEOPHIRES-X .. toctree:: diff --git a/docs/requirements.txt b/docs/requirements.txt index 1b8df5e70..0f4a325d8 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,4 @@ sphinx>=1.3 sphinx-py3doc-enhanced-theme m2r2 +Jinja2 diff --git a/docs/templates/layout.html b/docs/templates/layout.html index 681f7aefb..024fe5301 100644 --- a/docs/templates/layout.html +++ b/docs/templates/layout.html @@ -1,10 +1,21 @@ {% extends "!layout.html" %} {%- block extrahead %} + + {% endblock %} diff --git a/docs/watch_docs.py b/docs/watch_docs.py deleted file mode 100755 index 130ef909e..000000000 --- a/docs/watch_docs.py +++ /dev/null @@ -1,80 +0,0 @@ -#!python - -import os -import subprocess -import time - - -def get_file_states(directory): - """ - Returns a dictionary of file paths and their modification times. - """ - states = {} - for root, _, files in os.walk(directory): - for filename in files: - # Ignore hidden files, temporary editor files, and this script itself - # fmt:off - if (filename.startswith('.') or - filename.endswith('~') or filename == os.path.basename(__file__)): # noqa: PTH119 - # fmt:on - continue - - filepath = os.path.join(root, filename) - - # Avoid watching build directories if they are generated inside docs/ - if '_build' in filepath or 'build' in filepath: - continue - - try: - states[filepath] = os.path.getmtime(filepath) # noqa: PTH204 - except OSError: - pass - return states - - -def main(): - # Determine paths relative to this script - script_dir = os.path.dirname(os.path.abspath(__file__)) - project_root = os.path.dirname(script_dir) - - # Watch the directory where the script is located (docs/) - watch_dir = script_dir - - command = ['tox', '-e', 'docs'] - poll_interval = 2 # Seconds - - print(f"Watching '{watch_dir}' for changes...") - print(f"Project root determined as: '{project_root}'") - print(f"Command to run: {' '.join(command)}") - print('Press Ctrl+C to stop.') - - # Initial state - last_states = get_file_states(watch_dir) - - try: - while True: - time.sleep(poll_interval) - current_states = get_file_states(watch_dir) - - if current_states != last_states: - print('\n[Change Detected] Running docs build...') - - try: - # Run tox from the project root so it finds tox.ini - subprocess.run(command, cwd=project_root, check=False) # noqa: S603 - except FileNotFoundError: - print("Error: 'tox' command not found. Please ensure tox is installed.") - except Exception as e: - print(f'An error occurred: {e}') - - print(f"\nWaiting for further changes in '{watch_dir}'...") - - # Update state to the current state - last_states = get_file_states(watch_dir) - - except KeyboardInterrupt: - print('\nWatcher stopped.') - - -if __name__ == '__main__': - main() diff --git a/setup.py b/setup.py index 7aa1b7fe8..7ec02487e 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(*names, **kwargs): setup( name='geophires-x', - version='3.10.24', + version='3.11.2', license='MIT', description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.', long_description='{}\n{}'.format( diff --git a/src/geophires_docs/__init__.py b/src/geophires_docs/__init__.py new file mode 100644 index 000000000..1851f5e73 --- /dev/null +++ b/src/geophires_docs/__init__.py @@ -0,0 +1,77 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import Any + +from geophires_x_client import GeophiresInputParameters + + +def _get_file_path(file_name) -> Path: + return Path(os.path.join(os.path.abspath(os.path.dirname(__file__)), file_name)) + + +def _get_project_root() -> Path: + return _get_file_path('../..') + + +def _get_fpc5_input_file_path(project_root: Path | None = None) -> Path: + if project_root is None: + project_root = _get_project_root() + return project_root / 'tests/examples/Fervo_Project_Cape-5.txt' + + +def _get_fpc5_result_file_path(project_root: Path | None = None) -> Path: + if project_root is None: + project_root = _get_project_root() + return project_root / 'tests/examples/Fervo_Project_Cape-5.out' + + +_PROJECT_ROOT: Path = _get_project_root() +_FPC5_INPUT_FILE_PATH: Path = _get_fpc5_input_file_path() +_FPC5_RESULT_FILE_PATH: Path = _get_fpc5_result_file_path() + + +def _get_logger(_name_: str) -> Any: + # TODO consolidate _get_logger methods into a commonly accessible utility + + # sh = logging.StreamHandler(sys.stdout) + # sh.setLevel(logging.INFO) + # sh.setFormatter(logging.Formatter(fmt='[%(asctime)s][%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S')) + # + # ret = logging.getLogger(__name__) + # ret.addHandler(sh) + # return ret + + # noinspection PyMethodMayBeStatic + class _PrintLogger: + def info(self, msg): + print(f'[INFO] {msg}') + + def error(self, msg): + print(f'[ERROR] {msg}') + + return _PrintLogger() + + +def _get_input_parameters_dict( # TODO consolidate with FervoProjectCape5TestCase._get_input_parameters + _params: GeophiresInputParameters, include_parameter_comments: bool = False, include_line_comments: bool = False +) -> dict[str, Any]: + comment_idx = 0 + ret: dict[str, Any] = {} + for line in _params.as_text().split('\n'): + parts = line.strip().split(', ') # TODO generalize for array-type params + field = parts[0].strip() + if len(parts) >= 2 and not field.startswith('#'): + fieldValue = parts[1].strip() + if include_parameter_comments and len(parts) > 2: + fieldValue += ', ' + (', '.join(parts[2:])).strip() + ret[field] = fieldValue.strip() + + if include_line_comments and field.startswith('#'): + ret[f'_COMMENT-{comment_idx}'] = line.strip() + comment_idx += 1 + + # TODO preserve newlines + + return ret diff --git a/src/geophires_docs/__main__.py b/src/geophires_docs/__main__.py new file mode 100644 index 000000000..545afd1aa --- /dev/null +++ b/src/geophires_docs/__main__.py @@ -0,0 +1,4 @@ +if __name__ == '__main__': + from geophires_docs import generate_fervo_project_cape_5_docs + + generate_fervo_project_cape_5_docs.generate_fervo_project_cape_5_docs() diff --git a/src/geophires_docs/generate_fervo_project_cape_5_docs.py b/src/geophires_docs/generate_fervo_project_cape_5_docs.py new file mode 100644 index 000000000..1c9f0fcb3 --- /dev/null +++ b/src/geophires_docs/generate_fervo_project_cape_5_docs.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from typing import Any + +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 generate_fervo_project_cape_5_md +from geophires_docs.generate_fervo_project_cape_5_graphs import generate_fervo_project_cape_5_graphs +from geophires_x_client import GeophiresInputParameters +from geophires_x_client import GeophiresXClient +from geophires_x_client import GeophiresXResult +from geophires_x_client import ImmutableGeophiresInputParameters + +_SINGH_ET_AL_BASE_SIMULATION_PARAMETERS: dict[str, Any] = { + 'Number of Production Wells': 4, + 'Number of Injection Wells per Production Well': '1.2, -- The Singh et al. scenario has 4 producers and 6 injectors. ' + 'We model one fewer injector here to account for the combined injection rate being lower for ' + 'the higher bench separation cases.', + 'Maximum Drawdown': '1, -- Redrilling not modeled in Singh et al. scenario. ' + '(The equivalent GEOPHIRES simulation allows drawdown to reach up to 100% without triggering redrilling)', + 'Plant Lifetime': 15, +} + + +# fmt:off +def get_singh_et_al_base_simulation_result(base_input_params: GeophiresInputParameters) \ + -> tuple[GeophiresInputParameters,GeophiresXResult]: + singh_et_al_base_simulation_input_params = ImmutableGeophiresInputParameters( + from_file_path=base_input_params.as_file_path(), + params=_SINGH_ET_AL_BASE_SIMULATION_PARAMETERS, + ) + # fmt:on + + singh_et_al_base_simulation_result = GeophiresXClient().get_geophires_result( + singh_et_al_base_simulation_input_params + ) + + return singh_et_al_base_simulation_input_params, singh_et_al_base_simulation_result + + +def generate_fervo_project_cape_5_docs(): + input_params: GeophiresInputParameters = ImmutableGeophiresInputParameters( + from_file_path=_FPC5_INPUT_FILE_PATH + ) + result = GeophiresXResult(_FPC5_RESULT_FILE_PATH) + + singh_et_al_base_simulation: tuple[GeophiresInputParameters,GeophiresXResult] = get_singh_et_al_base_simulation_result(input_params) + + generate_fervo_project_cape_5_graphs( + (input_params, result), + singh_et_al_base_simulation, + _PROJECT_ROOT / 'docs/_images' + ) + + generate_fervo_project_cape_5_md.generate_fervo_project_cape_5_md( + input_params, + result, + _SINGH_ET_AL_BASE_SIMULATION_PARAMETERS + ) + + +if __name__ == '__main__': + generate_fervo_project_cape_5_docs() diff --git a/src/geophires_docs/generate_fervo_project_cape_5_graphs.py b/src/geophires_docs/generate_fervo_project_cape_5_graphs.py new file mode 100644 index 000000000..8bf156127 --- /dev/null +++ b/src/geophires_docs/generate_fervo_project_cape_5_graphs.py @@ -0,0 +1,294 @@ +from __future__ import annotations + +import json +from pathlib import Path + +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_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 + +_log = _get_logger(__name__) + + +def _get_full_net_production_profile(input_and_result: tuple[GeophiresInputParameters, GeophiresXResult]): + return _get_full_profile(input_and_result, 'Net Electricity Production') + + +def _get_full_production_temperature_profile(input_and_result: tuple[GeophiresInputParameters, GeophiresXResult]): + return _get_full_profile( + input_and_result, + #'Produced Temperature' + 'Reservoir Temperature History', + ) + + +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 generate_net_power_graph( + # result: GeophiresXResult, + input_and_result: tuple[GeophiresInputParameters, GeophiresXResult], + output_dir: Path, + filename: str = 'fervo_project_cape-5-net-power-production.png', +) -> str: + """ + Generate a graph of time vs net power production and save it to the output directory. + """ + _log.info('Generating net power production graph...') + + profile = _get_full_net_production_profile(input_and_result) + time_steps_per_year = int(_get_input_parameters_dict(input_and_result[0])['Time steps per year']) + + # profile is a list of PlainQuantity values with time_steps_per_year datapoints per year + # Convert to numpy arrays for plotting + net_power = np.array([p.magnitude for p in profile]) + + # Generate time values: each datapoint represents 1/time_steps_per_year of a year + # Starting from year 1 (first operational year) + years = np.array([(i + 1) / time_steps_per_year for i in range(len(profile))]) + + # Create the figure + fig, ax = plt.subplots(figsize=(10, 6)) + + # Plot the data + ax.plot(years, net_power, color='#3399e6', linewidth=2, marker='o', markersize=4) + + # Set labels and title + ax.set_xlabel('Time (Years since COD)', fontsize=12) + ax.set_ylabel('Net Power Production (MW)', fontsize=12) + ax.set_title('Net Power Production Over Project Lifetime', fontsize=14) + + # Set axis limits + ax.set_xlim(years.min(), years.max()) + ax.set_ylim(490, 610) + + # Add horizontal reference lines + ax.axhline(y=500, color='#e69500', linestyle='--', linewidth=1.5, alpha=0.8) + ax.text( + years.max() * 0.98, 498, 'PPA Minimum Production Requirement', ha='right', va='top', fontsize=9, color='#e69500' + ) + + ax.axhline(y=600, color='#33a02c', linestyle='--', linewidth=1.5, alpha=0.8) + ax.text( + years.max() * 0.98, + 602, + 'Gross Maximum (Combined nameplate capacity of individual ORCs)', + ha='right', + va='bottom', + fontsize=9, + color='#33a02c', + ) + + # Add grid for better readability + ax.grid(True, linestyle='--', alpha=0.7) + + # Ensure the output directory exists + output_dir.mkdir(parents=True, exist_ok=True) + + # Save the figure + save_path = output_dir / filename + plt.savefig(save_path, dpi=150, bbox_inches='tight') + plt.close(fig) + + _log.info(f'✓ Generated {save_path}') + return filename + + +def generate_production_temperature_and_drawdown_graph( + input_and_result: tuple[GeophiresInputParameters, GeophiresXResult], + output_dir: Path, + filename: str = 'fervo_project_cape-5-production-temperature.png', +) -> str: + """ + Generate a graph of time vs production temperature with a horizontal line + showing the temperature threshold at which maximum drawdown is reached. + """ + _log.info('Generating production temperature graph...') + + temp_profile = _get_full_production_temperature_profile(input_and_result) + input_params_dict = _get_input_parameters_dict(input_and_result[0]) + time_steps_per_year = int(input_params_dict['Time steps per year']) + + # Get maximum drawdown from input parameters (as a decimal, e.g., 0.03 for 3%) + max_drawdown_str = str(input_params_dict.get('Maximum Drawdown')) + # Handle case where value might have a comment after it + max_drawdown = float(max_drawdown_str.split(',')[0].strip()) + + # Convert to numpy arrays + temperatures_celsius = np.array([p.magnitude for p in temp_profile]) + + # Calculate the temperature at maximum drawdown threshold + # Drawdown = (T_initial - T_threshold) / T_initial + # So: T_threshold = T_initial * (1 - max_drawdown) + initial_temp = temperatures_celsius[0] + max_drawdown_temp = initial_temp * (1 - max_drawdown) + + # Generate time values + years = np.array([(i + 1) / time_steps_per_year for i in range(len(temp_profile))]) + + # Colors + COLOR_TEMPERATURE = '#e63333' + COLOR_THRESHOLD = '#e69500' + + # Create the figure + fig, ax = plt.subplots(figsize=(10, 6)) + + # Plot temperature + ax.plot(years, temperatures_celsius, color=COLOR_TEMPERATURE, linewidth=2, label='Production Temperature') + ax.set_xlabel('Time (Years since COD)', fontsize=12) + ax.set_ylabel('Production Temperature (°C)', fontsize=12) + ax.set_xlim(years.min(), years.max()) + + # Add horizontal line for maximum drawdown threshold + ax.axhline(y=max_drawdown_temp, color=COLOR_THRESHOLD, linestyle='--', linewidth=1.5, alpha=0.8) + max_drawdown_pct = max_drawdown * 100 + ax.text( + years.max() * 0.98, + max_drawdown_temp - 0.5, + f'Redrilling Threshold ({max_drawdown_pct:.1f}% drawdown = {max_drawdown_temp:.1f}°C)', + ha='right', + va='top', + fontsize=9, + color=COLOR_THRESHOLD, + ) + + # Title + ax.set_title('Production Temperature Over Project Lifetime', fontsize=14) + + # Add grid + ax.grid(True, linestyle='--', alpha=0.7) + + # Legend + ax.legend(loc='best') + + # Ensure the output directory exists + output_dir.mkdir(parents=True, exist_ok=True) + + # Save the figure + save_path = output_dir / filename + plt.savefig(save_path, dpi=150, bbox_inches='tight') + plt.close(fig) + + _log.info(f'✓ Generated {save_path}') + return filename + + +def generate_production_temperature_graph( + result: GeophiresXResult, output_dir: Path, filename: str = 'fervo_project_cape-5-production-temperature.png' +) -> str: + """ + Generate a graph of time vs production temperature and save it to the output directory. + """ + _log.info('Generating production temperature graph...') + + # Extract data from power generation profile + profile = result.power_generation_profile + headers = profile[0] + data = profile[1:] + + # Find the indices for YEAR and THERMAL DRAWDOWN columns + year_idx = headers.index('YEAR') + # Look for production temperature column - could be labeled differently + temp_idx = headers.index('GEOFLUID TEMPERATURE (degC)') + + # Extract years and temperature values + years = np.array([row[year_idx] for row in data]) + temperatures_celsius = np.array([row[temp_idx] for row in data]) + + # Convert Celsius to Fahrenheit + temperatures_fahrenheit = temperatures_celsius * 9 / 5 + 32 + + # Create the figure - taller than wide (portrait orientation) + fig, ax = plt.subplots(figsize=(6, 8)) + + # Plot the data - just the curve, no markers + ax.plot(years, temperatures_fahrenheit, color='#e63333', linewidth=2) + + # Set labels and title + ax.set_xlabel('Simulation time (Years)', fontsize=12) + ax.set_ylabel('Wellhead temperature (°F)', fontsize=12) + # ax.set_title('Production Temperature Over Project Lifetime', fontsize=14) + + # Set axis limits + ax.set_xlim(years.min(), years.max()) + ax.set_ylim(200, 450) + + # Set y-axis ticks every 50 degrees, with 400 explicitly labeled but not 200 or 450 + ax.set_yticks([250, 300, 350, 400]) + + # Add grid for better readability + ax.grid(True, linestyle='--', alpha=0.7) + + # Ensure the output directory exists + output_dir.mkdir(parents=True, exist_ok=True) + + # Save the figure + save_path = output_dir / filename + plt.savefig(save_path, dpi=150, bbox_inches='tight') + plt.close(fig) + + _log.info(f'✓ Generated {save_path}') + return filename + + +def generate_fervo_project_cape_5_graphs( + base_case: tuple[GeophiresInputParameters, GeophiresXResult], + singh_et_al_base_simulation: tuple[GeophiresInputParameters, GeophiresXResult], + output_dir: Path, +) -> None: + # base_case_input_params: GeophiresInputParameters = base_case[0] + # base_case_result: GeophiresXResult = base_case[1] + + generate_net_power_graph(base_case, output_dir) + generate_production_temperature_and_drawdown_graph(base_case, output_dir) + + if singh_et_al_base_simulation is not None: + singh_et_al_base_simulation_result: GeophiresXResult = singh_et_al_base_simulation[1] + + # generate_net_power_graph( + # singh_et_al_base_simulation_result, output_dir, + # filename='singh_et_al_base_simulation-net-power-production.png' + # ) + + generate_production_temperature_graph( + singh_et_al_base_simulation_result, + output_dir, + filename='singh_et_al_base_simulation-production-temperature.png', + ) + + +if __name__ == '__main__': + docs_dir = _PROJECT_ROOT / 'docs' + images_dir = docs_dir / '_images' + + input_params_: GeophiresInputParameters = ImmutableGeophiresInputParameters(from_file_path=_FPC5_INPUT_FILE_PATH) + + result_ = GeophiresXResult(_FPC5_RESULT_FILE_PATH) + + generate_fervo_project_cape_5_graphs( + (input_params_, result_), None, images_dir # TODO configure (for local development) + ) diff --git a/src/geophires_docs/generate_fervo_project_cape_5_md.py b/src/geophires_docs/generate_fervo_project_cape_5_md.py new file mode 100755 index 000000000..266b1378a --- /dev/null +++ b/src/geophires_docs/generate_fervo_project_cape_5_md.py @@ -0,0 +1,472 @@ +#!python +""" +Script to generate Fervo_Project_Cape-5.md from its jinja template. +This ensures the markdown documentation stays in sync with actual GEOPHIRES results. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +import numpy as np +from jinja2 import Environment +from jinja2 import FileSystemLoader +from pint.facets.plain import PlainQuantity + +from geophires_docs import _PROJECT_ROOT +from geophires_docs import _get_fpc5_input_file_path +from geophires_docs import _get_fpc5_result_file_path +from geophires_docs import _get_input_parameters_dict +from geophires_docs import _get_logger +from geophires_docs import _get_project_root +from geophires_x.GeoPHIRESUtils import is_int +from geophires_x.GeoPHIRESUtils import sig_figs +from geophires_x_client import GeophiresInputParameters +from geophires_x_client import GeophiresXResult +from geophires_x_client import ImmutableGeophiresInputParameters + +# Module-level variable to hold the current project root for schema access +_current_project_root: Path | None = None + +_log = _get_logger(__name__) + + +def _get_schema() -> dict[str, Any]: + project_root = _current_project_root if _current_project_root is not None else _get_project_root() + schema_file = project_root / 'src/geophires_x_schema_generator/geophires-request.json' + with open(schema_file, encoding='utf-8') as f: + return json.loads(f.read()) + + +def _get_parameter_schema(param_name: str) -> dict[str, Any]: + return _get_schema()['properties'][param_name] + + +def _get_parameter_schema_type(param_name: str) -> dict[str, Any]: + return _get_parameter_schema(param_name)['type'] + + +def _get_parameter_category(param_name: str) -> str: + return _get_parameter_schema(param_name)['category'] + + +def _get_parameter_units(param_name: str) -> str | None: + unit = _get_schema()['properties'][param_name]['units'] + + if unit == '': + return 'dimensionless' + + return unit + + +def _get_unit_display(parameter_units_from_schema: str) -> str: + if parameter_units_from_schema is None: + return '' + + display_unit_prefix = ( + ' ' + if not (parameter_units_from_schema and any(it in parameter_units_from_schema for it in ['%', 'USD', 'MUSD'])) + else '' + ) + display_unit = parameter_units_from_schema + for replacement in [ + ('kilometer', 'km'), + ('degC', '℃'), + ('meter', 'm'), + ('m**3', 'm³'), + ('m**2', 'm²'), + ('MUSD', 'M'), + ('USD', ''), + ]: + display_unit = display_unit.replace(replacement[0], replacement[1]) + + return f'{display_unit_prefix}{display_unit}' + + +def generate_fpc_reservoir_parameters_table_md(input_params: GeophiresInputParameters) -> str: + params_to_exclude = [ + 'Maximum Temperature', + 'Reservoir Porosity', + 'Reservoir Volume Option', + ] + + return get_fpc_category_parameters_table_md( + input_params, + 'Reservoir', + params_to_exclude, + ) + + +def generate_fpc_well_bores_parameters_table_md(input_params: GeophiresInputParameters) -> str: + return get_fpc_category_parameters_table_md( + input_params, + 'Well Bores', + parameters_to_exclude=['Number of Multilateral Sections'], + ) + + +def generate_fpc_surface_plant_parameters_table_md(input_params: GeophiresInputParameters) -> str: + return get_fpc_category_parameters_table_md( + input_params, + 'Surface Plant', + parameters_to_exclude=['End-Use Option', 'Construction Years'], + ) + + +def generate_fpc_construction_parameters_table_md(input_params: GeophiresInputParameters) -> str: + input_params_dict = _get_input_parameters_dict( + input_params, include_parameter_comments=True, include_line_comments=True + ) + schedule_param_name = 'Construction CAPEX Schedule' + construction_input_params = {} + for construction_param in ['Construction Years', schedule_param_name]: + construction_input_params[construction_param] = input_params_dict[construction_param] + + # Comment hardcoded here for now because handling of array parameters with comments might be buggy in client or + # web interface... + schedule_param_comment = ( + 'Array of fractions of overnight capital cost expenditure for each year, starting with ' + 'lower costs during initial years for exploration and increasing to higher costs during ' + 'later years as buildout progresses.' + ) + construction_input_params[schedule_param_name] = ( + f'{construction_input_params[schedule_param_name]}' f', -- {schedule_param_comment}' + ) + + return get_fpc_category_parameters_table_md( + ImmutableGeophiresInputParameters(params=construction_input_params), None + ) + + +def generate_fpc_economics_parameters_table_md(input_params: GeophiresInputParameters) -> str: + return get_fpc_category_parameters_table_md( + input_params, + 'Economics', + parameters_to_exclude=[ + 'Ending Electricity Sale Price', + 'Electricity Escalation Start Year', + 'Construction CAPEX Schedule', + 'Time steps per year', + 'Print Output to Console', + ], + ) + + +def get_fpc_category_parameters_table_md( + input_params: GeophiresInputParameters, category_name: str | None, parameters_to_exclude: list[str] | None = None +) -> str: + if parameters_to_exclude is None: + parameters_to_exclude = [] + + input_params_dict = _get_input_parameters_dict( + input_params, include_parameter_comments=True, include_line_comments=True + ) + + non_breaking_space = '\xa0' + + # noinspection MarkdownIncorrectTableFormatting + table_md = f""" +| Parameter | Input{non_breaking_space}Value | Comment | +|-------------------|-------------------------------------------|-------------| +""" + + table_entries = [] + for param_name, param_val_comment in input_params_dict.items(): + if param_name.startswith(('#', '_COMMENT-')): + continue + + if param_name in parameters_to_exclude: + continue + + category = _get_parameter_category(param_name) + if category_name is None or category == category_name: + param_val_comment_split = param_val_comment.split( + # ',', + ',' if _get_parameter_schema_type(param_name) != 'array' else ', ', + maxsplit=1, + ) + + param_val = param_val_comment_split[0] + + param_comment = ( + param_val_comment_split[1].replace('-- ', '') if len(param_val_comment_split) > 1 else ' .. N/A ' + ) + param_unit = _get_parameter_units(param_name) + if param_unit == 'dimensionless': + param_unit_display = '%' + param_val = sig_figs( + PlainQuantity(float(param_val), 'dimensionless').to('percent').magnitude, + 10, # trim floating point errors + ) + elif ' ' in param_val: + param_val_split = param_val.split(' ', maxsplit=1) + param_val = param_val_split[0] + param_unit_display = _get_unit_display(param_val_split[1]) + else: + param_unit_display = _get_unit_display(param_unit) + + param_unit_display_prefix = '$' if param_unit and 'USD' in param_unit else '' + + if is_int(param_val): + param_val = int(param_val) + + param_schema = _get_parameter_schema(param_name) + if param_schema and 'enum_values' in param_schema: + for enum_value in param_schema['enum_values']: + if enum_value['int_value'] == param_val: + enum_display = enum_value['value'] + # param_val = f'{param_val} ({enum_display})' + param_val = enum_display + break + + param_name_display = param_name.replace(' ', non_breaking_space, 2) + table_entries.append( + [param_name_display, f'{param_unit_display_prefix}{param_val}{param_unit_display}', param_comment] + ) + + for table_entry in table_entries: + table_md += f'| {table_entry[0]} | {table_entry[1]} | {table_entry[2]} |\n' + + return table_md.strip() + + +def _q(d: dict[str, Any]) -> PlainQuantity: + return PlainQuantity(d['value'], d['unit']) + + +def get_fpc5_input_parameter_values(input_params: GeophiresInputParameters, result: GeophiresXResult) -> dict[str, Any]: + _log.info('Extracting input parameter values...') + + params = _get_input_parameters_dict(input_params) + r: dict[str, dict[str, Any]] = result.result + + exploration_cost_musd = _q(r['CAPITAL COSTS (M$)']['Exploration costs']).to('MUSD').magnitude + assert exploration_cost_musd == float( + params['Exploration Capital Cost'] + ), 'Exploration cost mismatch between parameters and result' + + return { + 'exploration_cost_musd': round(sig_figs(exploration_cost_musd, 2)), + 'wacc_pct': sig_figs(r['ECONOMIC PARAMETERS']['WACC']['value'], 3), + 'reservoir_volume_m3': f"{r['RESERVOIR PARAMETERS']['Reservoir volume']['value']:,}", + } + + +def get_result_values(result: GeophiresXResult) -> dict[str, Any]: + _log.info('Extracting result values...') + + r: dict[str, dict[str, Any]] = result.result + + econ = r['ECONOMIC PARAMETERS'] + + total_capex_q: PlainQuantity = _q(r['CAPITAL COSTS (M$)']['Total CAPEX']) + + surf_equip_sim = r['SURFACE EQUIPMENT SIMULATION RESULTS'] + min_net_generation_mwe = surf_equip_sim['Minimum Net Electricity Generation']['value'] + avg_net_generation_mwe = surf_equip_sim['Average Net Electricity Generation']['value'] + max_net_generation_mwe = surf_equip_sim['Maximum Net Electricity Generation']['value'] + max_total_generation_mwe = surf_equip_sim['Maximum Total Electricity Generation']['value'] + parasitic_loss_pct = ( + surf_equip_sim['Average Pumping Power']['value'] + / surf_equip_sim['Average Total Electricity Generation']['value'] + * 100.0 + ) + net_power_idx = result.power_generation_profile[0].index('NET POWER (MW)') + + def n_year_avg_net_power_mwe(years: int) -> float: + return np.average([it[net_power_idx] for it in result.power_generation_profile[1:]][:years]) + + two_year_avg_net_power_mwe = n_year_avg_net_power_mwe(2) + two_year_avg_net_power_mwe_per_production_well = two_year_avg_net_power_mwe / _number_of_production_wells(result) + + total_fracture_surface_area_per_well_m2 = _total_fracture_surface_area_per_well_m2(result) + + occ_q = _q(r['CAPITAL COSTS (M$)']['Overnight Capital Cost']) + + field_gathering_cost_musd = _q(r['CAPITAL COSTS (M$)']['Field gathering system costs']).to('MUSD').magnitude + field_gathering_cost_pct_occ = field_gathering_cost_musd / occ_q.to('MUSD').magnitude * 100.0 + + redrills = r['ENGINEERING PARAMETERS']['Number of times redrilling']['value'] + total_wells_including_redrilling = redrills * _number_of_wells(result) + + return { + # Economic Results + 'lcoe_usd_per_mwh': sig_figs( + _q(r['SUMMARY OF RESULTS']['Electricity breakeven price']).to('USD / MWh').magnitude, 3 + ), + 'irr_pct': sig_figs(econ['After-tax IRR']['value'], 3), + 'npv_musd': sig_figs(econ['Project NPV']['value'], 3), + 'project_moic': sig_figs(econ['Project MOIC']['value'], 3), + 'project_vir': sig_figs(econ['Project VIR=PI=PIR']['value'], 3), + # Capital Costs + 'drilling_costs_musd': round(sig_figs(_drilling_costs_musd(result), 3)), + 'drilling_costs_per_well_musd': sig_figs(_drilling_costs_per_well_musd(result), 3), + 'stim_costs_musd': round(sig_figs(_stim_costs_musd(result), 3)), + 'stim_costs_per_well_musd': sig_figs(_stim_costs_per_well_musd(result), 3), + 'surface_power_plant_costs_gusd': sig_figs( + _q(r['CAPITAL COSTS (M$)']['Surface power plant costs']).to('GUSD').magnitude, 3 + ), + 'field_gathering_cost_musd': round(sig_figs(field_gathering_cost_musd, 3)), + 'field_gathering_cost_pct_occ': round(sig_figs(field_gathering_cost_pct_occ, 1)), + 'occ_gusd': sig_figs(occ_q.to('GUSD').magnitude, 3), + 'total_capex_gusd': sig_figs(total_capex_q.to('GUSD').magnitude, 3), + 'capex_usd_per_kw': round( + sig_figs((total_capex_q / PlainQuantity(max_net_generation_mwe, 'MW')).to('USD / kW').magnitude, 2) + ), + # Technical & Engineering Results + 'bht_temp_degc': r['RESERVOIR PARAMETERS']['Bottom-hole temperature']['value'], + 'min_net_generation_mwe': round(sig_figs(min_net_generation_mwe, 3)), + 'avg_net_generation_mwe': round(sig_figs(avg_net_generation_mwe, 3)), + 'max_net_generation_mwe': round(sig_figs(max_net_generation_mwe, 3)), + 'max_total_generation_mwe': round(sig_figs(max_total_generation_mwe, 3)), + 'two_year_avg_net_power_mwe_per_production_well': sig_figs(two_year_avg_net_power_mwe_per_production_well, 2), + 'parasitic_loss_pct': sig_figs(parasitic_loss_pct, 3), + 'number_of_times_redrilling': redrills, + 'total_wells_including_redrilling': total_wells_including_redrilling, + 'initial_production_temperature_degc': round( + sig_figs(r['RESERVOIR SIMULATION RESULTS']['Initial Production Temperature']['value'], 3) + ), + 'average_production_temperature_degc': round( + sig_figs(r['RESERVOIR SIMULATION RESULTS']['Average Production Temperature']['value'], 3) + ), + 'total_fracture_surface_area_per_well_mm2': sig_figs(total_fracture_surface_area_per_well_m2 / 1e6, 2), + 'total_fracture_surface_area_per_well_mft2': round( + sig_figs( + PlainQuantity(total_fracture_surface_area_per_well_m2, 'm ** 2').to('foot ** 2').magnitude * 1e-6, 2 + ) + ), + # TODO port all input and result values here instead of hardcoding them in the template + } + + +def _number_of_production_wells(result: GeophiresXResult) -> int: + return result.result['SUMMARY OF RESULTS']['Number of production wells']['value'] + + +def _number_of_wells(result: GeophiresXResult) -> int: + r: dict[str, dict[str, Any]] = result.result + + number_of_wells = r['SUMMARY OF RESULTS']['Number of injection wells']['value'] + _number_of_production_wells( + result + ) + + return number_of_wells + + +def _drilling_costs_musd(result: GeophiresXResult) -> float: + r: dict[str, dict[str, Any]] = result.result + + return _q(r['CAPITAL COSTS (M$)']['Drilling and completion costs']).to('MUSD').magnitude + + +def _drilling_costs_per_well_musd(result: GeophiresXResult) -> float: + return _drilling_costs_musd(result) / _number_of_wells(result) + + +def _stim_costs_per_well_musd(result: GeophiresXResult) -> float: + stim_costs_per_well_musd = _stim_costs_musd(result) / _number_of_wells(result) + return stim_costs_per_well_musd + + +def _stim_costs_musd(result: GeophiresXResult) -> float: + r: dict[str, dict[str, Any]] = result.result + + stim_costs_musd = _q(r['CAPITAL COSTS (M$)']['Stimulation costs']).to('MUSD').magnitude + return stim_costs_musd + + +def _stim_costs_per_well_musd(result: GeophiresXResult) -> float: + stim_costs_per_well_musd = _stim_costs_musd(result) / _number_of_wells(result) + return stim_costs_per_well_musd + + +def _total_fracture_surface_area_per_well_m2(result: GeophiresXResult) -> float: + r: dict[str, dict[str, Any]] = result.result + res_params = r['RESERVOIR PARAMETERS'] + return ( + _q(res_params['Fracture area']).to('m ** 2').magnitude + * res_params['Number of fractures']['value'] + / _number_of_wells(result) + ) + + +def generate_res_eng_reference_sim_params_table_md( + base_case_input_params: GeophiresInputParameters, res_eng_reference_sim_params: dict[str, Any] +) -> str: + return get_fpc_category_parameters_table_md( + ImmutableGeophiresInputParameters( + # from_file_path=base_case_input_params.as_file_path(), + params=res_eng_reference_sim_params + ), + None, + ) + + +def generate_fervo_project_cape_5_md( + input_params: GeophiresInputParameters, + result: GeophiresXResult, + res_eng_reference_sim_params: dict[str, Any] | None = None, + project_root: Path = _PROJECT_ROOT, +) -> None: + if res_eng_reference_sim_params is None: + res_eng_reference_sim_params = {} + + # noinspection PyDictCreation + template_values = {**get_fpc5_input_parameter_values(input_params, result), **get_result_values(result)} + + for template_key, md_method in { + 'reservoir_parameters_table_md': generate_fpc_reservoir_parameters_table_md, + 'surface_plant_parameters_table_md': generate_fpc_surface_plant_parameters_table_md, + 'well_bores_parameters_table_md': generate_fpc_well_bores_parameters_table_md, + 'economics_parameters_table_md': generate_fpc_economics_parameters_table_md, + 'construction_parameters_table_md': generate_fpc_construction_parameters_table_md, + }.items(): + template_values[template_key] = md_method(input_params) + + template_values['reservoir_engineering_reference_simulation_params_table_md'] = ( + generate_res_eng_reference_sim_params_table_md(input_params, res_eng_reference_sim_params) + ) + + docs_dir = project_root / 'docs' + + # Set up Jinja environment + env = Environment(loader=FileSystemLoader(docs_dir), autoescape=True) + template = env.get_template('Fervo_Project_Cape-5.md.jinja') + + # Render template + _log.info('Rendering template...') + output = template.render(**template_values) + + # Write output + output_file = docs_dir / 'Fervo_Project_Cape-5.md' + output_file.write_text(output, encoding='utf-8') + + _log.info(f'✓ Generated {output_file}') + _log.info('\nKey results:') + _log.info(f"\tLCOE: ${template_values['lcoe_usd_per_mwh']}/MWh") + _log.info(f"\tIRR: {template_values['irr_pct']}%") + _log.info(f"\tTotal CAPEX: ${template_values['total_capex_gusd']}B") + + +def main(project_root: Path | None = None): + """ + Generate Fervo_Project_Cape-5.md (markdown documentation) from the Jinja template. + """ + global _current_project_root + + if project_root is None: + project_root = _get_project_root() + + _current_project_root = project_root + + input_params: GeophiresInputParameters = ImmutableGeophiresInputParameters( + from_file_path=_get_fpc5_input_file_path(project_root) + ) + result = GeophiresXResult(_get_fpc5_result_file_path(project_root)) + generate_fervo_project_cape_5_md(input_params, result, project_root=project_root) + + +if __name__ == '__main__': + main() diff --git a/src/geophires_docs/watch_docs.py b/src/geophires_docs/watch_docs.py new file mode 100755 index 000000000..24f84d613 --- /dev/null +++ b/src/geophires_docs/watch_docs.py @@ -0,0 +1,113 @@ +#!python +# Automatically rebuilds docs locally when changes are detected. +# Usage, from the project root: +# ./src/geophires_docs/watch_docs.py + +import argparse +import os +import subprocess +import time +from pathlib import Path +from typing import Any + +from geophires_docs import _get_logger + +_log = _get_logger(__name__) + + +def get_file_states(directory) -> dict[str, Any]: + """ + Returns a dictionary of file paths and their modification times. + """ + states = {} + for root, _, files in os.walk(directory): + for filename in files: + # Ignore hidden files, temporary editor files, and this script itself + # fmt:off + if (filename.startswith('.') or + filename.endswith('~') or filename == os.path.basename(__file__)): # noqa: PTH119 + # fmt:on + continue + + filepath = os.path.join(root, filename) + + # Avoid watching build directories if they are generated inside docs/ + if '_build' in filepath or 'build' in filepath: + continue + + try: + states[filepath] = os.path.getmtime(filepath) # noqa: PTH204 + except OSError: + pass + return states + + +def main(): + parser = argparse.ArgumentParser(description='Automatically rebuilds docs locally when changes are detected.') + parser.add_argument('--no-say', action='store_true', help='Disable audio notifications via the say command') + args = parser.parse_args() + + # Determine paths relative to this script + script_dir = os.path.dirname(os.path.abspath(__file__)) + project_root: str = Path(__file__).parent.parent.parent + + def _say(msg) -> None: + if args.no_say: + return + try: + subprocess.run(['say', msg], cwd=project_root, check=False) # noqa: S603,S607 + except subprocess.CalledProcessError: + pass + + # Watch the directory where the script is located (docs/) + watch_dirs = [script_dir, Path(project_root) / 'docs', Path(project_root) / 'tests' / 'examples'] + + command = ['tox', '-e', 'docs'] + poll_interval = 2 # Seconds + + _log.info(f"Watching '{watch_dirs}' for changes...") + _log.info(f"Project root determined as: '{project_root}'") + _log.info(f"Command to run: {' '.join(command)}") + _log.info('Press Ctrl+C to stop.') + + def _get_file_states() -> dict: + states = {} + for watch_dir in watch_dirs: + states = {**states, **get_file_states(watch_dir)} + + return states + + # Initial state + last_states = _get_file_states() + + try: + while True: + time.sleep(poll_interval) + current_states = _get_file_states() + + if current_states != last_states: + _log.info('\n[Change Detected] Running docs build...') + time.sleep(1) + + try: + # Run tox from the project root so it finds tox.ini + subprocess.run(command, cwd=project_root, check=False) # noqa: S603 + except FileNotFoundError: + _log.error("Error: 'tox' command not found. Please ensure tox is installed.") + except Exception as e: + _log.error(f'An error occurred: {e}') + _say('error rebuilding docs') + + _log.info(f"\nDocs rebuild complete at {time.strftime('%Y-%m-%d %H:%M:%S')}.") + _say('docs rebuilt') + _log.info(f"\nWaiting for further changes in '{watch_dirs}'...") + + # Update state to the current state + last_states = _get_file_states() + + except KeyboardInterrupt: + _log.info('\nWatcher stopped.') + + +if __name__ == '__main__': + main() diff --git a/src/geophires_x/Economics.py b/src/geophires_x/Economics.py index f47b02d5c..de1afa2a4 100644 --- a/src/geophires_x/Economics.py +++ b/src/geophires_x/Economics.py @@ -15,7 +15,7 @@ project_payback_period_parameter, inflation_cost_during_construction_output_parameter, \ interest_during_construction_output_parameter, total_capex_parameter_output_parameter, \ overnight_capital_cost_output_parameter, CONSTRUCTION_CAPEX_SCHEDULE_PARAMETER_NAME, \ - _YEAR_INDEX_VALUE_EXPLANATION_SNIPPET + _YEAR_INDEX_VALUE_EXPLANATION_SNIPPET, investment_tax_credit_output_parameter from geophires_x.GeoPHIRESUtils import quantity from geophires_x.OptionList import Configuration, WellDrillingCostCorrelation, EconomicModel, EndUseOptions, PlantType, \ _WellDrillingCostCorrelationCitation @@ -2302,13 +2302,7 @@ def __init__(self, model: Model): self.ProjectMOIC = self.OutputParameterDict[self.ProjectMOIC.Name] = moic_parameter() self.ProjectPaybackPeriod = self.OutputParameterDict[self.ProjectPaybackPeriod.Name] = ( project_payback_period_parameter()) - self.RITCValue = self.OutputParameterDict[self.RITCValue.Name] = OutputParameter( - Name="Investment Tax Credit Value", - display_name='Investment Tax Credit', - UnitType=Units.CURRENCY, - PreferredUnits=CurrencyUnit.MDOLLARS, - CurrentUnits=CurrencyUnit.MDOLLARS - ) + self.RITCValue = self.OutputParameterDict[self.RITCValue.Name] = investment_tax_credit_output_parameter() self.cost_one_production_well = self.OutputParameterDict[self.cost_one_production_well.Name] = OutputParameter( Name="Cost of One Production Well", UnitType=Units.CURRENCY, @@ -3585,9 +3579,11 @@ def _calculate_sam_economics(self, model: Model) -> None: self.ProjectMOIC.value = self.sam_economics_calculations.moic.value self.ProjectVIR.value = self.sam_economics_calculations.project_vir.value - # TODO remove or clarify project payback period: https://github.com/NREL/GEOPHIRES-X/issues/413 self.ProjectPaybackPeriod.value = self.sam_economics_calculations.project_payback_period.value + self.RITCValue.value = self.sam_economics_calculations.investment_tax_credit.quantity().to( + self.RITCValue.CurrentUnits).magnitude + # noinspection SpellCheckingInspection def _calculate_derived_outputs(self, model: Model) -> None: """ diff --git a/src/geophires_x/EconomicsSam.py b/src/geophires_x/EconomicsSam.py index 419d04094..450def3b9 100644 --- a/src/geophires_x/EconomicsSam.py +++ b/src/geophires_x/EconomicsSam.py @@ -24,6 +24,7 @@ # noinspection PyPackageRequirements import PySAM.Utilityrate5 as UtilityRate +from pint.facets.plain import PlainQuantity from tabulate import tabulate from geophires_x import Model as Model @@ -43,6 +44,7 @@ royalty_cost_output_parameter, overnight_capital_cost_output_parameter, _SAM_EM_MOIC_RETURNS_TAX_QUALIFIER, + investment_tax_credit_output_parameter, ) from geophires_x.EconomicsSamPreRevenue import ( _AFTER_TAX_NET_CASH_FLOW_ROW_NAME, @@ -96,7 +98,8 @@ class SamEconomicsCalculations: project_vir: OutputParameter = field(default_factory=project_vir_parameter) project_payback_period: OutputParameter = field(default_factory=project_payback_period_parameter) - """TODO remove or clarify project payback period: https://github.com/NREL/GEOPHIRES-X/issues/413""" + + investment_tax_credit: OutputParameter = field(default_factory=investment_tax_credit_output_parameter) @property def _pre_revenue_years_count(self) -> int: @@ -368,6 +371,11 @@ def sf(_v: float, num_sig_figs: int = 5) -> float: sam_economics.project_payback_period.value = _calculate_project_payback_period( sam_economics.sam_cash_flow_profile, model ) + sam_economics.investment_tax_credit.value = ( + _calculate_investment_tax_credit_value(sam_economics.sam_cash_flow_profile) + .to(sam_economics.investment_tax_credit.CurrentUnits.value) + .magnitude + ) return sam_economics @@ -522,21 +530,43 @@ def _calculate_project_vir(cash_flow: list[list[Any]], model: Model) -> float: def _calculate_project_payback_period(cash_flow: list[list[Any]], model) -> float | None: """ - TODO remove or clarify project payback period: https://github.com/NREL/GEOPHIRES-X/issues/413 - """ + Calculates the Simple Payback Period (SPB). + SPB is the time required for the cumulative non-discounted after-tax net cash flow to turn positive. + The calculation assumes annual cash flows. The returned value represents the number of years + from the start of the provided cash flow list until the investment is recovered. + """ try: + # Get flattened annual after-tax cash flow after_tax_cash_flow = _after_tax_net_cash_flow_all_years(cash_flow, _pre_revenue_years_count(model)) - cumm_cash_flow = np.zeros(len(after_tax_cash_flow)) - cumm_cash_flow[0] = after_tax_cash_flow[0] - for year in range(1, len(after_tax_cash_flow)): - cumm_cash_flow[year] = cumm_cash_flow[year - 1] + after_tax_cash_flow[year] - if cumm_cash_flow[year] >= 0: - year_before_full_recovery = year - 1 - payback_period = ( - year_before_full_recovery - + abs(cumm_cash_flow[year_before_full_recovery]) / after_tax_cash_flow[year] - ) + + cumulative_cash_flow = np.zeros(len(after_tax_cash_flow)) + cumulative_cash_flow[0] = after_tax_cash_flow[0] + + # Handle edge case where the first year is already positive + if cumulative_cash_flow[0] >= 0: + # If the project is profitable immediately (rare for SPB), return 0 or fraction. + # For standard SPB logic where Index 0 is an investment year, this is an edge case. + pass + + for year_index in range(1, len(after_tax_cash_flow)): + cumulative_cash_flow[year_index] = cumulative_cash_flow[year_index - 1] + after_tax_cash_flow[year_index] + + if cumulative_cash_flow[year_index] >= 0: + # Payback occurred in this year (year_index). + # We need to calculate how far into this year the break-even point occurred. + + previous_year_index = year_index - 1 + unrecovered_cost_at_start_of_year = abs(cumulative_cash_flow[previous_year_index]) + cash_flow_in_current_year = after_tax_cash_flow[year_index] + + # Fraction of the current year required to recover the remaining cost + fraction_of_year = unrecovered_cost_at_start_of_year / cash_flow_in_current_year + + # Total years elapsed = Full years prior to this one + fraction of this one. + # If we are at year_index, the number of full years passed is equal to year_index. + # Example: If year_index is 5 (6th year), 5 full years (Indices 0..4) have passed. + payback_period = year_index + fraction_of_year return float(payback_period) @@ -546,6 +576,19 @@ def _calculate_project_payback_period(cash_flow: list[list[Any]], model) -> floa return None +def _calculate_investment_tax_credit_value(sam_cash_flow_profile) -> PlainQuantity: + total_itc_sum_q: PlainQuantity = quantity(0, 'USD') + + for itc_line_item in ['Federal ITC total income ($)', 'State ITC total income ($)']: + itc_numeric_entries = [ + float(it) for it in _cash_flow_profile_row(sam_cash_flow_profile, itc_line_item) if is_float(it) + ] + itc_sum_q = quantity(sum(itc_numeric_entries), 'USD') + total_itc_sum_q += itc_sum_q + + return total_itc_sum_q + + def get_sam_cash_flow_profile_tabulated_output(model: Model, **tabulate_kw_args) -> str: """ Note model must have already calculated economics for this to work (used in Outputs) diff --git a/src/geophires_x/EconomicsUtils.py b/src/geophires_x/EconomicsUtils.py index 78614f491..9044deff3 100644 --- a/src/geophires_x/EconomicsUtils.py +++ b/src/geophires_x/EconomicsUtils.py @@ -83,7 +83,10 @@ def project_payback_period_parameter() -> OutputParameter: CurrentUnits=TimeUnit.YEAR, ToolTipText='The time at which cumulative cash flow reaches zero. ' 'For projects that never pay back, the calculated value will be "N/A". ' - 'For SAM Economic Models, after-tax net cash flow is used to calculate the cumulative cash flow.', + 'For SAM Economic Models, this is Simple Payback Period (SPB): the time at which cumulative non-discounted ' + 'cash flow reaches zero, calculated using non-discounted after-tax net cash flow. ' + 'See https://samrepo.nrelcloud.org/help/mtf_payback.html for important considerations regarding the ' + 'limitations of this metric.', ) @@ -195,3 +198,16 @@ def royalty_cost_output_parameter() -> OutputParameter: ToolTipText='The annual costs paid to a royalty holder, calculated as a percentage of the ' 'project\'s gross annual revenue. This is modeled as a variable operating expense.', ) + + +def investment_tax_credit_output_parameter() -> OutputParameter: + return OutputParameter( + Name="Investment Tax Credit Value", + display_name='Investment Tax Credit', + UnitType=Units.CURRENCY, + PreferredUnits=CurrencyUnit.MDOLLARS, + CurrentUnits=CurrencyUnit.MDOLLARS, + ToolTipText='Represents the total undiscounted ITC sum. ' + 'For SAM Economic Models, this accounts for the standard Year 1 Federal ITC as well as any ' + 'applicable State ITCs or multi-year credit schedules.', + ) diff --git a/src/geophires_x/Outputs.py b/src/geophires_x/Outputs.py index 98bc6a680..c9ab609af 100644 --- a/src/geophires_x/Outputs.py +++ b/src/geophires_x/Outputs.py @@ -266,6 +266,12 @@ def PrintOutputs(self, model: Model): label = Outputs._field_label(field.Name, 49) f.write(f' {label}{field.value:10.2f} {field.CurrentUnits.value}\n') + if econ.RITCValue.value and is_sam_econ_model: + # Non-SAM-EMs (inaccurately) treat ITC as a capital cost and thus are displayed in the capital + # costs category rather than here. + f.write( + f' {econ.RITCValue.display_name}: {abs(econ.RITCValue.value):10.2f} {econ.RITCValue.CurrentUnits.value}\n') + if not is_sam_econ_model: # (parameter is ambiguous to the point of meaninglessness for SAM-EM) acf: OutputParameter = econ.accrued_financing_during_construction_percentage acf_label = Outputs._field_label(acf.display_name, 49) @@ -346,15 +352,15 @@ def PrintOutputs(self, model: Model): f.write(NL) f.write(' ***RESOURCE CHARACTERISTICS***\n') f.write(NL) - f.write(f' Maximum reservoir temperature: {model.reserv.Tmax.value:10.1f} ' + model.reserv.Tmax.CurrentUnits.value + NL) - f.write(f' Number of segments: {model.reserv.numseg.value:10.0f} ' + NL) + f.write(f' Maximum reservoir temperature: {model.reserv.Tmax.value:10.1f} {model.reserv.Tmax.CurrentUnits.value}\n') + f.write(f' Number of segments: {model.reserv.numseg.value:10.0f}\n') if model.reserv.numseg.value == 1: - f.write(f' Geothermal gradient: {model.reserv.gradient.value[0]:10.4g} ' + model.reserv.gradient.CurrentUnits.value + NL) + f.write(f' Geothermal gradient: {model.reserv.gradient.value[0]:10.4g} {model.reserv.gradient.CurrentUnits.value}\n') else: for i in range(1, model.reserv.numseg.value): - f.write(f' Segment {str(i):s} Geothermal gradient: {model.reserv.gradient.value[i-1]:10.4g} ' + model.reserv.gradient.CurrentUnits.value +NL) + f.write(f' Segment {str(i):s} Geothermal gradient: {model.reserv.gradient.value[i-1]:10.4g} {model.reserv.gradient.CurrentUnits.value}\n') f.write(f' Segment {str(i):s} Thickness: {round(model.reserv.layerthickness.value[i-1], 10)} {model.reserv.layerthickness.CurrentUnits.value}\n') - f.write(f' Segment {str(i+1):s} Geothermal gradient: {model.reserv.gradient.value[i]:10.4g} ' + model.reserv.gradient.CurrentUnits.value + NL) + f.write(f' Segment {str(i+1):s} Geothermal gradient: {model.reserv.gradient.value[i]:10.4g} {model.reserv.gradient.CurrentUnits.value}\n') f.write(NL) f.write(NL) @@ -495,17 +501,9 @@ def PrintOutputs(self, model: Model): f.write(f' Drilling and completion costs per redrilled well: {(econ.Cwell.value/(model.wellbores.nprod.value+model.wellbores.ninj.value)):10.2f} {econ.Cwell.CurrentUnits.value}\n') f.write(f' Stimulation costs (for redrilling): {econ.Cstim.value:10.2f} {econ.Cstim.CurrentUnits.value}\n') - if model.economics.RITCValue.value: - if not is_sam_econ_model: - f.write(f' {model.economics.RITCValue.display_name}: {-1*model.economics.RITCValue.value:10.2f} {model.economics.RITCValue.CurrentUnits.value}\n') - else: - # TODO Extract value from SAM Cash Flow Profile per - # https://github.com/NREL/GEOPHIRES-X/issues/404. - # For now we skip displaying the value because it can be/probably is usually mathematically - # inaccurate, and even if it's not, it's redundant with the cash flow profile and also - # misleading/confusing/wrong to display it as a capital cost since it is not a capital - # expenditure. - pass + if model.economics.RITCValue.value and not is_sam_econ_model: + # Note ITC is in ECONOMIC PARAMETERS category for SAM-EM (not capital costs) + f.write(f' {econ.RITCValue.display_name}: {-1 * econ.RITCValue.value:10.2f} {econ.RITCValue.CurrentUnits.value}\n') display_occ_and_inflation_during_construction_in_capital_costs = is_sam_econ_model if display_occ_and_inflation_during_construction_in_capital_costs: @@ -909,15 +907,15 @@ def get_sam_cash_flow_profile_output(self, model): # number that results in a separator line at least as wide as the table (narrower would be unsightly). spaces_per_tab = 4 - # The tabluate library has native separating line functionality (per https://pypi.org/project/tabulate/) but + # The tabulate library has native separating line functionality (per https://pypi.org/project/tabulate/) but # I wasn't able to get it to replicate the formatting as coded below. - separator_line = len(cfp_o.split('\n')[0].replace('\t',' ' * spaces_per_tab)) * '-' + separator_line = len(cfp_o.split('\n')[0].replace('\t', ' ' * spaces_per_tab)) * '-' ret += separator_line + '\n' ret += cfp_o ret += '\n' + separator_line - ret += '\n\n' + ret += '\n' return ret diff --git a/src/geophires_x/Reservoir.py b/src/geophires_x/Reservoir.py index 3eea04cec..639769d34 100644 --- a/src/geophires_x/Reservoir.py +++ b/src/geophires_x/Reservoir.py @@ -284,7 +284,9 @@ def __init__(self, model: Model): PreferredUnits=LengthUnit.METERS, CurrentUnits=LengthUnit.METERS, ErrMessage="assume default fracture width (500 m)", - ToolTipText="Width of each fracture" + ToolTipText="Total horizontal length of each fracture plane (from tip to tip). " + "Note: In some contexts this is called 'Fracture Length'; it refers to the fracture's lateral " + "extent, not its aperture or thickness." ) fracnumb_allowable_range = list(range(1, _MAX_ALLOWED_FRACTURES + 1, 1)) diff --git a/src/geophires_x/SurfacePlantSupercriticalORC.py b/src/geophires_x/SurfacePlantSupercriticalORC.py index e418165d2..7f971096b 100644 --- a/src/geophires_x/SurfacePlantSupercriticalORC.py +++ b/src/geophires_x/SurfacePlantSupercriticalORC.py @@ -14,7 +14,7 @@ def __init__(self, model: Model): :return: None """ - model.logger.info("Init " + self.__class__.__name__ + ": " + __name__) + model.logger.info(f'Init {self.__class__.__name__}: {__name__}') super().__init__(model) # Initialize all the parameters in the superclass # Set up all the Parameters that will be predefined by this class using the different types of parameter classes. @@ -33,7 +33,7 @@ def __init__(self, model: Model): sclass = self.__class__.__name__ self.MyClass = sclass self.MyPath = __file__ - model.logger.info("Complete " + self.__class__.__name__ + ": " + __name__) + model.logger.info(f"Complete {self.__class__.__name__}: {__name__}") def __str__(self): return "SurfacePlantSupercriticalORC" diff --git a/src/geophires_x/WellBores.py b/src/geophires_x/WellBores.py index 9e6e9249e..c4cddeb07 100644 --- a/src/geophires_x/WellBores.py +++ b/src/geophires_x/WellBores.py @@ -740,6 +740,18 @@ def __init__(self, model: Model): ToolTipText="Pass this parameter to set the Number of Production Wells and Number of Injection Wells to " "same value." ) + # noinspection SpellCheckingInspection + self.ninj_per_production_well = self.ParameterDict[self.ninj_per_production_well.Name] = floatParameter( + "Number of Injection Wells per Production Well", + DefaultValue=1, + Min=0, + Max=max_doublets-1, + UnitType=Units.NONE, + Required=False, + ToolTipText="Number of (identical) injection wells per production well. " + "For example, provide 0.666 to specify a 3:2 production:injection well ratio. " + "The number of injection wells will be rounded up to the nearest integer." + ) # noinspection SpellCheckingInspection self.prodwelldiam = self.ParameterDict[self.prodwelldiam.Name] = floatParameter( @@ -1361,20 +1373,35 @@ def read_parameters(self, model: Model) -> None: coerce_int_params_to_enum_values(self.ParameterDict) - if self.doublets_count.Provided: - def _error(num_wells_param_:intParameter): - msg = f'{num_wells_param_.Name} may not be provided when {self.doublets_count.Name} is provided.' - model.logger.error(msg) - raise ValueError(msg) + self._set_well_counts_from_parameters(model) + + model.logger.info(f"read parameters complete {self.__class__.__name__}: {__name__}") + + def _set_well_counts_from_parameters(self, model: Model): + mutually_exclusive_well_count_params = [self.doublets_count, self.ninj_per_production_well] + provided_well_count_params = [it for it in mutually_exclusive_well_count_params if it.Provided] + if len(provided_well_count_params) > 1: + raise ValueError(f'Only one of [{", ".join([it.Name for it in mutually_exclusive_well_count_params])}] ' + f'may be provided.') + + def _raise_incompatible_param_error(incompatible_param: intParameter, with_param: intParameter): + msg = f'{incompatible_param.Name} may not be provided when {with_param.Name} is provided.' + model.logger.error(msg) + raise ValueError(msg) + if self.doublets_count.Provided: for num_wells_param in [self.ninj, self.nprod]: if num_wells_param.Provided: - _error(num_wells_param) + _raise_incompatible_param_error(num_wells_param, self.doublets_count) self.ninj.value = self.doublets_count.value self.nprod.value = self.doublets_count.value - model.logger.info(f"read parameters complete {self.__class__.__name__}: {__name__}") + if self.ninj_per_production_well.Provided: + if self.ninj.Provided: + _raise_incompatible_param_error(self.ninj, self.ninj_per_production_well) + + self.ninj.value = int(math.ceil(self.nprod.value * self.ninj_per_production_well.value)) def Calculate(self, model: Model) -> None: """ diff --git a/src/geophires_x/__init__.py b/src/geophires_x/__init__.py index 8a9ccd93e..669870b14 100644 --- a/src/geophires_x/__init__.py +++ b/src/geophires_x/__init__.py @@ -1 +1 @@ -__version__ = '3.10.24' +__version__ = '3.11.2' diff --git a/src/geophires_x_client/geophires_x_result.py b/src/geophires_x_client/geophires_x_result.py index 91b14c26c..452416364 100644 --- a/src/geophires_x_client/geophires_x_result.py +++ b/src/geophires_x_client/geophires_x_result.py @@ -717,7 +717,7 @@ def _get_unlabeled_string_field( return None @property - def power_generation_profile(self): + def power_generation_profile(self) -> list[list[str | float]]: return self.result['POWER GENERATION PROFILE'] def _get_power_generation_profile(self): diff --git a/src/geophires_x_schema_generator/geophires-request.json b/src/geophires_x_schema_generator/geophires-request.json index be86aef39..0a0c354a8 100644 --- a/src/geophires_x_schema_generator/geophires-request.json +++ b/src/geophires_x_schema_generator/geophires-request.json @@ -305,7 +305,7 @@ "maximum": 10000 }, "Fracture Width": { - "description": "Width of each fracture", + "description": "Total horizontal length of each fracture plane (from tip to tip). Note: In some contexts this is called 'Fracture Length'; it refers to the fracture's lateral extent, not its aperture or thickness.", "type": "number", "units": "meter", "category": "Reservoir", @@ -664,6 +664,15 @@ "minimum": 0, "maximum": 200 }, + "Number of Injection Wells per Production Well": { + "description": "Number of (identical) injection wells per production well. For example, provide 0.666 to specify a 3:2 production:injection well ratio. The number of injection wells will be rounded up to the nearest integer.", + "type": "number", + "units": null, + "category": "Well Bores", + "default": 1, + "minimum": 0, + "maximum": 199 + }, "Production Well Diameter": { "description": "Inner diameter of production wellbore (assumed constant along the wellbore) to calculate frictional pressure drop and wellbore heat transmission with Rameys model", "type": "number", diff --git a/src/geophires_x_schema_generator/geophires-result.json b/src/geophires_x_schema_generator/geophires-result.json index 9b1e88947..9fe3b3f09 100644 --- a/src/geophires_x_schema_generator/geophires-result.json +++ b/src/geophires_x_schema_generator/geophires-result.json @@ -139,7 +139,7 @@ "Fixed Charge Rate (FCR)": {}, "Project Payback Period": { "type": "number", - "description": "The time at which cumulative cash flow reaches zero. For projects that never pay back, the calculated value will be \"N/A\". For SAM Economic Models, after-tax net cash flow is used to calculate the cumulative cash flow.", + "description": "The time at which cumulative cash flow reaches zero. For projects that never pay back, the calculated value will be \"N/A\". For SAM Economic Models, this is Simple Payback Period (SPB): the time at which cumulative non-discounted cash flow reaches zero, calculated using non-discounted after-tax net cash flow. See https://samrepo.nrelcloud.org/help/mtf_payback.html for important considerations regarding the limitations of this metric.", "units": "yr" }, "CHP: Percent cost allocation for electrical plant": {}, @@ -439,7 +439,7 @@ }, "Investment Tax Credit": { "type": "number", - "description": "Investment Tax Credit Value", + "description": "Investment Tax Credit Value. Represents the total undiscounted ITC sum. For SAM Economic Models, this accounts for the standard Year 1 Federal ITC as well as any applicable State ITCs or multi-year credit schedules.", "units": "MUSD" }, "Overnight Capital Cost": { diff --git a/tests/.gitignore b/tests/.gitignore index dd02483df..83ec4ce7b 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,3 +1,5 @@ +*.env + HIP.out *.log MC_*Result.json diff --git a/tests/examples/Fervo_Project_Cape-4.out b/tests/examples/Fervo_Project_Cape-4.out index e85f55118..b86ef62ee 100644 --- a/tests/examples/Fervo_Project_Cape-4.out +++ b/tests/examples/Fervo_Project_Cape-4.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.10.22 - Simulation Date: 2025-12-15 - Simulation Time: 09:15 - Calculation Time: 1.777 sec + GEOPHIRES Version: 3.11.0 + Simulation Date: 2026-01-16 + Simulation Time: 11:41 + Calculation Time: 1.775 sec ***SUMMARY OF RESULTS*** @@ -34,7 +34,7 @@ Simulation Metadata After-tax IRR: 27.55 % Project VIR=PI=PIR: 1.45 Project MOIC: 4.20 - Project Payback Period: 2.33 yr + Project Payback Period: 3.33 yr Estimated Jobs Created: 1300 ***ENGINEERING PARAMETERS*** diff --git a/tests/examples/Fervo_Project_Cape-4.txt b/tests/examples/Fervo_Project_Cape-4.txt index d2c1fe956..100882722 100644 --- a/tests/examples/Fervo_Project_Cape-4.txt +++ b/tests/examples/Fervo_Project_Cape-4.txt @@ -1,4 +1,4 @@ -# Case Study: 500 MWe EGS Project Modeled on Fervo Cape Station +# [Deprecated] Case Study: 500 MWe EGS Project Modeled on Fervo Cape Station # 500 MWe EGS Case Study Modeled on Fervo Cape Station after Fervo's April 2025 upsizing announcement: # https://fervoenergy.com/fervo-energy-announces-31-mw-power-purchase-agreement-with-shell-energy/ # See documentation: https://softwareengineerprogrammer.github.io/GEOPHIRES/Fervo_Project_Cape-4.html diff --git a/tests/examples/Fervo_Project_Cape-5.out b/tests/examples/Fervo_Project_Cape-5.out new file mode 100644 index 000000000..7b08b1112 --- /dev/null +++ b/tests/examples/Fervo_Project_Cape-5.out @@ -0,0 +1,462 @@ + ***************** + ***CASE REPORT*** + ***************** + +Simulation Metadata +---------------------- + GEOPHIRES Version: 3.11.1 + Simulation Date: 2026-01-16 + Simulation Time: 12:50 + Calculation Time: 1.783 sec + + ***SUMMARY OF RESULTS*** + + End-Use Option: Electricity + Average Net Electricity Production: 536.88 MW + Electricity breakeven price: 7.57 cents/kWh + Total CAPEX: 2859.26 MUSD + Number of production wells: 56 + Number of injection wells: 38 + Flowrate per production well: 107.0 kg/sec + Well depth: 2.7 kilometer + Segment 1 Geothermal gradient: 74 degC/km + Segment 1 Thickness: 2.5 kilometer + Segment 2 Geothermal gradient: 41 degC/km + Segment 2 Thickness: 0.5 kilometer + Segment 3 Geothermal gradient: 39.1 degC/km + + + ***ECONOMIC PARAMETERS*** + + Economic Model = SAM Single Owner PPA + Real Discount Rate: 12.00 % + Nominal Discount Rate: 15.02 % + WACC: 8.31 % + Investment Tax Credit: 857.78 MUSD + Project lifetime: 30 yr + Capacity factor: 90.0 % + Project NPV: 268.60 MUSD + After-tax IRR: 24.46 % + Project VIR=PI=PIR: 1.45 + Project MOIC: 5.05 + Project Payback Period: 5.93 yr + Estimated Jobs Created: 1269 + + ***ENGINEERING PARAMETERS*** + + Number of Production Wells: 56 + Number of Injection Wells: 38 + Well depth: 2.7 kilometer + Water loss rate: 1.0 % + Pump efficiency: 80.0 % + Injection temperature: 56.6 degC + Production Wellbore heat transmission calculated with Ramey's model + Average production well temperature drop: 0.2 degC + Flowrate per production well: 107.0 kg/sec + Injection well casing ID: 8.535 in + Production well casing ID: 8.535 in + Number of times redrilling: 3 + Power plant type: Supercritical ORC + + + ***RESOURCE CHARACTERISTICS*** + + Maximum reservoir temperature: 500.0 degC + Number of segments: 3 + Segment 1 Geothermal gradient: 74 degC/km + Segment 1 Thickness: 2.5 kilometer + Segment 2 Geothermal gradient: 41 degC/km + Segment 2 Thickness: 0.5 kilometer + Segment 3 Geothermal gradient: 39.1 degC/km + + + ***RESERVOIR PARAMETERS*** + + Reservoir Model = Multiple Parallel Fractures Model (Gringarten) + Bottom-hole temperature: 205.38 degC + Fracture model = Rectangular + Well separation: fracture height: 95.00 meter + Fracture width: 305.00 meter + Fracture area: 28975.00 m**2 + Reservoir volume calculated with fracture separation and number of fractures as input + Number of fractures: 14100 + Fracture separation: 9.83 meter + Reservoir volume: 4013898767 m**3 + Reservoir hydrostatic pressure: 25324.54 kPa + Plant outlet pressure: 13789.51 kPa + Production wellhead pressure: 2082.43 kPa + Productivity Index: 1.75 kg/sec/bar + Injectivity Index: 2.11 kg/sec/bar + Reservoir density: 2800.00 kg/m**3 + Reservoir thermal conductivity: 3.05 W/m/K + Reservoir heat capacity: 790.00 J/kg/K + + + ***RESERVOIR SIMULATION RESULTS*** + + Maximum Production Temperature: 203.3 degC + Average Production Temperature: 202.7 degC + Minimum Production Temperature: 197.9 degC + Initial Production Temperature: 201.9 degC + Average Reservoir Heat Extraction: 3664.25 MW + Production Wellbore Heat Transmission Model = Ramey Model + Average Production Well Temperature Drop: 0.2 degC + Average Injection Well Pump Pressure Drop: -5176.1 kPa + Average Production Well Pump Pressure Drop: 6917.1 kPa + + + ***CAPITAL COSTS (M$)*** + + Drilling and completion costs: 436.98 MUSD + Drilling and completion costs per well: 4.65 MUSD + Stimulation costs: 454.02 MUSD + Surface power plant costs: 1467.52 MUSD + Field gathering system costs: 43.13 MUSD + Total surface equipment costs: 1510.64 MUSD + Exploration costs: 30.00 MUSD + Overnight Capital Cost: 2431.65 MUSD + Interest during construction: 142.34 MUSD + Inflation costs during construction: 285.27 MUSD + Total CAPEX: 2859.26 MUSD + + + ***OPERATING AND MAINTENANCE COSTS (M$/yr)*** + + Wellfield maintenance costs: 5.75 MUSD/yr + Power plant maintenance costs: 24.87 MUSD/yr + Water costs: 3.15 MUSD/yr + Redrilling costs: 89.10 MUSD/yr + Total operating and maintenance costs: 122.87 MUSD/yr + + + ***SURFACE EQUIPMENT SIMULATION RESULTS*** + + Initial geofluid availability: 0.19 MW/(kg/s) + Maximum Total Electricity Generation: 599.67 MW + Average Total Electricity Generation: 595.97 MW + Minimum Total Electricity Generation: 561.22 MW + Initial Total Electricity Generation: 589.90 MW + Maximum Net Electricity Generation: 540.69 MW + Average Net Electricity Generation: 536.88 MW + Minimum Net Electricity Generation: 501.23 MW + Initial Net Electricity Generation: 530.73 MW + Average Annual Total Electricity Generation: 4698.75 GWh + Average Annual Net Electricity Generation: 4232.89 GWh + Initial pumping power/net installed power: 11.15 % + Average Pumping Power: 59.09 MW + Heat to Power Conversion Efficiency: 14.65 % + + ************************************************************ + * HEATING, COOLING AND/OR ELECTRICITY PRODUCTION PROFILE * + ************************************************************ + YEAR THERMAL GEOFLUID PUMP NET FIRST LAW + DRAWDOWN TEMPERATURE POWER POWER EFFICIENCY + (degC) (MW) (MW) (%) + 1 1.0000 201.89 59.1637 530.7347 14.5683 + 2 1.0049 202.87 59.1237 537.8447 14.6641 + 3 1.0058 203.06 59.1163 539.1668 14.6818 + 4 1.0063 203.15 59.1124 539.8546 14.6910 + 5 1.0066 203.21 59.1099 540.3081 14.6970 + 6 1.0067 203.25 59.1098 540.5619 14.7004 + 7 1.0060 203.10 59.1388 539.4687 14.6855 + 8 0.9995 201.80 59.3570 529.8806 14.5541 + 9 1.0000 201.89 59.0983 530.8001 14.5701 + 10 1.0050 202.90 59.0862 538.0445 14.6673 + 11 1.0058 203.07 59.0664 539.2881 14.6841 + 12 1.0063 203.16 59.0422 539.9690 14.6935 + 13 1.0066 203.22 59.0191 540.4296 14.6999 + 14 1.0067 203.25 59.0038 540.6662 14.7033 + 15 1.0058 203.06 59.0298 539.2898 14.6846 + 16 0.9984 201.56 59.2774 528.2830 14.5337 + 17 1.0017 202.23 58.9814 533.3905 14.6066 + 18 1.0051 202.92 58.9804 538.2979 14.6721 + 19 1.0059 203.08 58.9802 539.4425 14.6873 + 20 1.0063 203.16 58.9802 540.0738 14.6957 + 21 1.0066 203.22 58.9804 540.4980 14.7014 + 22 1.0067 203.25 58.9838 540.6768 14.7037 + 23 1.0056 203.01 59.0289 538.9462 14.6801 + 24 0.9971 201.30 59.3179 526.3599 14.5071 + 25 1.0026 202.41 58.9810 534.6246 14.6231 + 26 1.0052 202.94 58.9811 538.4325 14.6739 + 27 1.0059 203.09 58.9812 539.5064 14.6882 + 28 1.0063 203.17 58.9814 540.1142 14.6962 + 29 1.0066 203.23 58.9816 540.5254 14.7017 + 30 1.0067 203.25 58.9858 540.6561 14.7034 + + + ******************************************************************* + * ANNUAL HEATING, COOLING AND/OR ELECTRICITY PRODUCTION PROFILE * + ******************************************************************* + YEAR ELECTRICITY HEAT RESERVOIR PERCENTAGE OF + PROVIDED EXTRACTED HEAT CONTENT TOTAL HEAT MINED + (GWh/year) (GWh/year) (10^15 J) (%) + 1 4222.0 28852.7 1217.11 7.86 + 2 4246.3 28937.3 1112.94 15.75 + 3 4253.7 28963.0 1008.67 23.64 + 4 4258.1 28978.2 904.35 31.54 + 5 4261.0 28988.2 799.99 39.44 + 6 4259.5 28983.2 695.65 47.34 + 7 4225.7 28868.2 591.72 55.21 + 8 4090.0 28402.1 489.48 62.95 + 9 4227.0 28869.2 385.55 70.81 + 10 4247.5 28940.2 281.36 78.70 + 11 4254.6 28964.5 177.09 86.59 + 12 4259.0 28979.2 72.77 94.49 + 13 4261.9 28988.8 -31.59 102.39 + 14 4259.5 28980.4 -135.92 110.29 + 15 4219.9 28845.1 -239.77 118.15 + 16 4092.9 28408.4 -342.04 125.89 + 17 4231.9 28883.1 -446.01 133.76 + 18 4249.0 28942.9 -550.21 141.65 + 19 4255.6 28966.0 -654.49 149.55 + 20 4259.7 28980.2 -758.82 157.44 + 21 4262.3 28989.2 -863.18 165.34 + 22 4258.6 28976.9 -967.49 173.24 + 23 4212.2 28818.7 -1071.24 181.09 + 24 4097.3 28423.0 -1173.56 188.84 + 25 4234.7 28893.1 -1277.58 196.71 + 26 4249.7 28945.4 -1381.78 204.60 + 27 4256.0 28967.4 -1486.07 212.50 + 28 4260.0 28981.1 -1590.40 220.40 + 29 4262.4 28989.6 -1694.76 228.30 + 30 4258.3 28975.7 -1799.07 236.19 + + *************************** + * SAM CASH FLOW PROFILE * + *************************** +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Year -4 Year -3 Year -2 Year -1 Year 0 Year 1 Year 2 Year 3 Year 4 Year 5 Year 6 Year 7 Year 8 Year 9 Year 10 Year 11 Year 12 Year 13 Year 14 Year 15 Year 16 Year 17 Year 18 Year 19 Year 20 Year 21 Year 22 Year 23 Year 24 Year 25 Year 26 Year 27 Year 28 Year 29 Year 30 +CONSTRUCTION +Capital expenditure schedule [construction] (%) 1.40 2.70 13.90 43.10 38.90 +Overnight capital expenditure [construction] ($) -34,043,049 -65,654,451 -337,998,843 -1,048,039,577 -945,910,430 +plus: +Inflation cost [construction] ($) -919,162 -3,593,202 -28,123,763 -117,855,471 -134,782,306 +equals: +Nominal capital expenditure [construction] ($) -34,962,211 -69,247,654 -366,122,605 -1,165,895,048 -1,080,692,736 + +Issuance of equity [construction] ($) 34,962,211 69,247,654 109,836,782 349,768,514 324,207,821 +Issuance of debt [construction] ($) 0 0 256,285,824 816,126,533 756,484,915 +Debt balance [construction] ($) 0 0 256,285,824 1,099,322,368 1,971,236,132 +Debt interest payment [construction] ($) 0 0 0 26,910,011 115,428,849 + +Installed cost [construction] ($) -34,962,211 -69,247,654 -366,122,605 -1,192,805,059 -1,196,121,585 +After-tax net cash flow [construction] ($) -34,962,211 -69,247,654 -109,836,782 -349,768,514 -324,207,821 + +ENERGY +Electricity to grid (kWh) 0.0 4,222,308,474 4,246,656,805 4,254,077,539 4,258,464,138 4,261,361,983 4,259,838,404 4,226,101,564 4,090,369,692 4,227,353,669 4,247,826,578 4,254,988,701 4,259,392,408 4,262,298,892 4,259,905,427 4,220,263,261 4,093,268,367 4,232,225,414 4,249,366,482 4,255,997,698 4,260,070,210 4,262,659,891 4,258,995,778 4,212,556,835 4,097,673,094 4,235,086,416 4,250,088,054 4,256,396,046 4,260,335,780 4,262,752,890 4,258,625,740 +Electricity from grid (kWh) 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +Electricity to grid net (kWh) 0.0 4,222,308,474 4,246,656,805 4,254,077,539 4,258,464,138 4,261,361,983 4,259,838,404 4,226,101,564 4,090,369,692 4,227,353,669 4,247,826,578 4,254,988,701 4,259,392,408 4,262,298,892 4,259,905,427 4,220,263,261 4,093,268,367 4,232,225,414 4,249,366,482 4,255,997,698 4,260,070,210 4,262,659,891 4,258,995,778 4,212,556,835 4,097,673,094 4,235,086,416 4,250,088,054 4,256,396,046 4,260,335,780 4,262,752,890 4,258,625,740 + +REVENUE +PPA price (cents/kWh) 0.0 9.50 9.50 9.56 9.61 9.67 9.73 9.79 9.84 9.90 9.96 10.01 10.07 10.13 10.18 10.24 10.30 10.36 10.41 10.47 10.53 10.58 10.64 10.70 10.75 10.81 10.87 10.93 10.98 11.04 11.10 +PPA revenue ($) 0 401,119,305 403,432,396 406,562,190 409,408,742 412,116,317 414,397,080 413,524,038 402,574,185 418,465,740 422,913,614 426,052,019 428,920,815 431,643,009 433,828,769 432,197,161 421,524,776 438,246,942 442,444,038 445,560,399 448,414,990 451,117,296 453,157,151 450,617,205 440,663,765 457,855,192 461,899,570 465,011,268 467,870,075 470,565,292 472,537,112 +Curtailment payment revenue ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Capacity payment revenue ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Salvage value ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1,429,629,557 +Total revenue ($) 0 401,119,305 403,432,396 406,562,190 409,408,742 412,116,317 414,397,080 413,524,038 402,574,185 418,465,740 422,913,614 426,052,019 428,920,815 431,643,009 433,828,769 432,197,161 421,524,776 438,246,942 442,444,038 445,560,399 448,414,990 451,117,296 453,157,151 450,617,205 440,663,765 457,855,192 461,899,570 465,011,268 467,870,075 470,565,292 1,902,166,669 + +Property tax net assessed value ($) 0 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 2,859,259,114 + +OPERATING EXPENSES +O&M fixed expense ($) 0 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 122,870,223 +O&M production-based expense ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +O&M capacity-based expense ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Fuel expense ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Electricity purchase ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Property tax expense ($) 0 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 6,290,370 +Insurance expense ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Total operating expenses ($) 0 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 129,160,593 + +EBITDA ($) 0 271,958,712 274,271,803 277,401,597 280,248,149 282,955,724 285,236,487 284,363,445 273,413,592 289,305,147 293,753,021 296,891,426 299,760,222 302,482,416 304,668,176 303,036,568 292,364,183 309,086,349 313,283,445 316,399,806 319,254,397 321,956,703 323,996,558 321,456,612 311,503,172 328,694,599 332,738,977 335,850,675 338,709,482 341,404,699 1,773,006,076 + +OPERATING ACTIVITIES +EBITDA ($) 0 271,958,712 274,271,803 277,401,597 280,248,149 282,955,724 285,236,487 284,363,445 273,413,592 289,305,147 293,753,021 296,891,426 299,760,222 302,482,416 304,668,176 303,036,568 292,364,183 309,086,349 313,283,445 316,399,806 319,254,397 321,956,703 323,996,558 321,456,612 311,503,172 328,694,599 332,738,977 335,850,675 338,709,482 341,404,699 1,773,006,076 +Interest earned on reserves ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +plus PBI if not available for debt service: +Federal PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Utility PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Other PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Debt interest payment ($) 0 137,986,529 136,525,748 134,962,712 133,290,264 131,500,745 129,585,959 127,537,138 125,344,899 122,999,204 120,489,310 117,803,724 114,930,146 111,855,418 108,565,459 105,045,203 101,278,529 97,248,188 92,935,723 88,321,386 83,384,045 78,101,090 72,448,328 66,399,873 59,928,026 53,003,149 45,593,532 37,665,241 29,181,970 20,104,869 10,392,372 +Cash flow from operating activities ($) 0 133,972,183 137,746,055 142,438,885 146,957,885 151,454,980 155,650,528 156,826,307 148,068,693 166,305,943 173,263,711 179,087,702 184,830,076 190,626,998 196,102,716 197,991,364 191,085,654 211,838,160 220,347,722 228,078,420 235,870,352 243,855,613 251,548,230 255,056,739 251,575,146 275,691,450 287,145,445 298,185,434 309,527,513 321,299,829 1,762,613,704 + +INVESTING ACTIVITIES +Total installed cost ($) -2,859,259,114 +Debt closing costs ($) 0 +Debt up-front fee ($) 0 +minus: +Total IBI income ($) 0 +Total CBI income ($) 0 +equals: +Purchase of property ($) -2,859,259,114 +plus: +Reserve (increase)/decrease debt service ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve (increase)/decrease working capital ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve (increase)/decrease receivables ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve (increase)/decrease major equipment 1 ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve (increase)/decrease major equipment 2 ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve (increase)/decrease major equipment 3 ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve capital spending major equipment 1 ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve capital spending major equipment 2 ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve capital spending major equipment 3 ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +equals: +Cash flow from investing activities ($) -2,859,259,114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +FINANCING ACTIVITIES +Issuance of equity ($) 888,022,982 +Size of debt ($) 1,971,236,132 +minus: +Debt principal payment ($) 0 20,868,301 22,329,082 23,892,118 25,564,566 27,354,086 29,268,872 31,317,693 33,509,931 35,855,627 38,365,520 41,051,107 43,924,684 46,999,412 50,289,371 53,809,627 57,576,301 61,606,642 65,919,107 70,533,444 75,470,786 80,753,741 86,406,502 92,454,958 98,926,805 105,851,681 113,261,299 121,189,590 129,672,861 138,749,961 148,462,458 +equals: +Cash flow from financing activities ($) 2,859,259,114 -20,868,301 -22,329,082 -23,892,118 -25,564,566 -27,354,086 -29,268,872 -31,317,693 -33,509,931 -35,855,627 -38,365,520 -41,051,107 -43,924,684 -46,999,412 -50,289,371 -53,809,627 -57,576,301 -61,606,642 -65,919,107 -70,533,444 -75,470,786 -80,753,741 -86,406,502 -92,454,958 -98,926,805 -105,851,681 -113,261,299 -121,189,590 -129,672,861 -138,749,961 -148,462,458 + +PROJECT RETURNS +Pre-tax Cash Flow: +Cash flow from operating activities ($) 0 133,972,183 137,746,055 142,438,885 146,957,885 151,454,980 155,650,528 156,826,307 148,068,693 166,305,943 173,263,711 179,087,702 184,830,076 190,626,998 196,102,716 197,991,364 191,085,654 211,838,160 220,347,722 228,078,420 235,870,352 243,855,613 251,548,230 255,056,739 251,575,146 275,691,450 287,145,445 298,185,434 309,527,513 321,299,829 1,762,613,704 +Cash flow from investing activities ($) -2,859,259,114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Cash flow from financing activities ($) 2,859,259,114 -20,868,301 -22,329,082 -23,892,118 -25,564,566 -27,354,086 -29,268,872 -31,317,693 -33,509,931 -35,855,627 -38,365,520 -41,051,107 -43,924,684 -46,999,412 -50,289,371 -53,809,627 -57,576,301 -61,606,642 -65,919,107 -70,533,444 -75,470,786 -80,753,741 -86,406,502 -92,454,958 -98,926,805 -105,851,681 -113,261,299 -121,189,590 -129,672,861 -138,749,961 -148,462,458 +Total pre-tax cash flow ($) 0 113,103,882 115,416,973 118,546,767 121,393,319 124,100,894 126,381,657 125,508,615 114,558,762 130,450,316 134,898,191 138,036,595 140,905,392 143,627,585 145,813,345 144,181,737 133,509,353 150,231,518 154,428,615 157,544,976 160,399,567 163,101,873 165,141,727 162,601,781 152,648,341 169,839,769 173,884,146 176,995,845 179,854,652 182,549,868 1,614,151,246 + +Pre-tax Returns: +Issuance of equity ($) 888,022,982 +Total pre-tax cash flow ($) 0 113,103,882 115,416,973 118,546,767 121,393,319 124,100,894 126,381,657 125,508,615 114,558,762 130,450,316 134,898,191 138,036,595 140,905,392 143,627,585 145,813,345 144,181,737 133,509,353 150,231,518 154,428,615 157,544,976 160,399,567 163,101,873 165,141,727 162,601,781 152,648,341 169,839,769 173,884,146 176,995,845 179,854,652 182,549,868 1,614,151,246 +Total pre-tax returns ($) -888,022,982 113,103,882 115,416,973 118,546,767 121,393,319 124,100,894 126,381,657 125,508,615 114,558,762 130,450,316 134,898,191 138,036,595 140,905,392 143,627,585 145,813,345 144,181,737 133,509,353 150,231,518 154,428,615 157,544,976 160,399,567 163,101,873 165,141,727 162,601,781 152,648,341 169,839,769 173,884,146 176,995,845 179,854,652 182,549,868 1,614,151,246 + +After-tax Returns: +Total pre-tax returns ($) -888,022,982 113,103,882 115,416,973 118,546,767 121,393,319 124,100,894 126,381,657 125,508,615 114,558,762 130,450,316 134,898,191 138,036,595 140,905,392 143,627,585 145,813,345 144,181,737 133,509,353 150,231,518 154,428,615 157,544,976 160,399,567 163,101,873 165,141,727 162,601,781 152,648,341 169,839,769 173,884,146 176,995,845 179,854,652 182,549,868 1,614,151,246 +Federal ITC total income ($) 0 857,777,734 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Federal PTC income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Federal tax benefit (liability) ($) 0 -14,675,165 -3,252,730 -4,193,384 -5,099,195 -6,000,615 -6,841,592 -7,077,271 -5,321,851 -8,977,417 -10,372,066 -11,539,456 -12,690,486 -13,852,450 -14,950,031 -15,328,601 -13,944,386 -18,104,122 -19,809,821 -21,359,401 -22,921,255 -36,700,749 -50,421,585 -51,124,848 -50,426,980 -55,260,973 -57,556,869 -59,769,779 -62,043,242 -64,402,944 -353,307,104 +State ITC total income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State PTC income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State tax benefit (liability) ($) 0 -3,331,188 -738,353 -951,877 -1,157,491 -1,362,109 -1,553,007 -1,606,505 -1,208,033 -2,037,828 -2,354,407 -2,619,398 -2,880,676 -3,144,436 -3,393,581 -3,479,515 -3,165,305 -4,109,544 -4,496,729 -4,848,476 -5,203,009 -8,330,884 -11,445,444 -11,605,082 -11,446,669 -12,543,961 -13,065,118 -13,567,437 -14,083,502 -14,619,142 -80,198,924 +Total after-tax returns ($) -888,022,982 952,875,263 111,425,890 113,401,506 115,136,632 116,738,169 117,987,058 116,824,839 108,028,877 119,435,072 122,171,718 123,877,741 125,334,229 126,630,699 127,469,733 125,373,622 116,399,662 128,017,852 130,122,065 131,337,099 132,275,304 118,070,239 103,274,698 99,871,852 90,774,692 102,034,835 103,262,160 103,658,628 103,727,908 103,527,782 1,180,645,218 + +After-tax net cash flow ($) -34,962,211 -69,247,654 -109,836,782 -349,768,514 -324,207,821 952,875,263 111,425,890 113,401,506 115,136,632 116,738,169 117,987,058 116,824,839 108,028,877 119,435,072 122,171,718 123,877,741 125,334,229 126,630,699 127,469,733 125,373,622 116,399,662 128,017,852 130,122,065 131,337,099 132,275,304 118,070,239 103,274,698 99,871,852 90,774,692 102,034,835 103,262,160 103,658,628 103,727,908 103,527,782 1,180,645,218 +After-tax cumulative IRR (%) NaN NaN NaN NaN NaN 3.49 8.65 12.65 15.63 17.82 19.44 20.61 21.42 22.09 22.61 23.01 23.32 23.57 23.76 23.90 24.01 24.10 24.18 24.24 24.28 24.32 24.34 24.36 24.37 24.38 24.39 24.40 24.41 24.41 24.46 +After-tax cumulative NPV ($) -34,962,211 -95,164,998 -178,182,731 -408,017,280 -593,229,494 -119,976,117 -71,863,906 -29,294,307 8,281,281 41,403,291 70,507,092 95,560,226 115,701,098 135,060,049 152,276,045 167,452,358 180,801,527 192,527,127 202,788,718 211,563,277 218,645,709 225,417,643 231,401,824 236,652,953 241,250,811 244,818,843 247,532,118 249,813,272 251,615,823 253,377,325 254,927,166 256,279,746 257,456,443 258,477,471 268,600,525 + +AFTER-TAX LCOE AND PPA PRICE +Annual costs ($) -888,022,982 551,755,958 -292,006,506 -293,160,684 -294,272,110 -295,378,148 -296,410,022 -296,699,199 -294,545,308 -299,030,668 -300,741,896 -302,174,278 -303,586,586 -305,012,310 -306,359,035 -306,823,539 -305,125,114 -310,229,089 -312,321,973 -314,223,300 -316,139,687 -333,047,057 -349,882,453 -350,745,353 -349,889,073 -355,820,357 -358,637,410 -361,352,640 -364,142,168 -367,037,510 708,108,106 +PPA revenue ($) 0 401,119,305 403,432,396 406,562,190 409,408,742 412,116,317 414,397,080 413,524,038 402,574,185 418,465,740 422,913,614 426,052,019 428,920,815 431,643,009 433,828,769 432,197,161 421,524,776 438,246,942 442,444,038 445,560,399 448,414,990 451,117,296 453,157,151 450,617,205 440,663,765 457,855,192 461,899,570 465,011,268 467,870,075 470,565,292 472,537,112 +Electricity to grid (kWh) 0.0 4,222,308,474 4,246,656,805 4,254,077,539 4,258,464,138 4,261,361,983 4,259,838,404 4,226,101,564 4,090,369,692 4,227,353,669 4,247,826,578 4,254,988,701 4,259,392,408 4,262,298,892 4,259,905,427 4,220,263,261 4,093,268,367 4,232,225,414 4,249,366,482 4,255,997,698 4,260,070,210 4,262,659,891 4,258,995,778 4,212,556,835 4,097,673,094 4,235,086,416 4,250,088,054 4,256,396,046 4,260,335,780 4,262,752,890 4,258,625,740 + +Present value of annual costs ($) 2,101,467,107 +Present value of annual energy nominal (kWh) 27,766,031,299 +LCOE Levelized cost of energy nominal (cents/kWh) 7.57 + +Present value of PPA revenue ($) 2,722,048,915 +Present value of annual energy nominal (kWh) 27,766,031,299 +LPPA Levelized PPA price nominal (cents/kWh) 9.80 + +PROJECT STATE INCOME TAXES +EBITDA ($) 0 271,958,712 274,271,803 277,401,597 280,248,149 282,955,724 285,236,487 284,363,445 273,413,592 289,305,147 293,753,021 296,891,426 299,760,222 302,482,416 304,668,176 303,036,568 292,364,183 309,086,349 313,283,445 316,399,806 319,254,397 321,956,703 323,996,558 321,456,612 311,503,172 328,694,599 332,738,977 335,850,675 338,709,482 341,404,699 1,773,006,076 +State taxable PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Interest earned on reserves ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State taxable IBI income ($) 0 +State taxable CBI income ($) 0 +minus: +Debt interest payment ($) 0 137,986,529 136,525,748 134,962,712 133,290,264 131,500,745 129,585,959 127,537,138 125,344,899 122,999,204 120,489,310 117,803,724 114,930,146 111,855,418 108,565,459 105,045,203 101,278,529 97,248,188 92,935,723 88,321,386 83,384,045 78,101,090 72,448,328 66,399,873 59,928,026 53,003,149 45,593,532 37,665,241 29,181,970 20,104,869 10,392,372 +Total state tax depreciation ($) 0 60,759,256 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 60,759,256 0 0 0 0 0 0 0 0 0 +equals: +State taxable income ($) 0 73,212,927 16,227,543 20,920,373 25,439,373 29,936,467 34,132,016 35,307,795 26,550,181 44,787,431 51,745,199 57,569,190 63,311,564 69,108,485 74,584,204 76,472,852 69,567,142 90,319,648 98,829,209 106,559,908 114,351,840 183,096,357 251,548,230 255,056,739 251,575,146 275,691,450 287,145,445 298,185,434 309,527,513 321,299,829 1,762,613,704 + +State income tax rate (frac) 0.0 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 +State tax benefit (liability) ($) 0 -3,331,188 -738,353 -951,877 -1,157,491 -1,362,109 -1,553,007 -1,606,505 -1,208,033 -2,037,828 -2,354,407 -2,619,398 -2,880,676 -3,144,436 -3,393,581 -3,479,515 -3,165,305 -4,109,544 -4,496,729 -4,848,476 -5,203,009 -8,330,884 -11,445,444 -11,605,082 -11,446,669 -12,543,961 -13,065,118 -13,567,437 -14,083,502 -14,619,142 -80,198,924 + +PROJECT FEDERAL INCOME TAXES +EBITDA ($) 0 271,958,712 274,271,803 277,401,597 280,248,149 282,955,724 285,236,487 284,363,445 273,413,592 289,305,147 293,753,021 296,891,426 299,760,222 302,482,416 304,668,176 303,036,568 292,364,183 309,086,349 313,283,445 316,399,806 319,254,397 321,956,703 323,996,558 321,456,612 311,503,172 328,694,599 332,738,977 335,850,675 338,709,482 341,404,699 1,773,006,076 +Interest earned on reserves ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State tax benefit (liability) ($) 0 -3,331,188 -738,353 -951,877 -1,157,491 -1,362,109 -1,553,007 -1,606,505 -1,208,033 -2,037,828 -2,354,407 -2,619,398 -2,880,676 -3,144,436 -3,393,581 -3,479,515 -3,165,305 -4,109,544 -4,496,729 -4,848,476 -5,203,009 -8,330,884 -11,445,444 -11,605,082 -11,446,669 -12,543,961 -13,065,118 -13,567,437 -14,083,502 -14,619,142 -80,198,924 +State ITC total income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State PTC income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Federal taxable IBI income ($) 0 +Federal taxable CBI income ($) 0 +Federal taxable PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +minus: +Debt interest payment ($) 0 137,986,529 136,525,748 134,962,712 133,290,264 131,500,745 129,585,959 127,537,138 125,344,899 122,999,204 120,489,310 117,803,724 114,930,146 111,855,418 108,565,459 105,045,203 101,278,529 97,248,188 92,935,723 88,321,386 83,384,045 78,101,090 72,448,328 66,399,873 59,928,026 53,003,149 45,593,532 37,665,241 29,181,970 20,104,869 10,392,372 +Total federal tax depreciation ($) 0 60,759,256 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 121,518,512 60,759,256 0 0 0 0 0 0 0 0 0 +equals: +Federal taxable income ($) 0 69,881,738 15,489,190 19,968,496 24,281,881 28,574,358 32,579,009 33,701,290 25,342,147 42,749,602 49,390,792 54,949,792 60,430,888 65,964,049 71,190,623 72,993,337 66,401,837 86,210,104 94,332,480 101,711,432 109,148,831 174,765,473 240,102,785 243,451,657 240,128,477 263,147,489 274,080,327 284,617,997 295,444,011 306,680,687 1,682,414,780 + +Federal income tax rate (frac) 0.0 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 +Federal tax benefit (liability) ($) 0 -14,675,165 -3,252,730 -4,193,384 -5,099,195 -6,000,615 -6,841,592 -7,077,271 -5,321,851 -8,977,417 -10,372,066 -11,539,456 -12,690,486 -13,852,450 -14,950,031 -15,328,601 -13,944,386 -18,104,122 -19,809,821 -21,359,401 -22,921,255 -36,700,749 -50,421,585 -51,124,848 -50,426,980 -55,260,973 -57,556,869 -59,769,779 -62,043,242 -64,402,944 -353,307,104 + +CASH INCENTIVES +Federal IBI income ($) 0 +State IBI income ($) 0 +Utility IBI income ($) 0 +Other IBI income ($) 0 +Total IBI income ($) 0 + +Federal CBI income ($) 0 +State CBI income ($) 0 +Utility CBI income ($) 0 +Other CBI income ($) 0 +Total CBI income ($) 0 + +Federal PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Utility PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Other PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Total PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +TAX CREDITS +Federal PTC income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State PTC income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +Federal ITC amount income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Federal ITC percent income ($) 0 857,777,734 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Federal ITC total income ($) 0 857,777,734 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +State ITC amount income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State ITC percent income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State ITC total income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +DEBT REPAYMENT +Debt balance ($) 1,971,236,132 1,950,367,831 1,928,038,749 1,904,146,631 1,878,582,065 1,851,227,979 1,821,959,107 1,790,641,414 1,757,131,483 1,721,275,856 1,682,910,336 1,641,859,229 1,597,934,545 1,550,935,133 1,500,645,761 1,446,836,134 1,389,259,833 1,327,653,191 1,261,734,084 1,191,200,640 1,115,729,854 1,034,976,114 948,569,611 856,114,654 757,187,849 651,336,168 538,074,870 416,885,280 287,212,419 148,462,458 0 +Debt interest payment ($) 0 137,986,529 136,525,748 134,962,712 133,290,264 131,500,745 129,585,959 127,537,138 125,344,899 122,999,204 120,489,310 117,803,724 114,930,146 111,855,418 108,565,459 105,045,203 101,278,529 97,248,188 92,935,723 88,321,386 83,384,045 78,101,090 72,448,328 66,399,873 59,928,026 53,003,149 45,593,532 37,665,241 29,181,970 20,104,869 10,392,372 +Debt principal payment ($) 0 20,868,301 22,329,082 23,892,118 25,564,566 27,354,086 29,268,872 31,317,693 33,509,931 35,855,627 38,365,520 41,051,107 43,924,684 46,999,412 50,289,371 53,809,627 57,576,301 61,606,642 65,919,107 70,533,444 75,470,786 80,753,741 86,406,502 92,454,958 98,926,805 105,851,681 113,261,299 121,189,590 129,672,861 138,749,961 148,462,458 +Debt total payment ($) 0 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 + +DSCR (DEBT FRACTION) +EBITDA ($) 0 271,958,712 274,271,803 277,401,597 280,248,149 282,955,724 285,236,487 284,363,445 273,413,592 289,305,147 293,753,021 296,891,426 299,760,222 302,482,416 304,668,176 303,036,568 292,364,183 309,086,349 313,283,445 316,399,806 319,254,397 321,956,703 323,996,558 321,456,612 311,503,172 328,694,599 332,738,977 335,850,675 338,709,482 341,404,699 1,773,006,076 +minus: +Reserves major equipment 1 funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 2 funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 3 funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves receivables funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +equals: +Cash available for debt service (CAFDS) ($) 0 271,958,712 274,271,803 277,401,597 280,248,149 282,955,724 285,236,487 284,363,445 273,413,592 289,305,147 293,753,021 296,891,426 299,760,222 302,482,416 304,668,176 303,036,568 292,364,183 309,086,349 313,283,445 316,399,806 319,254,397 321,956,703 323,996,558 321,456,612 311,503,172 328,694,599 332,738,977 335,850,675 338,709,482 341,404,699 1,773,006,076 +Debt total payment ($) 0 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 158,854,830 +DSCR (pre-tax) 0.0 1.71 1.73 1.75 1.76 1.78 1.80 1.79 1.72 1.82 1.85 1.87 1.89 1.90 1.92 1.91 1.84 1.95 1.97 1.99 2.01 2.03 2.04 2.02 1.96 2.07 2.09 2.11 2.13 2.15 11.16 + +RESERVES +Reserves working capital funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves working capital disbursement ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves working capital balance ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +Reserves debt service funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves debt service disbursement ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves debt service balance ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +Reserves receivables funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves receivables disbursement ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves receivables balance ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +Reserves major equipment 1 funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 1 disbursement ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 1 balance ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +Reserves major equipment 2 funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 2 disbursement ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 2 balance ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +Reserves major equipment 3 funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 3 disbursement ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 3 balance ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +Reserves total reserves balance ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Interest on reserves (%/year) 1.75 +Interest earned on reserves ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/tests/examples/Fervo_Project_Cape-5.txt b/tests/examples/Fervo_Project_Cape-5.txt new file mode 100644 index 000000000..a12977f5a --- /dev/null +++ b/tests/examples/Fervo_Project_Cape-5.txt @@ -0,0 +1,116 @@ +# Case Study: 500 MWe EGS Project Modeled on Fervo Cape Station Phase II +# See documentation: https://softwareengineerprogrammer.github.io/GEOPHIRES/Fervo_Project_Cape-5.html + +# *** ECONOMIC/FINANCIAL PARAMETERS *** +# ************************************* +Economic Model, 5, -- The SAM Single Owner PPA economic model is used to calculate financial results including LCOE, NPV, IRR, and pro-forma cash flow analysis. See [GEOPHIRES documentation of SAM Economic Models](https://softwareengineerprogrammer.github.io/GEOPHIRES/SAM-Economic-Models.html) for details on how System Advisor Model financial models are integrated into GEOPHIRES. +Inflation Rate, .027, -- US inflation as of December 2025 + +Starting Electricity Sale Price, 0.095, -- Aligns with Geysers - Sacramento pricing in [2024b ATB](https://atb.nrel.gov/electricity/2024/geothermal) (NREL, 2025). See Sensitivity Analysis for effect of different prices on results. +Electricity Escalation Rate Per Year, 0.00057, -- Calibrated to reach 10¢/kWh at project year 11 +Ending Electricity Sale Price, 1, -- Note that this value does not directly determine price at the end of the project life, but rather as a cap as the maximum price to which the starting price can escalate. +Electricity Escalation Start Year, 1 + +Fraction of Investment in Bonds, .7, -- Approximate debt required to cover CAPEX after $1 billion sponsor equity per [Matson, 2024](https://www.linkedin.com/pulse/fervo-energy-technology-day-2024-entering-geothermal-decade-matson-n4stc/). Note that this source says that Fervo ultimately wants to target “15% sponsor equity, 15% bridge loan, and 70% construction to term loans”, but this case study does not attempt to model that capital structure precisely. +Discount Rate, 0.12, -- Typical discount rates for higher-risk projects may be 12–15%. +Inflated Bond Interest Rate, .07, -- 2024b ATB (NREL, 2025) + +Inflated Bond Interest Rate During Construction, 0.105, -- Higher than interest rate during normal operation to account for increased risk of default prior to COD. Value aligns with ATB discount rate (NREL, 2025). +Bond Financing Start Year, -2, -- Equity-only for first 2 construction years (ATB) + +Construction Years, 5, -- Ground broken in 2023 (Fervo Energy, 2023). Expected to reach full scale production in 2028 (Fervo Energy, 2025). See [GEOPHIRES documentation](SAM-EM_Multiple-Construction-Years.html) for details on how construction years affect CAPEX, IRR, and other calculations. + +# ATB advanced scenario +# Construction CAPEX Schedule, 0.09,0.28,0.1,0.34,0.28 + +# DOE scenario (alternative) +# Construction CAPEX Schedule, 0.014,0.027,0.137,0.274,0.548 + +# DOE-ATB hybrid scenario +Construction CAPEX Schedule, 0.014,0.027,0.139,0.431,0.389 + +Investment Tax Credit Rate, 0.3, -- Geothermal Drilling and Completions Apprenticeship Program ensures compliance with ITC labor requirements (Southern Utah University, 2024). +Combined Income Tax Rate, .2555, -- Federal Corporate Income Tax Rate of 21% plus Utah Corporate Franchise and Income Tax Rate of 4.55%. (Note: This input uses a simple summation of statutory rates; the effective combined rate calculated in the model may differ due to standard federal-state tax interactions.) +Property Tax Rate, 0.0022, -- Utah Inland Port Authority (UIPA) tax differential incentive + +Capital Cost for Power Plant for Electricity Generation, 1900, -- [US DOE, 2021](https://betterbuildingssolutioncenter.energy.gov/sites/default/files/attachments/Waste_Heat_to_Power_Fact_Sheet.pdf). Pricing information not publicly available for Turboden or Baker Hughes Gen 2 ORC units (Turboden, 2025; Jacobs, 2025). +Exploration Capital Cost, 30, -- Equivalent to 2024b ATB NF-EGS conservative scenario exploration assumption of 5 full-size wells (NREL, 2025), plus $1M for geophysical and field work, plus 15% contingency, plus 12% indirect costs. + +Well Drilling Cost Correlation, 3, -- 2025 NREL Geothermal Drilling Cost Curve Update (Akindipe and Witter, 2025). +Well Drilling and Completion Capital Cost Adjustment Factor, 0.9, -- 2024b Geothermal ATB ([NREL, 2025](https://atb.nrel.gov/electricity/2024b/geothermal)). Note: Fervo has claimed lower drilling costs equivalent to an adjustment factor of 0.8 (Latimer, 2025); the case study conservatively uses the higher ATB-aligned value. + +Reservoir Stimulation Capital Cost per Injection Well, 4, -- The baseline stimulation cost is calibrated from costs of high-intensity U.S. shale wells (Baytex Energy, 2024; Quantum Proppant Technologies, 2020), which are the closest technological analogue for multi-stage EGS (Gradl, 2018). Costs are also driven by the requirement for high-strength ceramic proppant rather than standard sand, which would crush or chemically degrade (diagenesis) over a 30-year lifecycle at 200℃ (Ko et al., 2023; Shiozawa & McClure, 2014) and the premium for ultra-high-temperature (HT) downhole tools. Note that all-in costs per well are higher than the baseline cost because they include additional indirect costs and contingency. +Reservoir Stimulation Capital Cost per Production Well, 4, -- See Reservoir Stimulation Capital Cost per Injection Well + +Field Gathering System Capital Cost Adjustment Factor, 0.54, -- Gathering costs represent 2% of facilities CAPEX per [Matson, 2024](https://www.linkedin.com/pulse/fervo-energy-technology-day-2024-entering-geothermal-decade-matson-n4stc/). + +# *** SURFACE & SUBSURFACE TECHNICAL PARAMETERS *** +# ************************************************* +End-Use Option, 1, -- Electricity +Power Plant Type, 2, -- Supercritical ORC +Plant Lifetime, 30, -- 30-year well life per [Geothermal Mythbusting: Water Use and Impacts](https://fervoenergy.com/geothermal-mythbusting-water-use-and-impacts/) (Fervo Energy, 2025). + +Reservoir Model, 1 + +Surface Temperature, 13, -- Surface temperature near Milford, UT (38.4987670, -112.9163432) ([Project InnerSpace, 2025](https://geomap.projectinnerspace.org/test/)). + +Number of Segments, 3 +Gradient 1, 74, -- Sedimentary overburden. 200℃ at 8500 ft depth (Fercho et al. 2024); 228.89℃ at 9824 ft (Norbeck et al. 2024). +Thickness 1, 2.5 +Gradient 2, 41, -- Crystalline reservoir +Thickness 2, 0.5 +Gradient 3, 39.1, -- Sugarloaf appraisal + +Reservoir Depth, 2.68, -- Extrapolated from surface temperature, gradient, and average production temperature of shallower and deeper producers in Singh et al., 2025. + +Reservoir Density, 2800, -- phyllite + quartzite + diorite + granodiorite ([Norbeck et al., 2023](https://doi.org/10.31223/X52X0B)) +Reservoir Heat Capacity, 790 +Reservoir Thermal Conductivity, 3.05 +Reservoir Porosity, 0.0118 + +Reservoir Volume Option, 1, -- FRAC_NUM_SEP: Reservoir volume calculated with fracture separation and number of fractures as input + +Number of Fractures per Stimulated Well, 150, -- The model assumes an Extreme Limited Entry stimulation design (Fervo Energy, 2023) utilizing 12 stages with 15 clusters per stage (derived from Singh et al., 2025) and 81–85% stimulation success rate per 2024b ATB Moderate Scenario (NREL, 2025). +Fracture Separation, 9.8255, -- Based on 30 foot cluster spacing (Singh et al., 2025) marginally uprated to align with long-term thermal decline behavior trend towards wider fracture spacing (Fercho et al., 2025). + +Fracture Shape, 4, -- Bench design and fracture geometry in Singh et al., 2025 are given in rectangular dimensions. +Fracture Width, 305, -- Matches intra-bench well spacing of 500 ft (corresponding to fracture length of 1000 ft) (Singh. et al., 2025) +Fracture Height, 95, -- Actual fracture geometry is irregular and heterogeneous; this height complies with the minimum height required by the implemented bench design (200 ft; 60.96 meters) and yields an effective fracture surface area consistent with simulation results in Singh. et al., 2025. + +Water Loss Fraction, 0.01, -- "Long-term modeling, calibrated to early field data, predicts circulation recapture rates exceeding 99%" ([Geothermal Mythbusting: Water Use and Impacts](https://fervoenergy.com/geothermal-mythbusting-water-use-and-impacts/); Fervo Energy, 2025). Modeling in Singh et al., 2025 predicts fluid loss of 0.36% to 0.49%. +Water Cost Adjustment Factor, 2, -- Local scarcity may increase procurement costs. Development near/on land with active/shut-in oil and gas wells could potentially utilize waste water to recover losses and offset costs. + +Ambient Temperature, 11.17, -- Average annual temperature of Milford, Utah ([NCEI](https://www.ncei.noaa.gov/access/us-climate-normals/#dataset=normals-annualseasonal&timeframe=30&station=USC00425654)). Note that this value affects heat to power conversion efficiency. The effects of hourly and seasonal ambient temperature fluctuations on efficiency and power generation are not modeled in this version of the case study. + +Utilization Factor, .9 +Plant Outlet Pressure, 2000 psi, -- McClure, 2024; Singh et al., 2025. +Circulation Pump Efficiency, 0.80 + +# *** Well Bores Parameters *** + +Number of Production Wells, 56, -- Number of production wells required to produce net generation greater than 500 MW (PPA minimum) and total generation less than 600 MW (Gen 2 ORCs gross capacity). +Number of Injection Wells per Production Well, 0.666, -- Modeled on the reference case 5-well bench pattern (3 producers : 2 injectors) described in Singh et al., 2025. + +Nonvertical Length per Multilateral Section, 5000 feet, -- Target lateral length given in environmental assessment (BLM, 2024). Note that lateral length is assumed to be an upper bound constraining the number of fractures per well for a given cluster spacing. +Number of Multilateral Sections, 0, -- This parameter is set to 0 because, for this case study, the cost of horizontal drilling is included within the 'vertical drilling cost.' This approach allows us to more directly convey the overall well drilling and completion cost. + +Production Flow Rate per Well, 107, -- Cape Station pilot testing reported a sustained flow rate of 95–100 kg/s and maximum flow rate of 107 kg/s (Fervo Energy, 2024). Modeling by Singh et al. suggests initial flow rates of 120–130 kg/sec that gradually decrease over time (Singh et al., 2025). The ATB Advanced Scenario models sustained flow rates of 110 kg/s (NREL, 2024). +Production Well Diameter, 8.535, -- Inner diameter of 9⅝ inch casing size, the next standard casing size up from 7 inches, implied by announcement of “increasing casing diameter” (Fervo Energy, 2025). +Injection Well Diameter, 8.535, -- See Production Well Diameter + +Production Wellhead Pressure, 300 psi, -- Set constant in Singh et al., 2025. Actual production WHP may gradually increase over time if flow rates are kept constant. + +Productivity Index, 1.7458, -- Based on ATB Conservative Scenario (NREL, 2025) derated by 30% per analyses that suggest lower productivity/injectivitity (Xing et al., 2025; Yearsley and Kombrink, 2024). +Injectivity Index, 2.1105, -- See Productivity Index + +Injection Temperature, 53.6, -- Calibrated with GEOPHIRES model-calculated reinjection temperature (Beckers and McCabe, 2019). Close to upper bound of Project Red injection temperatures (75–125℉; 23.89–51.67℃) (Norbeck and Latimer, 2023). +Ramey Production Wellbore Model, True, -- Ramey's model estimates the geofluid temperature drop in production wells +Injection Wellbore Temperature Gain, 3 + + +Maximum Drawdown, 0.023, -- This value represents the drop in production temperature compared to the initial temperature that is allowed before the wellfield is redrilled. It is tuned to keep minimum net electricity generation over the project lifetime ≥500 MWe. + +# *** SIMULATION PARAMETERS *** +# ***************************** +Maximum Temperature, 500 +Time steps per year, 12 diff --git a/tests/examples/Fervo_Project_Cape-6.out b/tests/examples/Fervo_Project_Cape-6.out new file mode 100644 index 000000000..6c2adcdd7 --- /dev/null +++ b/tests/examples/Fervo_Project_Cape-6.out @@ -0,0 +1,461 @@ + ***************** + ***CASE REPORT*** + ***************** + +Simulation Metadata +---------------------- + GEOPHIRES Version: 3.11.0 + Simulation Date: 2026-01-16 + Simulation Time: 11:42 + Calculation Time: 1.811 sec + + ***SUMMARY OF RESULTS*** + + End-Use Option: Electricity + Average Net Electricity Production: 114.98 MW + Electricity breakeven price: 7.55 cents/kWh + Total CAPEX: 596.72 MUSD + Number of production wells: 12 + Number of injection wells: 8 + Flowrate per production well: 107.0 kg/sec + Well depth: 2.7 kilometer + Segment 1 Geothermal gradient: 74 degC/km + Segment 1 Thickness: 2.5 kilometer + Segment 2 Geothermal gradient: 41 degC/km + Segment 2 Thickness: 0.5 kilometer + Segment 3 Geothermal gradient: 39.1 degC/km + + + ***ECONOMIC PARAMETERS*** + + Economic Model = SAM Single Owner PPA + Real Discount Rate: 12.00 % + Nominal Discount Rate: 15.02 % + WACC: 8.14 % + Project lifetime: 30 yr + Capacity factor: 90.0 % + Project NPV: 95.30 MUSD + After-tax IRR: 35.45 % + Project VIR=PI=PIR: 1.69 + Project MOIC: 5.26 + Project Payback Period: 3.88 yr + Estimated Jobs Created: 272 + + ***ENGINEERING PARAMETERS*** + + Number of Production Wells: 12 + Number of Injection Wells: 8 + Well depth: 2.7 kilometer + Water loss rate: 1.0 % + Pump efficiency: 80.0 % + Injection temperature: 56.6 degC + Production Wellbore heat transmission calculated with Ramey's model + Average production well temperature drop: 0.2 degC + Flowrate per production well: 107.0 kg/sec + Injection well casing ID: 8.535 in + Production well casing ID: 8.535 in + Number of times redrilling: 3 + Power plant type: Supercritical ORC + + + ***RESOURCE CHARACTERISTICS*** + + Maximum reservoir temperature: 500.0 degC + Number of segments: 3 + Segment 1 Geothermal gradient: 74 degC/km + Segment 1 Thickness: 2.5 kilometer + Segment 2 Geothermal gradient: 41 degC/km + Segment 2 Thickness: 0.5 kilometer + Segment 3 Geothermal gradient: 39.1 degC/km + + + ***RESERVOIR PARAMETERS*** + + Reservoir Model = Multiple Parallel Fractures Model (Gringarten) + Bottom-hole temperature: 205.38 degC + Fracture model = Rectangular + Well separation: fracture height: 95.00 meter + Fracture width: 305.00 meter + Fracture area: 28975.00 m**2 + Reservoir volume calculated with fracture separation and number of fractures as input + Number of fractures: 3000 + Fracture separation: 9.83 meter + Reservoir volume: 853796894 m**3 + Reservoir hydrostatic pressure: 25324.54 kPa + Plant outlet pressure: 13789.51 kPa + Production wellhead pressure: 2082.43 kPa + Productivity Index: 1.75 kg/sec/bar + Injectivity Index: 2.11 kg/sec/bar + Reservoir density: 2800.00 kg/m**3 + Reservoir thermal conductivity: 3.05 W/m/K + Reservoir heat capacity: 790.00 J/kg/K + + + ***RESERVOIR SIMULATION RESULTS*** + + Maximum Production Temperature: 203.2 degC + Average Production Temperature: 202.7 degC + Minimum Production Temperature: 197.4 degC + Initial Production Temperature: 201.9 degC + Average Reservoir Heat Extraction: 784.99 MW + Production Wellbore Heat Transmission Model = Ramey Model + Average Production Well Temperature Drop: 0.2 degC + Average Injection Well Pump Pressure Drop: -4972.5 kPa + Average Production Well Pump Pressure Drop: 6918.2 kPa + + + ***CAPITAL COSTS (M$)*** + + Drilling and completion costs: 92.98 MUSD + Drilling and completion costs per well: 4.65 MUSD + Stimulation costs: 96.60 MUSD + Surface power plant costs: 314.46 MUSD + Field gathering system costs: 9.21 MUSD + Total surface equipment costs: 323.67 MUSD + Exploration costs: 30.00 MUSD + Overnight Capital Cost: 543.24 MUSD + Interest during construction: 13.03 MUSD + Inflation costs during construction: 40.45 MUSD + Total CAPEX: 596.72 MUSD + + + ***OPERATING AND MAINTENANCE COSTS (M$/yr)*** + + Wellfield maintenance costs: 1.72 MUSD/yr + Power plant maintenance costs: 6.83 MUSD/yr + Water costs: 0.67 MUSD/yr + Redrilling costs: 18.96 MUSD/yr + Total operating and maintenance costs: 28.18 MUSD/yr + + + ***SURFACE EQUIPMENT SIMULATION RESULTS*** + + Initial geofluid availability: 0.19 MW/(kg/s) + Maximum Total Electricity Generation: 128.50 MW + Average Total Electricity Generation: 127.65 MW + Minimum Total Electricity Generation: 119.50 MW + Initial Total Electricity Generation: 126.41 MW + Maximum Net Electricity Generation: 115.86 MW + Average Net Electricity Generation: 114.98 MW + Minimum Net Electricity Generation: 106.62 MW + Initial Net Electricity Generation: 113.73 MW + Average Annual Total Electricity Generation: 1006.40 GWh + Average Annual Net Electricity Generation: 906.56 GWh + Initial pumping power/net installed power: 11.15 % + Average Pumping Power: 12.66 MW + Heat to Power Conversion Efficiency: 14.65 % + + ************************************************************ + * HEATING, COOLING AND/OR ELECTRICITY PRODUCTION PROFILE * + ************************************************************ + YEAR THERMAL GEOFLUID PUMP NET FIRST LAW + DRAWDOWN TEMPERATURE POWER POWER EFFICIENCY + (degC) (MW) (MW) (%) + 1 1.0000 201.89 12.6779 113.7289 14.5683 + 2 1.0049 202.87 12.6694 115.2524 14.6641 + 3 1.0058 203.06 12.6678 115.5357 14.6818 + 4 1.0063 203.15 12.6669 115.6831 14.6910 + 5 1.0066 203.21 12.6664 115.7802 14.6970 + 6 1.0067 203.25 12.6665 115.8313 14.7002 + 7 1.0059 203.07 12.6736 115.5548 14.6826 + 8 0.9987 201.62 12.7255 113.2693 14.5363 + 9 1.0000 201.89 12.6638 113.7430 14.5702 + 10 1.0050 202.90 12.6611 115.2955 14.6673 + 11 1.0058 203.07 12.6567 115.5621 14.6841 + 12 1.0063 203.16 12.6515 115.7081 14.6935 + 13 1.0066 203.22 12.6466 115.8066 14.6999 + 14 1.0067 203.25 12.6435 115.8529 14.7030 + 15 1.0056 203.03 12.6503 115.5077 14.6812 + 16 0.9974 201.36 12.7092 112.8913 14.5135 + 17 1.0017 202.23 12.6388 114.2980 14.6066 + 18 1.0051 202.92 12.6386 115.3496 14.6721 + 19 1.0059 203.08 12.6386 115.5948 14.6873 + 20 1.0063 203.16 12.6386 115.7301 14.6957 + 21 1.0066 203.22 12.6387 115.8208 14.7013 + 22 1.0067 203.24 12.6395 115.8537 14.7033 + 23 1.0054 202.97 12.6505 115.4238 14.6760 + 24 0.9960 201.08 12.7189 112.4403 14.4844 + 25 1.0026 202.41 12.6388 114.5624 14.6231 + 26 1.0052 202.94 12.6388 115.3784 14.6739 + 27 1.0059 203.09 12.6388 115.6085 14.6882 + 28 1.0063 203.17 12.6389 115.7387 14.6962 + 29 1.0066 203.23 12.6389 115.8266 14.7017 + 30 1.0067 203.24 12.6400 115.8477 14.7029 + + + ******************************************************************* + * ANNUAL HEATING, COOLING AND/OR ELECTRICITY PRODUCTION PROFILE * + ******************************************************************* + YEAR ELECTRICITY HEAT RESERVOIR PERCENTAGE OF + PROVIDED EXTRACTED HEAT CONTENT TOTAL HEAT MINED + (GWh/year) (GWh/year) (10^15 J) (%) + 1 904.7 6182.7 258.73 7.92 + 2 909.9 6200.8 236.40 15.87 + 3 911.5 6206.4 214.06 23.82 + 4 912.5 6209.6 191.71 31.77 + 5 913.1 6211.7 169.35 39.73 + 6 912.6 6210.2 146.99 47.69 + 7 904.4 6182.4 124.73 55.61 + 8 872.9 6073.9 102.87 63.39 + 9 905.8 6186.2 80.60 71.32 + 10 910.2 6201.5 58.27 79.26 + 11 911.7 6206.7 35.93 87.21 + 12 912.7 6209.8 13.57 95.17 + 13 913.3 6211.8 -8.79 103.13 + 14 912.6 6209.5 -31.15 111.08 + 15 903.0 6176.9 -53.38 119.00 + 16 873.7 6075.9 -75.26 126.78 + 17 906.8 6189.2 -97.54 134.71 + 18 910.5 6202.0 -119.87 142.66 + 19 911.9 6207.0 -142.21 150.61 + 20 912.8 6210.0 -164.57 158.57 + 21 913.3 6211.9 -186.93 166.53 + 22 912.4 6208.6 -209.28 174.48 + 23 901.2 6170.6 -231.49 182.39 + 24 874.9 6079.8 -253.38 190.18 + 25 907.4 6191.4 -275.67 198.11 + 26 910.7 6202.6 -298.00 206.06 + 27 912.0 6207.3 -320.35 214.01 + 28 912.9 6210.2 -342.70 221.96 + 29 913.4 6212.0 -365.07 229.92 + 30 912.3 6208.3 -387.42 237.88 + + *************************** + * SAM CASH FLOW PROFILE * + *************************** +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Year -2 Year -1 Year 0 Year 1 Year 2 Year 3 Year 4 Year 5 Year 6 Year 7 Year 8 Year 9 Year 10 Year 11 Year 12 Year 13 Year 14 Year 15 Year 16 Year 17 Year 18 Year 19 Year 20 Year 21 Year 22 Year 23 Year 24 Year 25 Year 26 Year 27 Year 28 Year 29 Year 30 +CONSTRUCTION +Capital expenditure schedule [construction] (%) 2.58 25.60 71.80 +Overnight capital expenditure [construction] ($) -14,032,092 -139,318,624 -389,891,689 +plus: +Inflation cost [construction] ($) -378,866 -7,624,769 -32,441,594 +equals: +Nominal capital expenditure [construction] ($) -14,410,958 -146,943,393 -422,333,283 + +Issuance of equity [construction] ($) 4,323,287 44,083,018 126,699,985 +Issuance of debt [construction] ($) 10,087,671 102,860,375 295,633,298 +Debt balance [construction] ($) 10,087,671 114,007,251 421,611,311 +Debt interest payment [construction] ($) 0 1,059,205 11,970,761 + +Installed cost [construction] ($) -14,410,958 -148,002,598 -434,304,044 +After-tax net cash flow [construction] ($) -4,323,287 -44,083,018 -126,699,985 + +ENERGY +Electricity to grid (kWh) 0.0 904,772,187 909,989,639 911,579,782 912,519,634 913,133,018 912,676,105 904,517,517 872,944,986 905,854,665 910,242,733 911,778,177 912,721,628 913,333,791 912,661,742 903,106,603 873,759,113 906,897,409 910,570,348 911,991,265 912,863,645 913,405,434 912,429,283 901,275,577 874,920,451 907,510,293 910,724,901 912,076,600.0 912,920,418 913,421,293 912,335,987 +Electricity from grid (kWh) 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +Electricity to grid net (kWh) 0.0 904,772,187 909,989,639 911,579,782 912,519,634 913,133,018 912,676,105 904,517,517 872,944,986 905,854,665 910,242,733 911,778,177 912,721,628 913,333,791 912,661,742 903,106,603 873,759,113 906,897,409 910,570,348 911,991,265 912,863,645 913,405,434 912,429,283 901,275,577 874,920,451 907,510,293 910,724,901 912,076,600.0 912,920,418 913,421,293 912,335,987 + +REVENUE +PPA price (cents/kWh) 0.0 9.50 9.50 9.56 9.61 9.67 9.73 9.79 9.84 9.90 9.96 10.01 10.07 10.13 10.18 10.24 10.30 10.36 10.41 10.47 10.53 10.58 10.64 10.70 10.75 10.81 10.87 10.93 10.98 11.04 11.10 +PPA revenue ($) 0 85,953,358 86,449,016 87,119,680 87,729,638 88,309,094 88,785,131 88,507,039 85,915,246 89,670,553 90,623,766 91,296,349 91,911,068 92,493,313 92,945,472 92,487,147 89,979,713 93,909,227 94,808,585 95,476,366 96,088,027 96,665,697 97,082,476 96,409,448 94,088,945 98,110,938 98,977,582 99,644,369 100,256,920 100,832,577 101,232,801 +Curtailment payment revenue ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Capacity payment revenue ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Salvage value ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 298,358,800 +Total revenue ($) 0 85,953,358 86,449,016 87,119,680 87,729,638 88,309,094 88,785,131 88,507,039 85,915,246 89,670,553 90,623,766 91,296,349 91,911,068 92,493,313 92,945,472 92,487,147 89,979,713 93,909,227 94,808,585 95,476,366 96,088,027 96,665,697 97,082,476 96,409,448 94,088,945 98,110,938 98,977,582 99,644,369 100,256,920 100,832,577 399,591,602 + +Property tax net assessed value ($) 0 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 596,717,601 + +OPERATING EXPENSES +O&M fixed expense ($) 0 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 28,182,177 +O&M production-based expense ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +O&M capacity-based expense ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Fuel expense ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Electricity purchase ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Property tax expense ($) 0 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 1,312,779 +Insurance expense ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Total operating expenses ($) 0 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 29,494,955 + +EBITDA ($) 0 56,458,402 56,954,060 57,624,724 58,234,682 58,814,139 59,290,176 59,012,084 56,420,290 60,175,598 61,128,811 61,801,393 62,416,112 62,998,358 63,450,516 62,992,192 60,484,758 64,414,271 65,313,629 65,981,410 66,593,072 67,170,742 67,587,520 66,914,493 64,593,990 68,615,982 69,482,627 70,149,413 70,761,965 71,337,621 370,096,646 + +OPERATING ACTIVITIES +EBITDA ($) 0 56,458,402 56,954,060 57,624,724 58,234,682 58,814,139 59,290,176 59,012,084 56,420,290 60,175,598 61,128,811 61,801,393 62,416,112 62,998,358 63,450,516 62,992,192 60,484,758 64,414,271 65,313,629 65,981,410 66,593,072 67,170,742 67,587,520 66,914,493 64,593,990 68,615,982 69,482,627 70,149,413 70,761,965 71,337,621 370,096,646 +Interest earned on reserves ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +plus PBI if not available for debt service: +Federal PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Utility PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Other PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Debt interest payment ($) 0 29,512,792 29,200,357 28,866,053 28,508,347 28,125,601 27,716,063 27,277,858 26,808,979 26,307,277 25,770,457 25,196,059 24,581,454 23,923,826 23,220,164 22,467,245 21,661,623 20,799,607 19,877,249 18,890,327 17,834,320 16,704,393 15,495,371 14,201,717 12,817,507 11,336,403 9,751,622 8,055,905 6,241,489 4,300,063 2,222,738 +Cash flow from operating activities ($) 0 26,945,611 27,753,703 28,758,672 29,726,335 30,688,538 31,574,112 31,734,225 29,611,312 33,868,321 35,358,354 36,605,334 37,834,659 39,074,532 40,230,353 40,524,946 38,823,135 43,614,665 45,436,380 47,091,083 48,758,752 50,466,349 52,092,150 52,712,776 51,776,483 57,279,579 59,731,005 62,093,508 64,520,476 67,037,558 367,873,908 + +INVESTING ACTIVITIES +Total installed cost ($) -596,717,601 +Debt closing costs ($) 0 +Debt up-front fee ($) 0 +minus: +Total IBI income ($) 0 +Total CBI income ($) 0 +equals: +Purchase of property ($) -596,717,601 +plus: +Reserve (increase)/decrease debt service ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve (increase)/decrease working capital ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve (increase)/decrease receivables ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve (increase)/decrease major equipment 1 ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve (increase)/decrease major equipment 2 ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve (increase)/decrease major equipment 3 ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve capital spending major equipment 1 ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve capital spending major equipment 2 ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserve capital spending major equipment 3 ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +equals: +Cash flow from investing activities ($) -596,717,601 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +FINANCING ACTIVITIES +Issuance of equity ($) 175,106,290 +Size of debt ($) 421,611,311 +minus: +Debt principal payment ($) 0 4,463,347 4,775,782 5,110,087 5,467,793 5,850,538 6,260,076 6,698,281 7,167,161 7,668,862 8,205,682 8,780,080 9,394,686 10,052,314 10,755,976 11,508,894 12,314,516 13,176,533 14,098,890 15,085,812 16,141,819 17,271,746 18,480,769 19,774,422 21,158,632 22,639,736 24,224,518 25,920,234 27,734,650 29,676,076 31,753,401 +equals: +Cash flow from financing activities ($) 596,717,601 -4,463,347 -4,775,782 -5,110,087 -5,467,793 -5,850,538 -6,260,076 -6,698,281 -7,167,161 -7,668,862 -8,205,682 -8,780,080 -9,394,686 -10,052,314 -10,755,976 -11,508,894 -12,314,516 -13,176,533 -14,098,890 -15,085,812 -16,141,819 -17,271,746 -18,480,769 -19,774,422 -21,158,632 -22,639,736 -24,224,518 -25,920,234 -27,734,650 -29,676,076 -31,753,401 + +PROJECT RETURNS +Pre-tax Cash Flow: +Cash flow from operating activities ($) 0 26,945,611 27,753,703 28,758,672 29,726,335 30,688,538 31,574,112 31,734,225 29,611,312 33,868,321 35,358,354 36,605,334 37,834,659 39,074,532 40,230,353 40,524,946 38,823,135 43,614,665 45,436,380 47,091,083 48,758,752 50,466,349 52,092,150 52,712,776 51,776,483 57,279,579 59,731,005 62,093,508 64,520,476 67,037,558 367,873,908 +Cash flow from investing activities ($) -596,717,601 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Cash flow from financing activities ($) 596,717,601 -4,463,347 -4,775,782 -5,110,087 -5,467,793 -5,850,538 -6,260,076 -6,698,281 -7,167,161 -7,668,862 -8,205,682 -8,780,080 -9,394,686 -10,052,314 -10,755,976 -11,508,894 -12,314,516 -13,176,533 -14,098,890 -15,085,812 -16,141,819 -17,271,746 -18,480,769 -19,774,422 -21,158,632 -22,639,736 -24,224,518 -25,920,234 -27,734,650 -29,676,076 -31,753,401 +Total pre-tax cash flow ($) 0 22,482,263 22,977,921 23,648,585 24,258,543 24,837,999 25,314,037 25,035,944 22,444,151 26,199,459 27,152,672 27,825,254 28,439,973 29,022,218 29,474,377 29,016,053 26,508,619 30,438,132 31,337,490 32,005,271 32,616,933 33,194,602 33,611,381 32,938,354 30,617,851 34,639,843 35,506,488 36,173,274 36,785,826 37,361,482 336,120,507 + +Pre-tax Returns: +Issuance of equity ($) 175,106,290 +Total pre-tax cash flow ($) 0 22,482,263 22,977,921 23,648,585 24,258,543 24,837,999 25,314,037 25,035,944 22,444,151 26,199,459 27,152,672 27,825,254 28,439,973 29,022,218 29,474,377 29,016,053 26,508,619 30,438,132 31,337,490 32,005,271 32,616,933 33,194,602 33,611,381 32,938,354 30,617,851 34,639,843 35,506,488 36,173,274 36,785,826 37,361,482 336,120,507 +Total pre-tax returns ($) -175,106,290 22,482,263 22,977,921 23,648,585 24,258,543 24,837,999 25,314,037 25,035,944 22,444,151 26,199,459 27,152,672 27,825,254 28,439,973 29,022,218 29,474,377 29,016,053 26,508,619 30,438,132 31,337,490 32,005,271 32,616,933 33,194,602 33,611,381 32,938,354 30,617,851 34,639,843 35,506,488 36,173,274 36,785,826 37,361,482 336,120,507 + +After-tax Returns: +Total pre-tax returns ($) -175,106,290 22,482,263 22,977,921 23,648,585 24,258,543 24,837,999 25,314,037 25,035,944 22,444,151 26,199,459 27,152,672 27,825,254 28,439,973 29,022,218 29,474,377 29,016,053 26,508,619 30,438,132 31,337,490 32,005,271 32,616,933 33,194,602 33,611,381 32,938,354 30,617,851 34,639,843 35,506,488 36,173,274 36,785,826 37,361,482 336,120,507 +Federal ITC total income ($) 0 179,015,280 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Federal PTC income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Federal tax benefit (liability) ($) 0 -2,859,420 -479,706 -681,147 -875,110 -1,067,979 -1,245,488 -1,277,582 -852,054 -1,705,350 -2,004,020 -2,253,971 -2,500,383 -2,748,910 -2,980,588 -3,039,638 -2,698,518 -3,658,956 -4,024,110 -4,355,787 -4,690,063 -7,574,035 -10,441,611 -10,566,012 -10,378,337 -11,481,405 -11,972,781 -12,446,333 -12,932,807 -13,437,343 -73,738,485 +State ITC total income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State PTC income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State tax benefit (liability) ($) 0 -649,074 -108,891 -154,617 -198,646 -242,426 -282,719 -290,005 -193,412 -387,106 -454,902 -511,640 -567,574 -623,989 -676,578 -689,982 -612,550 -830,565 -913,453 -988,742 -1,064,621 -1,719,268 -2,370,193 -2,398,431 -2,355,830 -2,606,221 -2,717,761 -2,825,255 -2,935,682 -3,050,209 -16,738,263 +Total after-tax returns ($) -175,106,290 197,989,049 22,389,324 22,812,821 23,184,787 23,527,595 23,785,829 23,468,358 21,398,685 24,107,002 24,693,749 25,059,643 25,372,016 25,649,320 25,817,211 25,286,432 23,197,550 25,948,611 26,399,927 26,660,742 26,862,249 23,901,300 20,799,577 19,973,910 17,883,684 20,552,217 20,815,945 20,901,686 20,917,337 20,873,930 245,643,759 + +After-tax net cash flow ($) -4,323,287 -44,083,018 -126,699,985 197,989,049 22,389,324 22,812,821 23,184,787 23,527,595 23,785,829 23,468,358 21,398,685 24,107,002 24,693,749 25,059,643 25,372,016 25,649,320 25,817,211 25,286,432 23,197,550 25,948,611 26,399,927 26,660,742 26,862,249 23,901,300 20,799,577 19,973,910 17,883,684 20,552,217 20,815,945 20,901,686 20,917,337 20,873,930 245,643,759 +After-tax cumulative IRR (%) NaN NaN NaN 9.80 17.61 23.24 27.07 29.65 31.40 32.58 33.32 33.91 34.33 34.64 34.86 35.02 35.14 35.22 35.28 35.32 35.36 35.38 35.40 35.41 35.42 35.43 35.43 35.43 35.44 35.44 35.44 35.44 35.45 +After-tax cumulative NPV ($) -4,323,287 -42,648,348 -138,411,764 -8,312,221 4,478,267 15,808,443 25,819,324 34,651,308 42,413,968 49,072,623 54,351,025 59,520,781 64,124,676 68,186,535 71,761,868 74,904,176 77,653,931 79,995,374 81,862,827 83,678,900 85,285,225 86,695,534 87,930,902 88,886,525 89,609,513 90,213,116 90,682,964 91,152,393 91,565,744 91,926,585 92,240,529 92,512,901 95,299,502 + +AFTER-TAX LCOE AND PPA PRICE +Annual costs ($) -175,106,290 112,035,691 -64,059,691 -64,306,858 -64,544,851 -64,781,499 -64,999,302 -65,038,681 -64,516,561 -65,563,551 -65,930,017 -66,236,706 -66,539,052 -66,843,993 -67,128,261 -67,200,715 -66,782,163 -67,960,616 -68,408,657 -68,815,623 -69,225,778 -72,764,397 -76,282,898 -76,435,538 -76,205,262 -77,558,721 -78,161,637 -78,742,682 -79,339,583 -79,958,647 144,410,957 +PPA revenue ($) 0 85,953,358 86,449,016 87,119,680 87,729,638 88,309,094 88,785,131 88,507,039 85,915,246 89,670,553 90,623,766 91,296,349 91,911,068 92,493,313 92,945,472 92,487,147 89,979,713 93,909,227 94,808,585 95,476,366 96,088,027 96,665,697 97,082,476 96,409,448 94,088,945 98,110,938 98,977,582 99,644,369 100,256,920 100,832,577 101,232,801 +Electricity to grid (kWh) 0.0 904,772,187 909,989,639 911,579,782 912,519,634 913,133,018 912,676,105 904,517,517 872,944,986 905,854,665 910,242,733 911,778,177 912,721,628 913,333,791 912,661,742 903,106,603 873,759,113 906,897,409 910,570,348 911,991,265 912,863,645 913,405,434 912,429,283 901,275,577 874,920,451 907,510,293 910,724,901 912,076,600.0 912,920,418 913,421,293 912,335,987 + +Present value of annual costs ($) 448,951,641 +Present value of annual energy nominal (kWh) 5,947,475,299 +LCOE Levelized cost of energy nominal (cents/kWh) 7.55 + +Present value of PPA revenue ($) 583,057,522 +Present value of annual energy nominal (kWh) 5,947,475,299 +LPPA Levelized PPA price nominal (cents/kWh) 9.80 + +PROJECT STATE INCOME TAXES +EBITDA ($) 0 56,458,402 56,954,060 57,624,724 58,234,682 58,814,139 59,290,176 59,012,084 56,420,290 60,175,598 61,128,811 61,801,393 62,416,112 62,998,358 63,450,516 62,992,192 60,484,758 64,414,271 65,313,629 65,981,410 66,593,072 67,170,742 67,587,520 66,914,493 64,593,990 68,615,982 69,482,627 70,149,413 70,761,965 71,337,621 370,096,646 +State taxable PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Interest earned on reserves ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State taxable IBI income ($) 0 +State taxable CBI income ($) 0 +minus: +Debt interest payment ($) 0 29,512,792 29,200,357 28,866,053 28,508,347 28,125,601 27,716,063 27,277,858 26,808,979 26,307,277 25,770,457 25,196,059 24,581,454 23,923,826 23,220,164 22,467,245 21,661,623 20,799,607 19,877,249 18,890,327 17,834,320 16,704,393 15,495,371 14,201,717 12,817,507 11,336,403 9,751,622 8,055,905 6,241,489 4,300,063 2,222,738 +Total state tax depreciation ($) 0 12,680,249 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 12,680,249 0 0 0 0 0 0 0 0 0 +equals: +State taxable income ($) 0 14,265,362 2,393,205 3,398,174 4,365,837 5,328,039 6,213,614 6,373,727 4,250,813 8,507,823 9,997,856 11,244,836 12,474,161 13,714,034 14,869,855 15,164,448 13,462,637 18,254,167 20,075,882 21,730,585 23,398,254 37,786,100 52,092,150 52,712,776 51,776,483 57,279,579 59,731,005 62,093,508 64,520,476 67,037,558 367,873,908 + +State income tax rate (frac) 0.0 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 +State tax benefit (liability) ($) 0 -649,074 -108,891 -154,617 -198,646 -242,426 -282,719 -290,005 -193,412 -387,106 -454,902 -511,640 -567,574 -623,989 -676,578 -689,982 -612,550 -830,565 -913,453 -988,742 -1,064,621 -1,719,268 -2,370,193 -2,398,431 -2,355,830 -2,606,221 -2,717,761 -2,825,255 -2,935,682 -3,050,209 -16,738,263 + +PROJECT FEDERAL INCOME TAXES +EBITDA ($) 0 56,458,402 56,954,060 57,624,724 58,234,682 58,814,139 59,290,176 59,012,084 56,420,290 60,175,598 61,128,811 61,801,393 62,416,112 62,998,358 63,450,516 62,992,192 60,484,758 64,414,271 65,313,629 65,981,410 66,593,072 67,170,742 67,587,520 66,914,493 64,593,990 68,615,982 69,482,627 70,149,413 70,761,965 71,337,621 370,096,646 +Interest earned on reserves ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State tax benefit (liability) ($) 0 -649,074 -108,891 -154,617 -198,646 -242,426 -282,719 -290,005 -193,412 -387,106 -454,902 -511,640 -567,574 -623,989 -676,578 -689,982 -612,550 -830,565 -913,453 -988,742 -1,064,621 -1,719,268 -2,370,193 -2,398,431 -2,355,830 -2,606,221 -2,717,761 -2,825,255 -2,935,682 -3,050,209 -16,738,263 +State ITC total income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State PTC income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Federal taxable IBI income ($) 0 +Federal taxable CBI income ($) 0 +Federal taxable PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +minus: +Debt interest payment ($) 0 29,512,792 29,200,357 28,866,053 28,508,347 28,125,601 27,716,063 27,277,858 26,808,979 26,307,277 25,770,457 25,196,059 24,581,454 23,923,826 23,220,164 22,467,245 21,661,623 20,799,607 19,877,249 18,890,327 17,834,320 16,704,393 15,495,371 14,201,717 12,817,507 11,336,403 9,751,622 8,055,905 6,241,489 4,300,063 2,222,738 +Total federal tax depreciation ($) 0 12,680,249 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 25,360,498 12,680,249 0 0 0 0 0 0 0 0 0 +equals: +Federal taxable income ($) 0 13,616,288 2,284,314 3,243,557 4,167,192 5,085,614 5,930,895 6,083,723 4,057,401 8,120,717 9,542,954 10,733,196 11,906,586 13,090,045 14,193,276 14,474,466 12,850,087 17,423,602 19,162,429 20,741,843 22,333,633 36,066,832 49,721,957 50,314,345 49,420,653 54,673,358 57,013,244 59,268,253 61,584,794 63,987,349 351,135,645 + +Federal income tax rate (frac) 0.0 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 0.21 +Federal tax benefit (liability) ($) 0 -2,859,420 -479,706 -681,147 -875,110 -1,067,979 -1,245,488 -1,277,582 -852,054 -1,705,350 -2,004,020 -2,253,971 -2,500,383 -2,748,910 -2,980,588 -3,039,638 -2,698,518 -3,658,956 -4,024,110 -4,355,787 -4,690,063 -7,574,035 -10,441,611 -10,566,012 -10,378,337 -11,481,405 -11,972,781 -12,446,333 -12,932,807 -13,437,343 -73,738,485 + +CASH INCENTIVES +Federal IBI income ($) 0 +State IBI income ($) 0 +Utility IBI income ($) 0 +Other IBI income ($) 0 +Total IBI income ($) 0 + +Federal CBI income ($) 0 +State CBI income ($) 0 +Utility CBI income ($) 0 +Other CBI income ($) 0 +Total CBI income ($) 0 + +Federal PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Utility PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Other PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Total PBI income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +TAX CREDITS +Federal PTC income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State PTC income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +Federal ITC amount income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Federal ITC percent income ($) 0 179,015,280 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Federal ITC total income ($) 0 179,015,280 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +State ITC amount income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State ITC percent income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +State ITC total income ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +DEBT REPAYMENT +Debt balance ($) 421,611,311 417,147,963 412,372,181 407,262,095 401,794,302 395,943,764 389,683,688 382,985,407 375,818,247 368,149,385 359,943,703 351,163,623 341,768,937 331,716,623 320,960,648 309,451,754 297,137,238 283,960,705 269,861,815 254,776,003 238,634,184 221,362,438 202,881,669 183,107,247 161,948,615 139,308,879 115,084,361 89,164,127 61,429,477 31,753,401 0 +Debt interest payment ($) 0 29,512,792 29,200,357 28,866,053 28,508,347 28,125,601 27,716,063 27,277,858 26,808,979 26,307,277 25,770,457 25,196,059 24,581,454 23,923,826 23,220,164 22,467,245 21,661,623 20,799,607 19,877,249 18,890,327 17,834,320 16,704,393 15,495,371 14,201,717 12,817,507 11,336,403 9,751,622 8,055,905 6,241,489 4,300,063 2,222,738 +Debt principal payment ($) 0 4,463,347 4,775,782 5,110,087 5,467,793 5,850,538 6,260,076 6,698,281 7,167,161 7,668,862 8,205,682 8,780,080 9,394,686 10,052,314 10,755,976 11,508,894 12,314,516 13,176,533 14,098,890 15,085,812 16,141,819 17,271,746 18,480,769 19,774,422 21,158,632 22,639,736 24,224,518 25,920,234 27,734,650 29,676,076 31,753,401 +Debt total payment ($) 0 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 + +DSCR (DEBT FRACTION) +EBITDA ($) 0 56,458,402 56,954,060 57,624,724 58,234,682 58,814,139 59,290,176 59,012,084 56,420,290 60,175,598 61,128,811 61,801,393 62,416,112 62,998,358 63,450,516 62,992,192 60,484,758 64,414,271 65,313,629 65,981,410 66,593,072 67,170,742 67,587,520 66,914,493 64,593,990 68,615,982 69,482,627 70,149,413 70,761,965 71,337,621 370,096,646 +minus: +Reserves major equipment 1 funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 2 funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 3 funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves receivables funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +equals: +Cash available for debt service (CAFDS) ($) 0 56,458,402 56,954,060 57,624,724 58,234,682 58,814,139 59,290,176 59,012,084 56,420,290 60,175,598 61,128,811 61,801,393 62,416,112 62,998,358 63,450,516 62,992,192 60,484,758 64,414,271 65,313,629 65,981,410 66,593,072 67,170,742 67,587,520 66,914,493 64,593,990 68,615,982 69,482,627 70,149,413 70,761,965 71,337,621 370,096,646 +Debt total payment ($) 0 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 33,976,139 +DSCR (pre-tax) 0.0 1.66 1.68 1.70 1.71 1.73 1.75 1.74 1.66 1.77 1.80 1.82 1.84 1.85 1.87 1.85 1.78 1.90 1.92 1.94 1.96 1.98 1.99 1.97 1.90 2.02 2.05 2.06 2.08 2.10 10.89 + +RESERVES +Reserves working capital funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves working capital disbursement ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves working capital balance ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +Reserves debt service funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves debt service disbursement ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves debt service balance ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +Reserves receivables funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves receivables disbursement ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves receivables balance ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +Reserves major equipment 1 funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 1 disbursement ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 1 balance ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +Reserves major equipment 2 funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 2 disbursement ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 2 balance ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +Reserves major equipment 3 funding ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 3 disbursement ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Reserves major equipment 3 balance ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +Reserves total reserves balance ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Interest on reserves (%/year) 1.75 +Interest earned on reserves ($) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/tests/examples/Fervo_Project_Cape-6.txt b/tests/examples/Fervo_Project_Cape-6.txt new file mode 100644 index 000000000..53404859a --- /dev/null +++ b/tests/examples/Fervo_Project_Cape-6.txt @@ -0,0 +1,116 @@ +# Case Study: 100 MWe EGS Project Modeled on Fervo Cape Station Phase I +# See documentation: https://softwareengineerprogrammer.github.io/GEOPHIRES/Fervo_Project_Cape-5.html + +# *** ECONOMIC/FINANCIAL PARAMETERS *** +# ************************************* +Economic Model, 5, -- The SAM Single Owner PPA economic model is used to calculate financial results including LCOE, NPV, IRR, and pro-forma cash flow analysis. See [GEOPHIRES documentation of SAM Economic Models](https://softwareengineerprogrammer.github.io/GEOPHIRES/SAM-Economic-Models.html) for details on how System Advisor Model financial models are integrated into GEOPHIRES. +Inflation Rate, .027, -- US inflation as of December 2025 + +Starting Electricity Sale Price, 0.095, -- Aligns with Geysers - Sacramento pricing in [2024b ATB](https://atb.nrel.gov/electricity/2024/geothermal) (NREL, 2025). See Sensitivity Analysis for effect of different prices on results. +Electricity Escalation Rate Per Year, 0.00057, -- Calibrated to reach 10¢/kWh at project year 11 +Ending Electricity Sale Price, 1, -- Note that this value does not directly determine price at the end of the project life, but rather as a cap as the maximum price to which the starting price can escalate. +Electricity Escalation Start Year, 1 + +Fraction of Investment in Bonds, .7, -- Approximate debt required to cover CAPEX after $1 billion sponsor equity per [Matson, 2024](https://www.linkedin.com/pulse/fervo-energy-technology-day-2024-entering-geothermal-decade-matson-n4stc/). Note that this source says that Fervo ultimately wants to target “15% sponsor equity, 15% bridge loan, and 70% construction to term loans”, but this case study does not attempt to model that capital structure precisely. +Discount Rate, 0.12, -- Typical discount rates for higher-risk projects may be 12–15%. +Inflated Bond Interest Rate, .07, -- 2024b ATB (NREL, 2025) + +Inflated Bond Interest Rate During Construction, 0.105, -- Higher than interest rate during normal operation to account for increased risk of default prior to COD. Value aligns with ATB discount rate (NREL, 2025). +Bond Financing Start Year, -2, -- Equity-only for first 2 construction years (ATB) + +Construction Years, 3 + +# ATB advanced scenario +# Construction CAPEX Schedule, 0.09,0.28,0.1,0.34,0.28 + +# DOE scenario (alternative) +# Construction CAPEX Schedule, 0.014,0.027,0.137,0.274,0.548 + +# DOE-ATB hybrid scenario +Construction CAPEX Schedule, 0.014,0.027,0.139,0.431,0.389 + +Investment Tax Credit Rate, 0.3, -- Geothermal Drilling and Completions Apprenticeship Program ensures compliance with ITC labor requirements (Southern Utah University, 2024). +Combined Income Tax Rate, .2555, -- Federal Corporate Income Tax Rate of 21% plus Utah Corporate Franchise and Income Tax Rate of 4.55%. (Note: This input uses a simple summation of statutory rates; the effective combined rate calculated in the model may differ due to standard federal-state tax interactions.) +Property Tax Rate, 0.0022, -- Utah Inland Port Authority (UIPA) tax differential incentive + +Capital Cost for Power Plant for Electricity Generation, 1900, -- [US DOE, 2021](https://betterbuildingssolutioncenter.energy.gov/sites/default/files/attachments/Waste_Heat_to_Power_Fact_Sheet.pdf). Pricing information not publicly available for Turboden or Baker Hughes Gen 2 ORC units (Turboden, 2025; Jacobs, 2025). +Exploration Capital Cost, 30, -- Equivalent to 2024b ATB NF-EGS conservative scenario exploration assumption of 5 full-size wells (NREL, 2025), plus $1M for geophysical and field work, plus 15% contingency, plus 12% indirect costs. + +Well Drilling Cost Correlation, 3, -- 2025 NREL Geothermal Drilling Cost Curve Update (Akindipe and Witter, 2025). +Well Drilling and Completion Capital Cost Adjustment Factor, 0.9, -- 2024b Geothermal ATB ([NREL, 2025](https://atb.nrel.gov/electricity/2024b/geothermal)). Note: Fervo has claimed lower drilling costs equivalent to an adjustment factor of 0.8 (Latimer, 2025); the case study conservatively uses the higher ATB-aligned value. + +Reservoir Stimulation Capital Cost per Injection Well, 4, -- The baseline stimulation cost is calibrated from costs of high-intensity U.S. shale wells (Baytex Energy, 2024; Quantum Proppant Technologies, 2020), which are the closest technological analogue for multi-stage EGS (Gradl, 2018). Costs are also driven by the requirement for high-strength ceramic proppant rather than standard sand, which would crush or chemically degrade (diagenesis) over a 30-year lifecycle at 200℃ (Ko et al., 2023; Shiozawa & McClure, 2014) and the premium for ultra-high-temperature (HT) downhole tools. Note that all-in costs per well are higher than the baseline cost because they include additional indirect costs and contingency. +Reservoir Stimulation Capital Cost per Production Well, 4, -- See Reservoir Stimulation Capital Cost per Injection Well + +Field Gathering System Capital Cost Adjustment Factor, 0.54, -- Gathering costs represent 2% of facilities CAPEX per [Matson, 2024](https://www.linkedin.com/pulse/fervo-energy-technology-day-2024-entering-geothermal-decade-matson-n4stc/). + +# *** SURFACE & SUBSURFACE TECHNICAL PARAMETERS *** +# ************************************************* +End-Use Option, 1, -- Electricity +Power Plant Type, 2, -- Supercritical ORC +Plant Lifetime, 30, -- 30-year well life per [Geothermal Mythbusting: Water Use and Impacts](https://fervoenergy.com/geothermal-mythbusting-water-use-and-impacts/) (Fervo Energy, 2025). + +Reservoir Model, 1 + +Surface Temperature, 13, -- Surface temperature near Milford, UT (38.4987670, -112.9163432) ([Project InnerSpace, 2025](https://geomap.projectinnerspace.org/test/)). + +Number of Segments, 3 +Gradient 1, 74, -- Sedimentary overburden. 200℃ at 8500 ft depth (Fercho et al. 2024); 228.89℃ at 9824 ft (Norbeck et al. 2024). +Thickness 1, 2.5 +Gradient 2, 41, -- Crystalline reservoir +Thickness 2, 0.5 +Gradient 3, 39.1, -- Sugarloaf appraisal + +Reservoir Depth, 2.68, -- Extrapolated from surface temperature, gradient, and average production temperature of shallower and deeper producers in Singh et al., 2025. + +Reservoir Density, 2800, -- phyllite + quartzite + diorite + granodiorite ([Norbeck et al., 2023](https://doi.org/10.31223/X52X0B)) +Reservoir Heat Capacity, 790 +Reservoir Thermal Conductivity, 3.05 +Reservoir Porosity, 0.0118 + +Reservoir Volume Option, 1, -- FRAC_NUM_SEP: Reservoir volume calculated with fracture separation and number of fractures as input + +Number of Fractures per Stimulated Well, 150, -- The model assumes an Extreme Limited Entry stimulation design (Fervo Energy, 2023) utilizing 12 stages with 15 clusters per stage (derived from Singh et al., 2025) and 81–85% stimulation success rate per 2024b ATB Moderate Scenario (NREL, 2025). +Fracture Separation, 9.8255, -- Based on 30 foot cluster spacing (Singh et al., 2025) marginally uprated to align with long-term thermal decline behavior trend towards wider fracture spacing (Fercho et al., 2025). + +Fracture Shape, 4, -- Bench design and fracture geometry in Singh et al., 2025 are given in rectangular dimensions. +Fracture Width, 305, -- Matches intra-bench well spacing of 500 ft (corresponding to fracture length of 1000 ft) (Singh. et al., 2025) +Fracture Height, 95, -- Actual fracture geometry is irregular and heterogeneous; this height complies with the minimum height required by the implemented bench design (200 ft; 60.96 meters) and yields an effective fracture surface area consistent with simulation results in Singh. et al., 2025. + +Water Loss Fraction, 0.01, -- "Long-term modeling, calibrated to early field data, predicts circulation recapture rates exceeding 99%" ([Geothermal Mythbusting: Water Use and Impacts](https://fervoenergy.com/geothermal-mythbusting-water-use-and-impacts/); Fervo Energy, 2025). Modeling in Singh et al., 2025 predicts fluid loss of 0.36% to 0.49%. +Water Cost Adjustment Factor, 2, -- Local scarcity may increase procurement costs. Development near/on land with active/shut-in oil and gas wells could potentially utilize waste water to recover losses and offset costs. + +Ambient Temperature, 11.17, -- Average annual temperature of Milford, Utah ([NCEI](https://www.ncei.noaa.gov/access/us-climate-normals/#dataset=normals-annualseasonal&timeframe=30&station=USC00425654)). Note that this value affects heat to power conversion efficiency. The effects of hourly and seasonal ambient temperature fluctuations on efficiency and power generation are not modeled in this version of the case study. + +Utilization Factor, .9 +Plant Outlet Pressure, 2000 psi, -- McClure, 2024; Singh et al., 2025. +Circulation Pump Efficiency, 0.80 + +# *** Well Bores Parameters *** + +Number of Production Wells, 12 +Number of Injection Wells per Production Well, 0.666, -- Modeled on the reference case 5-well bench pattern (3 producers : 2 injectors) described in Singh et al., 2025. + +Nonvertical Length per Multilateral Section, 5000 feet, -- Target lateral length given in environmental assessment (BLM, 2024). Note that lateral length is assumed to be an upper bound constraining the number of fractures per well for a given cluster spacing. +Number of Multilateral Sections, 0, -- This parameter is set to 0 because, for this case study, the cost of horizontal drilling is included within the 'vertical drilling cost.' This approach allows us to more directly convey the overall well drilling and completion cost. + +Production Flow Rate per Well, 107, -- Cape Station pilot testing reported a sustained flow rate of 95–100 kg/s and maximum flow rate of 107 kg/s (Fervo Energy, 2024). Modeling by Singh et al. suggests initial flow rates of 120–130 kg/sec that gradually decrease over time (Singh et al., 2025). The ATB Advanced Scenario models sustained flow rates of 110 kg/s (NREL, 2024). +Production Well Diameter, 8.535, -- Inner diameter of 9⅝ inch casing size, the next standard casing size up from 7 inches, implied by announcement of “increasing casing diameter” (Fervo Energy, 2025). +Injection Well Diameter, 8.535, -- See Production Well Diameter + +Production Wellhead Pressure, 300 psi, -- Set constant in Singh et al., 2025. Actual production WHP may gradually increase over time if flow rates are kept constant. + +Productivity Index, 1.7458, -- Based on ATB Conservative Scenario (NREL, 2025) derated by 30% per analyses that suggest lower productivity/injectivitity (Xing et al., 2025; Yearsley and Kombrink, 2024). +Injectivity Index, 2.1105, -- See Productivity Index + +Injection Temperature, 53.6, -- Calibrated with GEOPHIRES model-calculated reinjection temperature (Beckers and McCabe, 2019). Close to upper bound of Project Red injection temperatures (75–125℉; 23.89–51.67℃) (Norbeck and Latimer, 2023). +Ramey Production Wellbore Model, True, -- Ramey's model estimates the geofluid temperature drop in production wells +Injection Wellbore Temperature Gain, 3 + + +Maximum Drawdown, 0.023, -- This value represents the drop in production temperature compared to the initial temperature that is allowed before the wellfield is redrilled. It is tuned to keep minimum net electricity generation over the project lifetime ≥100 MWe. + +# *** SIMULATION PARAMETERS *** +# ***************************** +Maximum Temperature, 500 +Time steps per year, 12 diff --git a/tests/examples/example_SAM-single-owner-PPA-2.out b/tests/examples/example_SAM-single-owner-PPA-2.out index 6bd01e4ff..1c69cd137 100644 --- a/tests/examples/example_SAM-single-owner-PPA-2.out +++ b/tests/examples/example_SAM-single-owner-PPA-2.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.10.22 - Simulation Date: 2025-12-15 - Simulation Time: 09:15 - Calculation Time: 1.013 sec + GEOPHIRES Version: 3.11.0 + Simulation Date: 2026-01-16 + Simulation Time: 11:42 + Calculation Time: 1.086 sec ***SUMMARY OF RESULTS*** @@ -34,7 +34,7 @@ Simulation Metadata After-tax IRR: 59.73 % Project VIR=PI=PIR: 4.58 Project MOIC: 9.59 - Project Payback Period: 1.13 yr + Project Payback Period: 2.13 yr Estimated Jobs Created: 976 ***ENGINEERING PARAMETERS*** diff --git a/tests/examples/example_SAM-single-owner-PPA-3.out b/tests/examples/example_SAM-single-owner-PPA-3.out index 85e785dac..a2508bc3f 100644 --- a/tests/examples/example_SAM-single-owner-PPA-3.out +++ b/tests/examples/example_SAM-single-owner-PPA-3.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.10.22 - Simulation Date: 2025-12-15 - Simulation Time: 09:15 - Calculation Time: 1.189 sec + GEOPHIRES Version: 3.11.0 + Simulation Date: 2026-01-16 + Simulation Time: 11:41 + Calculation Time: 1.221 sec ***SUMMARY OF RESULTS*** @@ -34,7 +34,7 @@ Simulation Metadata After-tax IRR: 30.00 % Project VIR=PI=PIR: 2.27 Project MOIC: 4.61 - Project Payback Period: 2.94 yr + Project Payback Period: 3.94 yr Estimated Jobs Created: 125 ***ENGINEERING PARAMETERS*** diff --git a/tests/examples/example_SAM-single-owner-PPA-4.out b/tests/examples/example_SAM-single-owner-PPA-4.out index f7e2cfebe..404143926 100644 --- a/tests/examples/example_SAM-single-owner-PPA-4.out +++ b/tests/examples/example_SAM-single-owner-PPA-4.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.10.22 - Simulation Date: 2025-12-15 - Simulation Time: 09:15 - Calculation Time: 1.205 sec + GEOPHIRES Version: 3.11.0 + Simulation Date: 2026-01-16 + Simulation Time: 11:42 + Calculation Time: 1.231 sec ***SUMMARY OF RESULTS*** @@ -34,7 +34,7 @@ Simulation Metadata After-tax IRR: 22.13 % Project VIR=PI=PIR: 1.76 Project MOIC: 3.38 - Project Payback Period: 4.29 yr + Project Payback Period: 5.29 yr Estimated Jobs Created: 125 ***ENGINEERING PARAMETERS*** @@ -434,7 +434,6 @@ Interest earned on reserves ($) 0 0 0 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ***EXTENDED ECONOMICS*** Royalty Holder NPV: 50.59 MUSD diff --git a/tests/examples/example_SAM-single-owner-PPA-5.out b/tests/examples/example_SAM-single-owner-PPA-5.out index 7d3ac7f2c..99e01af50 100644 --- a/tests/examples/example_SAM-single-owner-PPA-5.out +++ b/tests/examples/example_SAM-single-owner-PPA-5.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.10.22 - Simulation Date: 2025-12-15 - Simulation Time: 09:15 - Calculation Time: 1.769 sec + GEOPHIRES Version: 3.11.0 + Simulation Date: 2026-01-16 + Simulation Time: 11:42 + Calculation Time: 1.865 sec ***SUMMARY OF RESULTS*** @@ -34,7 +34,7 @@ Simulation Metadata After-tax IRR: 16.54 % Project VIR=PI=PIR: 1.58 Project MOIC: 5.72 - Project Payback Period: 8.92 yr + Project Payback Period: 9.92 yr Estimated Jobs Created: 250 ***ENGINEERING PARAMETERS*** @@ -69,9 +69,9 @@ Simulation Metadata Well separation: fracture height: 500.00 meter Fracture area: 250000.00 m**2 Reservoir volume calculated with fracture separation and number of fractures as input - Number of fractures: 1663 + Number of fractures: 1650 Fracture separation: 26.00 meter - Reservoir volume: 10803000000 m**3 + Reservoir volume: 10718500000 m**3 Reservoir hydrostatic pressure: 24578.69 kPa Plant outlet pressure: 6894.76 kPa Production wellhead pressure: 2240.80 kPa @@ -179,36 +179,36 @@ Simulation Metadata YEAR ELECTRICITY HEAT RESERVOIR PERCENTAGE OF PROVIDED EXTRACTED HEAT CONTENT TOTAL HEAT MINED (GWh/year) (GWh/year) (10^15 J) (%) - 1 859.5 5629.4 3310.87 0.61 - 2 865.7 5651.2 3290.53 1.22 - 3 867.6 5657.8 3270.16 1.83 - 4 868.8 5661.7 3249.78 2.44 - 5 869.6 5664.5 3229.38 3.05 - 6 870.2 5666.6 3208.98 3.67 - 7 870.7 5668.3 3188.58 4.28 - 8 871.1 5669.6 3168.17 4.89 - 9 871.4 5670.8 3147.75 5.51 - 10 871.7 5671.9 3127.33 6.12 - 11 872.0 5672.8 3106.91 6.73 - 12 872.2 5673.6 3086.49 7.34 - 13 872.4 5674.4 3066.06 7.96 - 14 872.6 5675.0 3045.63 8.57 - 15 872.8 5675.6 3025.20 9.18 - 16 872.9 5676.2 3004.76 9.80 - 17 873.1 5676.7 2984.33 10.41 - 18 873.2 5677.2 2963.89 11.02 - 19 873.4 5677.7 2943.45 11.64 - 20 873.5 5678.1 2923.01 12.25 - 21 873.6 5678.5 2902.56 12.87 - 22 873.7 5678.9 2882.12 13.48 - 23 873.8 5679.3 2861.67 14.09 - 24 873.9 5679.6 2841.23 14.71 - 25 874.0 5679.9 2820.78 15.32 - 26 874.1 5680.2 2800.33 15.93 - 27 874.2 5680.5 2779.88 16.55 - 28 874.3 5680.8 2759.43 17.16 - 29 874.4 5681.1 2738.98 17.78 - 30 874.4 5681.4 2718.53 18.39 + 1 859.5 5629.4 3284.81 0.61 + 2 865.7 5651.2 3264.47 1.23 + 3 867.6 5657.8 3244.10 1.84 + 4 868.8 5661.7 3223.72 2.46 + 5 869.6 5664.5 3203.33 3.08 + 6 870.2 5666.6 3182.93 3.70 + 7 870.7 5668.3 3162.52 4.31 + 8 871.1 5669.6 3142.11 4.93 + 9 871.4 5670.8 3121.70 5.55 + 10 871.7 5671.9 3101.28 6.17 + 11 872.0 5672.8 3080.86 6.78 + 12 872.2 5673.6 3060.43 7.40 + 13 872.4 5674.4 3040.00 8.02 + 14 872.6 5675.0 3019.57 8.64 + 15 872.8 5675.6 2999.14 9.26 + 16 872.9 5676.2 2978.71 9.87 + 17 873.1 5676.7 2958.27 10.49 + 18 873.2 5677.2 2937.83 11.11 + 19 873.4 5677.7 2917.39 11.73 + 20 873.5 5678.1 2896.95 12.35 + 21 873.6 5678.5 2876.51 12.97 + 22 873.7 5678.9 2856.06 13.59 + 23 873.8 5679.3 2835.62 14.20 + 24 873.9 5679.6 2815.17 14.82 + 25 874.0 5679.9 2794.72 15.44 + 26 874.1 5680.2 2774.28 16.06 + 27 874.2 5680.5 2753.83 16.68 + 28 874.3 5680.8 2733.37 17.30 + 29 874.4 5681.1 2712.92 17.92 + 30 874.4 5681.4 2692.47 18.54 *************************** * SAM CASH FLOW PROFILE * @@ -232,9 +232,9 @@ Installed cost [construction] ($) -6,132,082 -12,546,240 -44,921 After-tax net cash flow [construction] ($) -6,132,082 -12,546,240 -44,921,812 -22,977,507 -47,011,979 -48,093,255 -98,398,799 ENERGY -Electricity to grid (kWh) 0.0 859,490,068 865,758,172 867,671,460 868,803,529 869,596,370 870,200,786 870,686,054 871,089,570 871,433,728 871,732,959 871,997,081 872,233,061 872,446,015 872,639,805 872,817,414 872,981,192 873,133,019 873,274,427 873,406,675 873,530,811 873,647,716 873,758,141 873,862,724 873,962,017 874,056,500 874,146,590 874,232,654 874,315,016 874,393,962 874,466,670 +Electricity to grid (kWh) 0.0 859,490,068 865,758,172 867,671,460 868,803,529 869,596,370 870,200,786 870,686,054 871,089,570 871,433,728 871,732,959 871,997,081 872,233,061 872,446,015 872,639,805 872,817,414 872,981,191 873,133,019 873,274,427 873,406,675 873,530,811 873,647,716 873,758,141 873,862,724 873,962,017 874,056,500 874,146,590 874,232,654 874,315,016 874,393,962 874,466,670 Electricity from grid (kWh) 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -Electricity to grid net (kWh) 0.0 859,490,068 865,758,172 867,671,460 868,803,529 869,596,370 870,200,786 870,686,054 871,089,570 871,433,728 871,732,959 871,997,081 872,233,061 872,446,015 872,639,805 872,817,414 872,981,192 873,133,019 873,274,427 873,406,675 873,530,811 873,647,716 873,758,141 873,862,724 873,962,017 874,056,500 874,146,590 874,232,654 874,315,016 874,393,962 874,466,670 +Electricity to grid net (kWh) 0.0 859,490,068 865,758,172 867,671,460 868,803,529 869,596,370 870,200,786 870,686,054 871,089,570 871,433,728 871,732,959 871,997,081 872,233,061 872,446,015 872,639,805 872,817,414 872,981,191 873,133,019 873,274,427 873,406,675 873,530,811 873,647,716 873,758,141 873,862,724 873,962,017 874,056,500 874,146,590 874,232,654 874,315,016 874,393,962 874,466,670 REVENUE PPA price (cents/kWh) 0.0 8.0 8.0 8.32 8.64 8.97 9.29 9.61 9.93 10.25 10.58 10.90 11.22 11.54 11.86 12.19 12.51 12.83 13.15 13.47 13.80 14.12 14.44 14.76 15.08 15.41 15.73 16.05 16.37 16.69 17.02 @@ -328,14 +328,14 @@ After-tax cumulative NPV ($) -6,132,082 -17,487,790 -54,288 AFTER-TAX LCOE AND PPA PRICE Annual costs ($) -280,081,674 163,885,873 -45,513,073 -46,385,473 -47,249,566 -48,114,308 -48,983,020 -49,857,494 -50,738,953 -51,628,379 -52,526,647 -53,434,591 -54,353,042 -55,282,847 -56,224,884 -57,180,074 -58,149,386 -59,133,850 -60,134,558 -61,152,671 -62,189,430 -67,252,394 -72,336,738 -73,437,733 -74,563,229 -75,714,948 -76,894,734 -78,104,556 -79,346,524 -80,622,890 179,112,172 PPA revenue ($) 0 68,759,205 69,260,654 72,207,619 75,099,377 77,968,011 80,824,249 83,672,930 86,516,616 89,356,814 92,194,478 95,030,242 97,864,549 100,697,719 103,529,986 106,361,530 109,192,487 112,022,966 114,853,053 117,682,815 120,512,311 123,341,585 126,170,676 128,999,615 131,828,431 134,657,144 137,485,776 140,314,341 143,142,854 145,971,328 148,799,249 -Electricity to grid (kWh) 0.0 859,490,068 865,758,172 867,671,460 868,803,529 869,596,370 870,200,786 870,686,054 871,089,570 871,433,728 871,732,959 871,997,081 872,233,061 872,446,015 872,639,805 872,817,414 872,981,192 873,133,019 873,274,427 873,406,675 873,530,811 873,647,716 873,758,141 873,862,724 873,962,017 874,056,500 874,146,590 874,232,654 874,315,016 874,393,962 874,466,670 +Electricity to grid (kWh) 0.0 859,490,068 865,758,172 867,671,460 868,803,529 869,596,370 870,200,786 870,686,054 871,089,570 871,433,728 871,732,959 871,997,081 872,233,061 872,446,015 872,639,805 872,817,414 872,981,191 873,133,019 873,274,427 873,406,675 873,530,811 873,647,716 873,758,141 873,862,724 873,962,017 874,056,500 874,146,590 874,232,654 874,315,016 874,393,962 874,466,670 Present value of annual costs ($) 554,074,192 -Present value of annual energy nominal (kWh) 7,877,183,891 +Present value of annual energy nominal (kWh) 7,877,183,890 LCOE Levelized cost of energy nominal (cents/kWh) 7.03 Present value of PPA revenue ($) 809,659,354 -Present value of annual energy nominal (kWh) 7,877,183,891 +Present value of annual energy nominal (kWh) 7,877,183,890 LPPA Levelized PPA price nominal (cents/kWh) 10.28 PROJECT STATE INCOME TAXES diff --git a/tests/examples/example_SAM-single-owner-PPA-5.txt b/tests/examples/example_SAM-single-owner-PPA-5.txt index fe78ef48d..b46d60ad2 100644 --- a/tests/examples/example_SAM-single-owner-PPA-5.txt +++ b/tests/examples/example_SAM-single-owner-PPA-5.txt @@ -46,7 +46,7 @@ Plant Lifetime, 30 Reservoir Model, 1 Reservoir Volume Option, 1, -- FRAC_NUM_SEP: Reservoir volume calculated with fracture separation and number of fractures as input -Number of Fractures, 1663, -- 55 fractures per well +Number of Fractures per Stimulated Well, 55 Fracture Shape, 3, -- Square Fracture Separation, 26, diff --git a/tests/examples/example_SAM-single-owner-PPA.out b/tests/examples/example_SAM-single-owner-PPA.out index ae6a8d647..a23128ead 100644 --- a/tests/examples/example_SAM-single-owner-PPA.out +++ b/tests/examples/example_SAM-single-owner-PPA.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.10.22 - Simulation Date: 2025-12-15 - Simulation Time: 09:15 - Calculation Time: 1.196 sec + GEOPHIRES Version: 3.11.0 + Simulation Date: 2026-01-16 + Simulation Time: 11:41 + Calculation Time: 1.222 sec ***SUMMARY OF RESULTS*** @@ -34,7 +34,7 @@ Simulation Metadata After-tax IRR: 24.35 % Project VIR=PI=PIR: 1.93 Project MOIC: 3.85 - Project Payback Period: 3.91 yr + Project Payback Period: 4.91 yr Estimated Jobs Created: 125 ***ENGINEERING PARAMETERS*** diff --git a/tests/geophires_x_tests/test_fervo_project_cape_5.py b/tests/geophires_x_tests/test_fervo_project_cape_5.py new file mode 100644 index 000000000..6a3de42b2 --- /dev/null +++ b/tests/geophires_x_tests/test_fervo_project_cape_5.py @@ -0,0 +1,487 @@ +from __future__ import annotations + +import math +import re +from pathlib import Path +from typing import Any + +from pint.facets.plain import PlainQuantity + +from base_test_case import BaseTestCase +from geophires_docs import generate_fervo_project_cape_5_md +from geophires_x.GeoPHIRESUtils import quantity +from geophires_x.GeoPHIRESUtils import sig_figs +from geophires_x.Parameter import HasQuantity +from geophires_x_client import GeophiresInputParameters +from geophires_x_client import GeophiresXClient +from geophires_x_client import GeophiresXResult +from geophires_x_client import ImmutableGeophiresInputParameters + + +class FervoProjectCape5TestCase(BaseTestCase): + """ + FIXME WIP - see https://github.com/softwareengineerprogrammer/GEOPHIRES/pull/117 + """ + + def test_internal_consistency(self): + + fpc5_result: GeophiresXResult = GeophiresXResult( + self._get_test_file_path('../examples/Fervo_Project_Cape-5.out') + ) + fpc5_input_params: GeophiresInputParameters = ImmutableGeophiresInputParameters( + from_file_path=self._get_test_file_path('../examples/Fervo_Project_Cape-5.txt') + ) + fpc5_input_params_dict: dict[str, Any] = self._get_input_parameters(fpc5_input_params) + + def _q(dict_val: str) -> PlainQuantity: + spl = dict_val.split(' ') + return quantity(float(spl[0]), spl[1]) + + lateral_length_q = _q(fpc5_input_params_dict['Nonvertical Length per Multilateral Section']) + frac_sep_q = quantity(float(fpc5_input_params_dict['Fracture Separation']), 'meter') + number_of_fracs_per_well = int(fpc5_input_params_dict['Number of Fractures per Stimulated Well']) + + self.assertLess(number_of_fracs_per_well * frac_sep_q, lateral_length_q) + + result_number_of_wells: int = self._number_of_wells(fpc5_result) + number_of_fracs = int(fpc5_result.result['RESERVOIR PARAMETERS']['Number of fractures']['value']) + self.assertEqual(number_of_fracs, result_number_of_wells * number_of_fracs_per_well) + + input_num_production_wells: int = int(fpc5_input_params_dict['Number of Production Wells']) + input_num_injection_wells: int = math.ceil( + float(fpc5_input_params_dict['Number of Injection Wells per Production Well']) * input_num_production_wells + ) + fpc5_input_params_number_of_wells = input_num_production_wells + input_num_injection_wells + self.assertEqual(result_number_of_wells, fpc5_input_params_number_of_wells) + + @staticmethod + def _get_input_parameters( + params: GeophiresInputParameters, include_parameter_comments: bool = False, include_line_comments: bool = False + ) -> dict[str, Any]: + """ + TODO consolidate with src/geophires_docs/generate_fervo_project_cape_5_md.py:30 as a common utility function. + Note doing so is non-trivial because there would need to be a mechanism to ensure parsing exactly matches + GEOPHIRES behavior, which may diverge from the below implementation under some circumstances. + """ + + comment_idx = 0 + ret: dict[str, Any] = {} + for line in params.as_text().split('\n'): + parts = line.strip().split(', ') # TODO generalize for array-type params + field = parts[0].strip() + if len(parts) >= 2 and not field.startswith('#'): + fieldValue = parts[1].strip() + if include_parameter_comments and len(parts) > 2: + fieldValue += ', ' + (', '.join(parts[2:])).strip() + ret[field] = fieldValue.strip() + + if include_line_comments and field.startswith('#'): + ret[f'_COMMENT-{comment_idx}'] = line.strip() + comment_idx += 1 + + # TODO preserve newlines + + return ret + + @staticmethod + def _number_of_wells(result: GeophiresXResult) -> int: + r: dict[str, dict[str, Any]] = result.result + + number_of_wells = ( + r['SUMMARY OF RESULTS']['Number of injection wells']['value'] + + r['SUMMARY OF RESULTS']['Number of production wells']['value'] + ) + + return number_of_wells + + def test_fervo_project_cape_5_results_against_reference_values(self): + """ + Asserts that results conform to some of the key reference values claimed in docs/Fervo_Project_Cape-5.md. + """ + + r = GeophiresXClient().get_geophires_result( + GeophiresInputParameters(from_file_path=self._get_test_file_path('../examples/Fervo_Project_Cape-5.txt')) + ) + + min_net_gen = r.result['SURFACE EQUIPMENT SIMULATION RESULTS']['Minimum Net Electricity Generation']['value'] + self.assertGreater(min_net_gen, 500) + self.assertLess(min_net_gen, 505) + + max_total_gen = r.result['SURFACE EQUIPMENT SIMULATION RESULTS']['Maximum Total Electricity Generation'][ + 'value' + ] + self.assertGreater(max_total_gen, 550) + self.assertLess(max_total_gen, 600) + + lcoe = r.result['SUMMARY OF RESULTS']['Electricity breakeven price']['value'] + self.assertGreater(lcoe, 7.5) + self.assertLess(lcoe, 8.5) + + redrills = r.result['ENGINEERING PARAMETERS']['Number of times redrilling']['value'] + self.assertGreater(redrills, 1) + self.assertLess(redrills, 6) + max_phase_2_permitted_wells = 320 + self.assertLess(self._number_of_wells(r) * redrills, max_phase_2_permitted_wells) + self.assertGreater(self._number_of_wells(r) * redrills, max_phase_2_permitted_wells * 0.75) + + well_cost = r.result['CAPITAL COSTS (M$)']['Drilling and completion costs per well']['value'] + self.assertLess(well_cost, 5.0) + self.assertGreater(well_cost, 4.0) + + pumping_power_pct = r.result['SURFACE EQUIPMENT SIMULATION RESULTS'][ + 'Initial pumping power/net installed power' + ]['value'] + self.assertGreater(pumping_power_pct, 5) + self.assertLess(pumping_power_pct, 15) + + num_prod_wells = r.result['SUMMARY OF RESULTS']['Number of production wells']['value'] + num_inj_wells = r.result['SUMMARY OF RESULTS']['Number of injection wells']['value'] + self.assertTrue(num_prod_wells * 0.5 < num_inj_wells < num_prod_wells) + self.assertTrue(74 < num_inj_wells + num_prod_wells < 124) + + def test_case_study_documentation(self): + """ + Parses result values from case study documentation Markdown and checks that they match the actual result. + Useful for catching when minor updates are made to the case study which need to be manually synced to the + documentation. + + Note: for future case studies, generate the documentation Markdown from the input/result rather than writing + (partially) by hand so that they are guaranteed to be in sync and don't need to be tested like this, + which has proved messy. + + Update 2026-01-13: Markdown is now partially generated from input and result in + src/geophires_docs/generate_fervo_project_cape_5_md.py. + """ + + def generate_documentation_markdown() -> None: + generate_fervo_project_cape_5_md.main(project_root=Path(self._get_test_file_path('../../')).absolute()) + + generate_documentation_markdown() # Ensure we're testing the latest version of the generated doc + + documentation_file_content = '\n'.join( + self._get_test_file_content('../../docs/Fervo_Project_Cape-5.md', encoding='utf-8') + ) + inputs_in_markdown = self.parse_markdown_inputs_structured(documentation_file_content) + results_in_markdown = self.parse_markdown_results_structured(documentation_file_content) + + example_result = GeophiresXResult(self._get_test_file_path('../examples/Fervo_Project_Cape-5.out')) + + expected_drilling_cost_MUSD_per_well = 4.46 + # number_of_doublets = inputs_in_markdown['Number of Doublets']['value'] + number_of_wells = self._number_of_wells(example_result) + self.assertAlmostEqualWithinPercentage( + expected_drilling_cost_MUSD_per_well * number_of_wells, + results_in_markdown['Well Drilling and Completion Costs']['value'], + percent=5, + ) + self.assertEqual('MUSD', results_in_markdown['Well Drilling and Completion Costs']['unit']) + + expected_base_stim_cost_MUSD_per_well = 4.0 + expected_all_in_stim_cost_MUSD_per_well = 4.83 + self.assertAlmostEqualWithinSigFigs( + expected_all_in_stim_cost_MUSD_per_well * number_of_wells, + results_in_markdown['Stimulation Costs']['value'], + 3, + ) + self.assertEqual('MUSD', results_in_markdown['Stimulation Costs']['unit']) + + self.assertEqual( + expected_base_stim_cost_MUSD_per_well, + inputs_in_markdown['Reservoir Stimulation Capital Cost per Production Well']['value'], + ) + self.assertEqual('MUSD', inputs_in_markdown['Reservoir Stimulation Capital Cost per Production Well']['unit']) + + class _Q(HasQuantity): + def __init__(self, vu: dict[str, Any]): + self.value = vu['value'] + + # https://stackoverflow.com/questions/2280334/shortest-way-of-creating-an-object-with-arbitrary-attributes-in-python + self.CurrentUnits = type('', (), {})() + + self.CurrentUnits.value = vu['unit'] + + capex_q = _Q(results_in_markdown['Total CAPEX']).quantity() + markdown_capex_USD_per_kW = ( + capex_q.to('USD').magnitude + / _Q(results_in_markdown['Maximum Net Electricity Generation']).quantity().to('kW').magnitude + ) + self.assertAlmostEqual( + sig_figs(markdown_capex_USD_per_kW, 2), results_in_markdown['Total CAPEX: $/kW']['value'] + ) + + field_mapping = { + 'LCOE': 'Electricity breakeven price', + 'Project capital costs: Total CAPEX': 'Total CAPEX', + 'Well Drilling and Completion Costs': 'Drilling and completion costs per well', + 'Well Drilling and Completion Costs total': 'Drilling and completion costs', + 'Stimulation Costs total': 'Stimulation costs', + } + + ignore_keys = [ + 'Total CAPEX: $/kW', # See https://github.com/NREL/GEOPHIRES-X/issues/391 + 'Total fracture surface area per production well', + 'Stimulation Costs', # remapped to 'Stimulation Costs total' + ] + + example_result_values = {} + for key, _ in results_in_markdown.items(): + if key not in ignore_keys: + mapped_key = field_mapping.get(key) if key in field_mapping else key + entry = example_result._get_result_field(mapped_key) + if entry is not None and 'value' in entry: + entry['value'] = sig_figs(entry['value'], 3) + + example_result_values[key] = entry + + for ignore_key in ignore_keys: + if ignore_key in results_in_markdown: + del results_in_markdown[ignore_key] + + result_capex_USD_per_kW = ( + _Q(example_result._get_result_field('Total CAPEX')).quantity().to('USD').magnitude + / _Q(example_result._get_result_field('Maximum Net Electricity Generation')).quantity().to('kW').magnitude + ) + self.assertAlmostEqual(sig_figs(result_capex_USD_per_kW, 2), sig_figs(markdown_capex_USD_per_kW, 2)) + + # FIXME WIP refactor to work with number of injection wells per production well + # num_doublets = inputs_in_markdown['Number of Doublets']['value'] + # self.assertEqual( + # example_result.result['SUMMARY OF RESULTS']['Number of production wells']['value'], num_doublets + # ) + # + # num_fracs_per_well = inputs_in_markdown['Number of Fractures per Well']['value'] + # expected_total_fracs = num_doublets * 2 * num_fracs_per_well + # self.assertEqual( + # expected_total_fracs, example_result.result['RESERVOIR PARAMETERS']['Number of fractures']['value'] + # ) + + # FIXME WIP + # self.assertEqual( + # example_result.result['RESERVOIR PARAMETERS']['Reservoir volume']['value'], + # results_in_markdown['Reservoir Volume']['value'] + # ) + + additional_expected_stim_indirect_cost_frac = 0.00 + expected_stim_cost_total_MUSD = ( + expected_all_in_stim_cost_MUSD_per_well + * self._number_of_wells(example_result) + * (1.0 + additional_expected_stim_indirect_cost_frac) + ) + self.assertAlmostEqualWithinSigFigs( + expected_stim_cost_total_MUSD, + example_result.result['CAPITAL COSTS (M$)']['Stimulation costs']['value'], + num_sig_figs=3, + ) + + def parse_markdown_results_structured(self, markdown_text: str) -> dict: + """ + Parses result values from markdown into a structured dictionary with values and units. + """ + raw_results = {} + table_pattern = re.compile(r'^\s*\|\s*(?!-)([^|]+?)\s*\|\s*([^|]+?)\s*\|', re.MULTILINE) + + # Pattern to strip HTML tags and extract text content + html_tag_pattern = re.compile(r'<[^>]+>') + + try: + results_start_index = markdown_text.index('## Results') + search_area = markdown_text[results_start_index:] + + matches = table_pattern.findall(search_area) + + # Use key_ and value_ to avoid shadowing + for match in matches: + key_ = match[0].strip() + # Strip HTML tags from the key (e.g., LCOE -> LCOE) + key_ = html_tag_pattern.sub('', key_).strip() + value_ = match[1].strip() + if key_.lower() not in ('metric', 'parameter'): + raw_results[key_] = value_ + except ValueError: + print("Warning: '## Results' section not found.") + return {} + + # Consistency check + special_case_pattern = re.compile(r'LCOE\s*=\s*(\S+)\s*and\s*IRR\s*=\s*(\S+)') + special_case_match = special_case_pattern.search(markdown_text) + if special_case_match: + lcoe_text = special_case_match.group(1).rstrip('.,;') + lcoe_table_base = raw_results.get('LCOE', '').split('(')[0].strip() + if lcoe_text != lcoe_table_base: + raise ValueError( + f'LCOE mismatch: Text value ({lcoe_text}) does not match table value ({lcoe_table_base}).' + ) + + # Now, process the raw results into the structured format + structured_results = {} + # Use key_ and value_ to avoid shadowing + for key_, value_ in raw_results.items(): + if key_ in [ + 'After-tax IRR', + 'Average Production Temperature', + 'LCOE', + 'Maximum Total Electricity Generation', + 'Minimum Net Electricity Generation', + 'Maximum Net Electricity Generation', + 'Number of times redrilling', + 'Total CAPEX', + 'Total CAPEX: $/kW', + 'WACC', + 'Well Drilling and Completion Costs', + 'Stimulation Costs', + ]: + structured_results[key_] = self._parse_value_unit(value_) + + # Handle drilling and stimulation costs in format: "$464M total ($4.46M/well)" + for result_with_total_key in ['Well Drilling and Completion Costs', 'Stimulation Costs']: + entry = structured_results[result_with_total_key] + + unit_str = entry['unit'] + # unit_str is like "total; $4.46M/well" after _parse_value_unit processes "$464M total ($4.46M/well)" + # The entry['value'] is 464 (total MUSD) + # We need to extract per-well value from unit string + + # Parse per-well value from the parenthetical part + per_well_match = re.search(r'\$(\d+\.?\d*)M/well', unit_str) + if per_well_match: + per_well_value = float(per_well_match.group(1)) + # Store total in 'X total' key + structured_results[f'{result_with_total_key} total'] = { + 'value': entry['value'], + 'unit': 'MUSD', + } + # Update entry to be per-well value + entry['value'] = per_well_value + entry['unit'] = 'MUSD/well' + + return structured_results + + def parse_markdown_inputs_structured(self, markdown_text: str) -> dict: + """ + Parses all input values from all tables under the '## Inputs' section + of a markdown file into a structured dictionary. + """ + try: + # Isolate the content from "## Inputs" to the next "## " header + sections = re.split(r'(^###\s.*)', markdown_text, flags=re.MULTILINE) + inputs_header_index = next(i for i, s in enumerate(sections) if s.startswith('### Inputs')) + inputs_content = sections[inputs_header_index + 1] + except (StopIteration, IndexError): + print("Warning: '## Inputs' section not found or is empty.") + return {} + + raw_inputs = {} + table_pattern = re.compile(r'^\s*\|\s*(?!-)([^|]+?)\s*\|\s*([^|]+?)\s*\|', re.MULTILINE) + matches = table_pattern.findall(inputs_content) + + for match in matches: + key_ = match[0].strip() + value_ = match[1].strip() + if key_.lower() not in ('parameter', 'metric'): + raw_inputs[key_] = value_ + + structured_inputs = {} + for key_, value_ in raw_inputs.items(): + key_ = key_.replace(' ', ' ') + if key_ == 'Construction CAPEX Schedule': + parsed_value_unit = {'value': value_, 'unit': 'percent'} + else: + parsed_value_unit = self._parse_value_unit(value_) + structured_inputs[key_] = parsed_value_unit + + return structured_inputs + + # noinspection PyMethodMayBeStatic + def _parse_value_unit(self, raw_string: str) -> dict: + """ + A helper function to parse a string and extract a numerical value and its unit. + It handles various formats like currency, percentages, text, and scientific notation. + """ + clean_str = re.split(r'\s*\(|,(?!\s*\d)', raw_string)[0].strip() + + if clean_str.startswith('$') and 'M total' in clean_str: + return {'value': float(clean_str.split('M total')[0][1:]), 'unit': 'MUSD'} + + # LCOE format ($X.X/MWh -> cents/kWh) + match = re.match(r'^\$(\d+\.?\d*)/MWh$', clean_str) + if match: + value = float(match.group(1)) + return {'value': round(value / 10, 2), 'unit': 'cents/kWh'} + + # Billion dollar format ($X.XB -> MUSD) + match = re.match(r'^\$(\d+\.?\d*)B$', clean_str) + if match: + value = float(match.group(1)) + return {'value': value * 1000, 'unit': 'MUSD'} + + # Million dollar format ($X.XM or $X.XM/unit) + match = re.match(r'^\$(\d+\.?\d*)M(\/.*)?$', clean_str) + if match: + value = float(match.group(1)) + unit_suffix = match.group(2) + unit = 'MUSD' + if unit_suffix: + unit = f'MUSD{unit_suffix}' + return {'value': value, 'unit': unit} + + # Dollar per kW format ($X/kW -> USD/kW) + match = re.match(r'^\$(\d+\.?\d*)/kW$', clean_str) + if match: + value = float(match.group(1)) + return {'value': value, 'unit': 'USD/kW'} + + # Percentage format (X.X%) + match = re.search(r'(\d+\.?\d*)%$', clean_str) + if match: + value = float(match.group(1)) + return {'value': value, 'unit': '%'} + + # Temperature format (X℃ -> degC) + match = re.search(r'(\d+\.?\d*)\s*℃$', clean_str) + if match: + value = float(match.group(1)) + return {'value': value, 'unit': 'degC'} + + # Scientific notation format (X.X*10⁶ Y) + match = re.match(r'^(\d+\.?\d*)\s*[×xX]\s*10[⁶6]\s*(.*)$', clean_str) + if match: + base_value = float(match.group(1)) + unit = match.group(2).strip() + return {'value': base_value * 1e6, 'unit': unit} + + # Generic number and unit parser + if clean_str.startswith('9⅝'): + parts = clean_str.split(' ') + value = 9.0 + 5.0 / 8.0 + unit = parts[1] if len(parts) > 1 else 'unknown' + return {'value': value, 'unit': unit} + + match = re.search(r'([\d\.,]+)\s*(.*)', clean_str) + if match: + value_str = match.group(1).replace(',', '').replace(' ', '') + unit = match.group(2).strip() + + if '.' in value_str: + value = float(value_str) + else: + value = int(value_str) + + return {'value': value, 'unit': unit if unit else 'count'} + + # Fallback for text-only values + return {'value': clean_str, 'unit': 'text'} + + def test_fervo_project_cape_6(self) -> None: + """ + Fervo_Project_Cape-6 is derived from Fervo_Project_Cape-5 - see tests/regenerate-example-result.sh + """ + + fpc5_result: GeophiresXResult = GeophiresXResult( + self._get_test_file_path('../examples/Fervo_Project_Cape-6.out') + ) + min_net_gen_dict = fpc5_result.result['SURFACE EQUIPMENT SIMULATION RESULTS'][ + 'Minimum Net Electricity Generation' + ] + fpc5_min_net_gen_mwe = quantity(min_net_gen_dict['value'], min_net_gen_dict['unit']).to('MW').magnitude + self.assertGreater(fpc5_min_net_gen_mwe, 100) + self.assertLess(fpc5_min_net_gen_mwe, 110) diff --git a/tests/geophires_x_tests/test_well_bores.py b/tests/geophires_x_tests/test_well_bores.py index 7db431f1b..30b2ebace 100644 --- a/tests/geophires_x_tests/test_well_bores.py +++ b/tests/geophires_x_tests/test_well_bores.py @@ -82,6 +82,50 @@ def test_number_of_doublets_non_integer(self): self.assertEqual(prod_inj_lcoe_2[0], 199) self.assertEqual(prod_inj_lcoe_2[1], 199) + def test_number_of_injection_wells_per_production_well(self): + r_ratio: GeophiresXResult = self._get_result( + { + 'Number of Production Wells': 63, + 'Number of Injection Wells per Production Well': 0.666, # 3:2 ratio + } + ) + + r_explicit_counts: GeophiresXResult = self._get_result( + {'Number of Production Wells': 63, 'Number of Injection Wells': 42} + ) + + self.assertEqual(self._prod_inj_lcoe_production(r_explicit_counts), self._prod_inj_lcoe_production(r_ratio)) + + self.assertEqual( + self._prod_inj_lcoe_production( + self._get_result( + { + 'Number of Production Wells': 2, # default value + 'Number of Injection Wells per Production Well': 3, + } + ) + ), + self._prod_inj_lcoe_production(self._get_result({'Number of Injection Wells per Production Well': 3})), + ) + + with self.assertRaises(RuntimeError): + self._get_result( + { + 'Number of Production Wells': 63, + 'Number of Injection Wells per Production Well': 0.6666, # 3:2 ratio + 'Number of Injection Wells': 42, + } + ) + + with self.assertRaises(RuntimeError): + self._get_result( + { + 'Number of Production Wells': 63, + 'Number of Injection Wells per Production Well': 0.6666, # 3:2 ratio + 'Number of Doublets': 52, + } + ) + # noinspection PyMethodMayBeStatic def _get_result(self, _params) -> GeophiresXResult: params = GeophiresInputParameters( diff --git a/tests/regenerate-example-result.env.template b/tests/regenerate-example-result.env.template new file mode 100644 index 000000000..e71088891 --- /dev/null +++ b/tests/regenerate-example-result.env.template @@ -0,0 +1,4 @@ +# export GEOPHIRES_FPC5_SENSITIVITY_ANALYSIS_PROJECT_ROOT= + +export SWS_GTP_CLIENT_USERNAME= +export SWS_GTP_CLIENT_USER_PASSWORD= diff --git a/tests/regenerate-example-result.sh b/tests/regenerate-example-result.sh index faef5cee3..e6fbc57dc 100755 --- a/tests/regenerate-example-result.sh +++ b/tests/regenerate-example-result.sh @@ -1,15 +1,16 @@ #!/bin/zsh +set -e +cd "$(dirname "$0")" # Use this script to regenerate example results in cases where changes in GEOPHIRES # calculations alter the example test output. Example: # ./tests/regenerate-example-result.sh SUTRAExample1 # See https://github.com/NREL/GEOPHIRES-X/issues/107 -# Note: make sure your virtualenv is activated before running or this script will fail +# Note: make sure your virtualenv is activated and you have run pip install -e . before running or this script will fail # or generate incorrect results. -cd "$(dirname "$0")" python -mgeophires_x examples/$1.txt examples/$1.out rm examples/$1.json @@ -18,3 +19,35 @@ then echo "Updating CSV..." python regenerate_example_result_csv.py example1_addons fi + +if [[ $1 == "Fervo_Project_Cape-5" ]] +then + python ../src/geophires_docs/generate_fervo_project_cape_5_docs.py + + echo "Regenerating Fervo_Project_Cape-6..." + + sed -e 's/Construction Years,.*/Construction Years, 3/' \ + -e 's/^Number of Production Wells,.*/Number of Production Wells, 12/' \ + -e 's/500 MWe/100 MWe/' \ + -e 's/Phase II/Phase I/' \ + examples/Fervo_Project_Cape-5.txt > examples/Fervo_Project_Cape-6.txt + + python -mgeophires_x examples/Fervo_Project_Cape-6.txt examples/Fervo_Project_Cape-6.out + rm examples/Fervo_Project_Cape-6.json + + if [ ! -f regenerate-example-result.env ] && [ -f regenerate-example-result.env.template ]; then + echo "Creating regenerate-example-result.env from template..." + cp regenerate-example-result.env.template regenerate-example-result.env + fi + + source regenerate-example-result.env + if [ -n "$GEOPHIRES_FPC5_SENSITIVITY_ANALYSIS_PROJECT_ROOT" ]; then + echo "Updating sensitivity analysis..." + STASH_PWD=$(pwd) + cd $GEOPHIRES_FPC5_SENSITIVITY_ANALYSIS_PROJECT_ROOT + source venv/bin/activate + python -m fpc_sensitivity_analysis.generate_geophires_fpc5_sensitivity_analysis + deactivate + cd $STASH_PWD + fi +fi diff --git a/tox.ini b/tox.ini index ce5b99e78..320d5c6a9 100644 --- a/tox.ini +++ b/tox.ini @@ -38,6 +38,7 @@ usedevelop = false deps = pytest pytest-cov + Jinja2 commands = {posargs:pytest --cov --cov-report=term-missing --cov-report=xml -vv tests} @@ -61,8 +62,10 @@ deps = -r{toxinidir}/docs/requirements.txt commands = python src/geophires_x_schema_generator/main.py --build-path docs/ + python src/geophires_docs/__main__.py sphinx-build {posargs:-E} -b html docs dist/docs sphinx-build docs dist/docs + python -c "import shutil; shutil.copytree('docs/_images', 'dist/docs/_images', dirs_exist_ok=True)" ; TODO re-enable linkcheck probably - `sphinx-build -b linkcheck docs dist/docs` [testenv:report]