11from __future__ import annotations
22
3+ import json
34from pathlib import Path
45
56import numpy as np
67from matplotlib import pyplot as plt
8+ from pint .facets .plain import PlainQuantity
79
810from geophires_docs import _FPC5_INPUT_FILE_PATH
911from geophires_docs import _FPC5_RESULT_FILE_PATH
1012from geophires_docs import _PROJECT_ROOT
13+ from geophires_docs import _get_input_parameters_dict
14+ from geophires_docs import _get_logger
1115from geophires_x_client import GeophiresInputParameters
16+ from geophires_x_client import GeophiresXClient
1217from geophires_x_client import GeophiresXResult
1318from geophires_x_client import ImmutableGeophiresInputParameters
1419
20+ _log = _get_logger (__name__ )
21+
22+
23+ def _get_full_net_production_profile (input_and_result : tuple [GeophiresInputParameters , GeophiresXResult ]):
24+ return _get_full_profile (input_and_result , 'Net Electricity Production' )
25+
26+
27+ def _get_full_production_temperature_profile (input_and_result : tuple [GeophiresInputParameters , GeophiresXResult ]):
28+ return _get_full_profile (
29+ input_and_result ,
30+ #'Produced Temperature'
31+ 'Reservoir Temperature History' ,
32+ )
33+
34+
35+ def _get_full_thermal_drawdown_profile (input_and_result : tuple [GeophiresInputParameters , GeophiresXResult ]):
36+ return _get_full_profile (input_and_result , 'Thermal Drawdown' )
37+
38+
39+ def _get_full_profile (input_and_result : tuple [GeophiresInputParameters , GeophiresXResult ], profile_key : str ):
40+ input_params : GeophiresInputParameters = input_and_result [0 ]
41+ result = GeophiresXClient ().get_geophires_result (input_params )
42+
43+ with open (result .json_output_file_path , encoding = 'utf-8' ) as f :
44+ full_result_obj = json .load (f )
45+
46+ net_gen_obj = full_result_obj [profile_key ]
47+ net_gen_obj_unit = net_gen_obj ['CurrentUnits' ].replace ('CELSIUS' , 'degC' )
48+ profile = [PlainQuantity (it , net_gen_obj_unit ) for it in net_gen_obj ['value' ]]
49+ return profile
50+
1551
1652def generate_net_power_graph (
17- result : GeophiresXResult , output_dir : Path , filename = 'fervo_project_cape-5-net-power-production.png'
53+ # result: GeophiresXResult,
54+ input_and_result : tuple [GeophiresInputParameters , GeophiresXResult ],
55+ output_dir : Path ,
56+ filename : str = 'fervo_project_cape-5-net-power-production.png' ,
1857) -> str :
1958 """
2059 Generate a graph of time vs net power production and save it to the output directory.
21-
22- Args:
23- result: The GEOPHIRES result object
24- output_dir: Directory to save the graph image
25-
26- Returns:
27- The filename of the generated graph
2860 """
29- print ('Generating net power production graph...' )
61+ _log . info ('Generating net power production graph...' )
3062
31- # Extract data from power generation profile
32- profile = result .power_generation_profile
33- headers = profile [0 ]
34- data = profile [1 :]
63+ profile = _get_full_net_production_profile (input_and_result )
64+ time_steps_per_year = int (_get_input_parameters_dict (input_and_result [0 ])['Time steps per year' ])
3565
36- # Find the indices for YEAR and NET POWER columns
37- year_idx = headers . index ( 'YEAR' )
38- net_power_idx = headers . index ( 'NET POWER (MW)' )
66+ # profile is a list of PlainQuantity values with time_steps_per_year datapoints per year
67+ # Convert to numpy arrays for plotting
68+ net_power = np . array ([ p . magnitude for p in profile ] )
3969
40- # Extract years and net power values
41- years = np . array ([ row [ year_idx ] for row in data ] )
42- net_power = np .array ([row [ net_power_idx ] for row in data ])
70+ # Generate time values: each datapoint represents 1/time_steps_per_year of a year
71+ # Starting from year 1 (first operational year )
72+ years = np .array ([( i + 1 ) / time_steps_per_year for i in range ( len ( profile )) ])
4373
4474 # Create the figure
4575 fig , ax = plt .subplots (figsize = (10 , 6 ))
@@ -48,12 +78,30 @@ def generate_net_power_graph(
4878 ax .plot (years , net_power , color = '#3399e6' , linewidth = 2 , marker = 'o' , markersize = 4 )
4979
5080 # Set labels and title
51- ax .set_xlabel ('Time (Years)' , fontsize = 12 )
81+ ax .set_xlabel ('Time (Years since COD )' , fontsize = 12 )
5282 ax .set_ylabel ('Net Power Production (MW)' , fontsize = 12 )
5383 ax .set_title ('Net Power Production Over Project Lifetime' , fontsize = 14 )
5484
5585 # Set axis limits
5686 ax .set_xlim (years .min (), years .max ())
87+ ax .set_ylim (490 , 610 )
88+
89+ # Add horizontal reference lines
90+ ax .axhline (y = 500 , color = '#e69500' , linestyle = '--' , linewidth = 1.5 , alpha = 0.8 )
91+ ax .text (
92+ years .max () * 0.98 , 498 , 'PPA Minimum Production Requirement' , ha = 'right' , va = 'top' , fontsize = 9 , color = '#e69500'
93+ )
94+
95+ ax .axhline (y = 600 , color = '#33a02c' , linestyle = '--' , linewidth = 1.5 , alpha = 0.8 )
96+ ax .text (
97+ years .max () * 0.98 ,
98+ 602 ,
99+ 'Gross Maximum (Combined nameplate capacity of individual ORCs)' ,
100+ ha = 'right' ,
101+ va = 'bottom' ,
102+ fontsize = 9 ,
103+ color = '#33a02c' ,
104+ )
57105
58106 # Add grid for better readability
59107 ax .grid (True , linestyle = '--' , alpha = 0.7 )
@@ -66,24 +114,96 @@ def generate_net_power_graph(
66114 plt .savefig (save_path , dpi = 150 , bbox_inches = 'tight' )
67115 plt .close (fig )
68116
69- print (f'✓ Generated { save_path } ' )
117+ _log . info (f'✓ Generated { save_path } ' )
70118 return filename
71119
72120
73- def generate_production_temperature_graph (
74- result : GeophiresXResult , output_dir : Path , filename = 'fervo_project_cape-5-production-temperature.png'
121+ def generate_production_temperature_and_drawdown_graph (
122+ input_and_result : tuple [GeophiresInputParameters , GeophiresXResult ],
123+ output_dir : Path ,
124+ filename : str = 'fervo_project_cape-5-production-temperature.png' ,
75125) -> str :
76126 """
77- Generate a graph of time vs production temperature and save it to the output directory.
127+ Generate a graph of time vs production temperature with a horizontal line
128+ showing the temperature threshold at which maximum drawdown is reached.
129+ """
130+ _log .info ('Generating production temperature graph...' )
131+
132+ temp_profile = _get_full_production_temperature_profile (input_and_result )
133+ input_params_dict = _get_input_parameters_dict (input_and_result [0 ])
134+ time_steps_per_year = int (input_params_dict ['Time steps per year' ])
135+
136+ # Get maximum drawdown from input parameters (as a decimal, e.g., 0.03 for 3%)
137+ max_drawdown_str = str (input_params_dict .get ('Maximum Drawdown' ))
138+ # Handle case where value might have a comment after it
139+ max_drawdown = float (max_drawdown_str .split (',' )[0 ].strip ())
140+
141+ # Convert to numpy arrays
142+ temperatures_celsius = np .array ([p .magnitude for p in temp_profile ])
78143
79- Args:
80- result: The GEOPHIRES result object
81- output_dir: Directory to save the graph image
144+ # Calculate the temperature at maximum drawdown threshold
145+ # Drawdown = (T_initial - T_threshold) / T_initial
146+ # So: T_threshold = T_initial * (1 - max_drawdown)
147+ initial_temp = temperatures_celsius [0 ]
148+ max_drawdown_temp = initial_temp * (1 - max_drawdown )
82149
83- Returns:
84- The filename of the generated graph
150+ # Generate time values
151+ years = np .array ([(i + 1 ) / time_steps_per_year for i in range (len (temp_profile ))])
152+
153+ # Colors
154+ COLOR_TEMPERATURE = '#e63333'
155+ COLOR_THRESHOLD = '#e69500'
156+
157+ # Create the figure
158+ fig , ax = plt .subplots (figsize = (10 , 6 ))
159+
160+ # Plot temperature
161+ ax .plot (years , temperatures_celsius , color = COLOR_TEMPERATURE , linewidth = 2 , label = 'Production Temperature' )
162+ ax .set_xlabel ('Time (Years since COD)' , fontsize = 12 )
163+ ax .set_ylabel ('Production Temperature (°C)' , fontsize = 12 )
164+ ax .set_xlim (years .min (), years .max ())
165+
166+ # Add horizontal line for maximum drawdown threshold
167+ ax .axhline (y = max_drawdown_temp , color = COLOR_THRESHOLD , linestyle = '--' , linewidth = 1.5 , alpha = 0.8 )
168+ max_drawdown_pct = max_drawdown * 100
169+ ax .text (
170+ years .max () * 0.98 ,
171+ max_drawdown_temp - 0.5 ,
172+ f'Redrilling Threshold ({ max_drawdown_pct :.1f} % drawdown = { max_drawdown_temp :.1f} °C)' ,
173+ ha = 'right' ,
174+ va = 'top' ,
175+ fontsize = 9 ,
176+ color = COLOR_THRESHOLD ,
177+ )
178+
179+ # Title
180+ ax .set_title ('Production Temperature Over Project Lifetime' , fontsize = 14 )
181+
182+ # Add grid
183+ ax .grid (True , linestyle = '--' , alpha = 0.7 )
184+
185+ # Legend
186+ ax .legend (loc = 'best' )
187+
188+ # Ensure the output directory exists
189+ output_dir .mkdir (parents = True , exist_ok = True )
190+
191+ # Save the figure
192+ save_path = output_dir / filename
193+ plt .savefig (save_path , dpi = 150 , bbox_inches = 'tight' )
194+ plt .close (fig )
195+
196+ _log .info (f'✓ Generated { save_path } ' )
197+ return filename
198+
199+
200+ def generate_production_temperature_graph (
201+ result : GeophiresXResult , output_dir : Path , filename : str = 'fervo_project_cape-5-production-temperature.png'
202+ ) -> str :
203+ """
204+ Generate a graph of time vs production temperature and save it to the output directory.
85205 """
86- print ('Generating production temperature graph...' )
206+ _log . info ('Generating production temperature graph...' )
87207
88208 # Extract data from power generation profile
89209 profile = result .power_generation_profile
@@ -131,7 +251,7 @@ def generate_production_temperature_graph(
131251 plt .savefig (save_path , dpi = 150 , bbox_inches = 'tight' )
132252 plt .close (fig )
133253
134- print (f'✓ Generated { save_path } ' )
254+ _log . info (f'✓ Generated { save_path } ' )
135255 return filename
136256
137257
@@ -141,21 +261,24 @@ def generate_fervo_project_cape_5_graphs(
141261 output_dir : Path ,
142262) -> None :
143263 # base_case_input_params: GeophiresInputParameters = base_case[0]
144- # result: GeophiresXResult = base_case[1]
264+ # base_case_result: GeophiresXResult = base_case[1]
145265
146- # generate_net_power_graph(result , output_dir)
147- # generate_production_temperature_graph(result , output_dir)
266+ generate_net_power_graph (base_case , output_dir )
267+ generate_production_temperature_and_drawdown_graph ( base_case , output_dir )
148268
149- singh_et_al_base_simulation_result : GeophiresXResult = singh_et_al_base_simulation [1 ]
269+ if singh_et_al_base_simulation is not None :
270+ singh_et_al_base_simulation_result : GeophiresXResult = singh_et_al_base_simulation [1 ]
150271
151- # generate_net_power_graph(
152- # singh_et_al_base_simulation_result, output_dir, filename='singh_et_al_base_simulation-net-power-production.png'
153- # )
154- generate_production_temperature_graph (
155- singh_et_al_base_simulation_result ,
156- output_dir ,
157- filename = 'singh_et_al_base_simulation-production-temperature.png' ,
158- )
272+ # generate_net_power_graph(
273+ # singh_et_al_base_simulation_result, output_dir,
274+ # filename='singh_et_al_base_simulation-net-power-production.png'
275+ # )
276+
277+ generate_production_temperature_graph (
278+ singh_et_al_base_simulation_result ,
279+ output_dir ,
280+ filename = 'singh_et_al_base_simulation-production-temperature.png' ,
281+ )
159282
160283
161284if __name__ == '__main__' :
@@ -166,4 +289,6 @@ def generate_fervo_project_cape_5_graphs(
166289
167290 result_ = GeophiresXResult (_FPC5_RESULT_FILE_PATH )
168291
169- generate_fervo_project_cape_5_graphs (input_params_ , result_ , images_dir )
292+ generate_fervo_project_cape_5_graphs (
293+ (input_params_ , result_ ), None , images_dir # TODO configure (for local development)
294+ )
0 commit comments