Skip to content

Commit 53c098d

Browse files
Merge branch 'NREL:main' into main
2 parents 71b2a51 + 51ec7b7 commit 53c098d

File tree

3 files changed

+201
-6
lines changed

3 files changed

+201
-6
lines changed

src/geophires_monte_carlo/MC_GeoPHIRES3.py

Lines changed: 196 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@
2222
import matplotlib.pyplot as plt
2323
import numpy as np
2424
import pandas as pd
25+
from rich.console import Console
26+
from rich.table import Table
2527

2628
from geophires_monte_carlo.common import _get_logger
29+
from geophires_x.Parameter import Parameter
2730
from geophires_x_client import GeophiresInputParameters
2831
from geophires_x_client import GeophiresXClient
2932
from geophires_x_client import GeophiresXResult
@@ -33,6 +36,184 @@
3336
from hip_ra_x import HipRaXClient
3437

3538

39+
def Write_HTML_Output(
40+
html_path: str,
41+
df: pd.DataFrame,
42+
outputs: list,
43+
mins: list,
44+
maxs: list,
45+
medians: list,
46+
averages: list,
47+
means: list,
48+
std: list,
49+
full_names: set,
50+
short_names: set,
51+
) -> None:
52+
"""
53+
Write_HTML_Output - write the results of the Monte Carlo simulation to an HTML file
54+
:param html_path: the path to the HTML file to write
55+
:type html_path: str
56+
:param df: the DataFrame with the results
57+
:type df: pd.DataFrame
58+
:param outputs: the list of output variable names
59+
:type outputs: list
60+
:param mins: the list of minimum values for each output variable
61+
:type mins: list
62+
:param maxs: the list of maximum values for each output variable
63+
:type maxs: list
64+
:param medians: the list of median values for each output variable
65+
:type medians: list
66+
:param averages: the list of average values for each output variable
67+
:type averages: list
68+
:param means: the list of mean values for each output variable
69+
:type means: list
70+
:param std: the list of standard deviation values for each output variable
71+
:type std: list
72+
:param full_names: the list of full names for each output variable
73+
:type full_names: set
74+
:param short_names: the list of short names for each output variable
75+
:type short_names: set
76+
"""
77+
78+
# Build the tables that will hold those results, along with the columns for the input variables
79+
results_table = Table(title='GEOPHIRES/HIR-RA Monte Carlo Results')
80+
results_table.add_column('Iteration #', no_wrap=True, justify='center')
81+
for output in df.axes[1]:
82+
results_table.add_column(output.replace(',', ''), no_wrap=True, justify='center')
83+
84+
statistics_table = Table(title='GEOPHIRES/HIR-RA Monte Carlo Statistics')
85+
statistics_table.add_column('Output Parameter Name', no_wrap=True, justify='center')
86+
statistics_table.add_column('minimum', no_wrap=True, justify='center')
87+
statistics_table.add_column('maximum', no_wrap=True, justify='center')
88+
statistics_table.add_column('median', no_wrap=True, justify='center')
89+
statistics_table.add_column('average', no_wrap=True, justify='center')
90+
statistics_table.add_column('mean', no_wrap=True, justify='center')
91+
statistics_table.add_column('standard deviation', no_wrap=True, justify='center')
92+
93+
# Iterate over the rows of the DataFrame and add them to the results table
94+
for index, row in df.iterrows():
95+
data = row.values[0 : len(outputs)]
96+
97+
# have to deal with the special case where thr last column is actually
98+
# a compound string with multiple columns in it that looks like this:
99+
# ' (Gradient 1:47.219846973456924;Reservoir Temperature:264.7789623351493;...)'
100+
str_to_parse = str(row.values[len(outputs)]).strip().replace('(', '').replace(')', '')
101+
fields = str_to_parse.split(';')
102+
for field in fields:
103+
if len(field) > 0:
104+
key, value = field.split(':')
105+
data = np.append(data, float(value))
106+
107+
results_table.add_row(str(int(index)), *[render_default(d) for d in data])
108+
109+
for i in range(len(outputs)):
110+
statistics_table.add_row(
111+
outputs[i],
112+
render_default(mins[i]),
113+
render_default(maxs[i]),
114+
render_default(medians[i]),
115+
render_default(averages[i]),
116+
render_default(means[i]),
117+
render_default(std[i]),
118+
)
119+
120+
console = Console(style='bold white on black', force_terminal=True, record=True, width=500)
121+
console.print(results_table)
122+
console.print(' ')
123+
console.print(statistics_table)
124+
console.save_html(html_path)
125+
126+
# Write a reference to the image(s) into the HTML file by inserting before the "</body>" tag
127+
# build the string to be inserted first
128+
insert_string = ''
129+
for _ in range(len(full_names)):
130+
name_to_use = short_names.pop()
131+
insert_string = insert_string + f'<img src="{name_to_use}.png" alt="{name_to_use}">\n'
132+
133+
match_string = '</body>'
134+
with open(html_path, 'r+', encoding='UTF-8') as html_file:
135+
contents = html_file.readlines()
136+
if match_string in contents[-1]: # Handle last line to prevent IndexError
137+
pass
138+
else:
139+
for index, line in enumerate(contents):
140+
if match_string in line and insert_string not in contents[index + 1]:
141+
contents.insert(index, insert_string)
142+
break
143+
html_file.seek(0)
144+
html_file.writelines(contents)
145+
146+
147+
def UpgradeSymbologyOfUnits(unit: str) -> str:
148+
"""
149+
UpgradeSymbologyOfUnits is a function that takes a string that represents a unit and replaces the **2 and **3
150+
with the appropriate unicode characters for superscript 2 and 3, and replaces "deg" with the unicode character
151+
for degrees.
152+
:param unit: a string that represents a unit
153+
:return: a string that represents a unit with the appropriate unicode characters for superscript 2 and 3, and
154+
replaces "deg" with the unicode character for degrees.
155+
"""
156+
return unit.replace('**2', '\u00b2').replace('**3', '\u00b3').replace('deg', '\u00b0')
157+
158+
159+
def render_default(p: float, unit: str = '') -> str:
160+
"""
161+
RenderDefault - render a float as a string with 2 decimal places, or in scientific notation if it is greater than
162+
10,000 with the unit appended to it if it is not an empty string (the default)
163+
:param p: the float to render
164+
:type p: float
165+
:param unit: the unit to append to the string
166+
:type unit: str
167+
:return: the string representation of the float
168+
:rtype: str
169+
"""
170+
unit = UpgradeSymbologyOfUnits(unit)
171+
# if the number is greater than 10,000, render it in scientific notation
172+
if p > 10_000:
173+
return f'{p:10.2e} {unit}'.strip()
174+
# otherwise, render it with 2 decimal places
175+
else:
176+
return f'{p:10.2f} {unit}'.strip()
177+
178+
179+
def render_scientific(p: float, unit: str = '') -> str:
180+
"""
181+
RenderScientific - render a float as a string in scientific notation with 2 decimal places
182+
and the unit appended to it if it is not an empty string (the default)
183+
:param p: the float to render
184+
:type p: float
185+
:param unit: the unit to append to the string
186+
:type unit: str
187+
:return: the string representation of the float
188+
:rtype: str
189+
"""
190+
unit = UpgradeSymbologyOfUnits(unit)
191+
return f'{p:10.2e} {unit}'.strip()
192+
193+
194+
def render_Parameter_default(p: Parameter) -> str:
195+
"""
196+
RenderDefault - render a float as a string with 2 decimal places, or in scientific notation if it is greater than
197+
10,000 with the unit appended to it if it is not an empty string (the default) by calling the render_default base
198+
function
199+
:param p: the parameter to render
200+
:type p: float
201+
:return: the string representation of the float
202+
"""
203+
return render_default(p.value, p.CurrentUnits.value)
204+
205+
206+
def render_parameter_scientific(p: Parameter) -> str:
207+
"""
208+
RenderScientific - render a float as a string in scientific notation with 2 decimal places
209+
and the unit appended to it if it is not an empty string (the default) by calling the render_scientific base function
210+
:param p: the parameter to render
211+
:type p: float
212+
:return: the string representation of the float
213+
"""
214+
return render_scientific(p.value, p.CurrentUnits.value)
215+
216+
36217
def check_and_replace_mean(input_value, args) -> list:
37218
"""
38219
CheckAndReplaceMean - check to see if the user has requested that a value be replaced by a mean value by specifying
@@ -268,7 +449,9 @@ def main(command_line_args=None):
268449
if 'MC_OUTPUT_FILE' in args and args.MC_OUTPUT_FILE is not None
269450
else str(Path(Path(args.Input_file).parent, 'MC_Result.txt').absolute())
270451
)
452+
args.MC_OUTPUT_FILE = output_file
271453
python_path = 'python'
454+
html_path = ''
272455

273456
for line in flist:
274457
clean = line.strip()
@@ -284,6 +467,8 @@ def main(command_line_args=None):
284467
output_file = pair[1]
285468
elif pair[0].startswith('PYTHON_PATH'):
286469
python_path = pair[1]
470+
elif pair[0].startswith('HTML_PATH'):
471+
html_path = pair[1]
287472

288473
# check to see if there is a "#" in an input, if so, use the results file to replace it with the value
289474
for input_value in inputs:
@@ -375,6 +560,8 @@ def main(command_line_args=None):
375560
# write them out
376561
annotations = ''
377562
outputs_result: dict[str, dict] = {}
563+
full_names: set = set()
564+
short_names: set = set()
378565
with open(output_file, 'a') as f:
379566
if iterations != actual_records_count:
380567
f.write(
@@ -408,10 +595,17 @@ def main(command_line_args=None):
408595
f.write(f'bin values (as percentage): {ret[0]!s}\n')
409596
f.write(f'bin edges: {ret[1]!s}\n')
410597
fname = df.columns[i].strip().replace('/', '-')
411-
plt.savefig(Path(Path(output_file).parent, f'{fname}.png'))
412-
598+
save_path = Path(Path(output_file).parent, f'{fname}.png')
599+
if html_path:
600+
save_path = Path(Path(html_path).parent, f'{fname}.png')
601+
plt.savefig(save_path)
602+
full_names.add(save_path)
603+
short_names.add(fname)
413604
annotations = ''
414605

606+
if html_path:
607+
Write_HTML_Output(html_path, df, outputs, mins, maxs, medians, averages, means, std, full_names, short_names)
608+
415609
with open(Path(output_file).with_suffix('.json'), 'w') as json_output_file:
416610
json_output_file.write(json.dumps(outputs_result))
417611
logger.info(f'Wrote JSON results to {json_output_file.name}')

src/hip_ra/HIP_RA.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from geophires_x.Parameter import ConvertUnitsBack
2727
from geophires_x.Parameter import LookupUnits
2828
from geophires_x.Parameter import OutputParameter
29+
from geophires_x.Parameter import Parameter
2930
from geophires_x.Parameter import ReadParameter
3031
from geophires_x.Parameter import floatParameter
3132
from geophires_x.Parameter import intParameter
@@ -742,10 +743,10 @@ def PrintOutputs(self):
742743
nl = '\n'
743744
outputfile = 'HIP.out' if len(sys.argv) <= 2 else sys.argv[2]
744745

745-
def render_default(p: floatParameter | OutputParameter) -> str:
746+
def render_default(p: Parameter) -> str:
746747
return f'{p.value:10.2f} {p.CurrentUnits.value}'
747748

748-
def render_scientific(p: floatParameter | OutputParameter) -> str:
749+
def render_scientific(p: Parameter) -> str:
749750
return f'{p.value:10.2e} {p.CurrentUnits.value}'
750751

751752
summary_of_results = {}

src/hip_ra_x/hip_ra_x.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -811,10 +811,10 @@ def PrintOutputs(self):
811811
try:
812812
outputfile = 'HIP.out' if len(sys.argv) <= 2 else sys.argv[2]
813813

814-
def render_default(p: floatParameter | OutputParameter) -> str:
814+
def render_default(p: Parameter) -> str:
815815
return f'{p.value:10.2f} {p.CurrentUnits.value}'
816816

817-
def render_scientific(p: floatParameter | OutputParameter) -> str:
817+
def render_scientific(p: Parameter) -> str:
818818
return f'{p.value:10.2e} {p.CurrentUnits.value}'
819819

820820
summary_of_inputs = {}

0 commit comments

Comments
 (0)