22
22
import matplotlib .pyplot as plt
23
23
import numpy as np
24
24
import pandas as pd
25
+ from rich .console import Console
26
+ from rich .table import Table
25
27
26
28
from geophires_monte_carlo .common import _get_logger
29
+ from geophires_x .Parameter import OutputParameter
30
+ from geophires_x .Parameter import floatParameter
27
31
from geophires_x_client import GeophiresInputParameters
28
32
from geophires_x_client import GeophiresXClient
29
33
from geophires_x_client import GeophiresXResult
33
37
from hip_ra_x import HipRaXClient
34
38
35
39
40
+ def Write_HTML_Output (
41
+ html_path : str ,
42
+ df : pd .DataFrame ,
43
+ outputs : list ,
44
+ mins : list ,
45
+ maxs : list ,
46
+ medians : list ,
47
+ averages : list ,
48
+ means : list ,
49
+ std : list ,
50
+ full_names : set ,
51
+ short_names : set ,
52
+ ) -> None :
53
+ """
54
+ Write_HTML_Output - write the results of the Monte Carlo simulation to an HTML file
55
+ :param html_path: the path to the HTML file to write
56
+ :type html_path: str
57
+ :param df: the DataFrame with the results
58
+ :type df: pd.DataFrame
59
+ :param outputs: the list of output variable names
60
+ :type outputs: list
61
+ :param mins: the list of minimum values for each output variable
62
+ :type mins: list
63
+ :param maxs: the list of maximum values for each output variable
64
+ :type maxs: list
65
+ :param medians: the list of median values for each output variable
66
+ :type medians: list
67
+ :param averages: the list of average values for each output variable
68
+ :type averages: list
69
+ :param means: the list of mean values for each output variable
70
+ :type means: list
71
+ :param std: the list of standard deviation values for each output variable
72
+ :type std: list
73
+ :param full_names: the list of full names for each output variable
74
+ :type full_names: set
75
+ :param short_names: the list of short names for each output variable
76
+ :type short_names: set
77
+ """
78
+
79
+ # Build the tables that will hold those results, along with the columns for the input variables
80
+ results_table = Table (title = 'GEOPHIRES/HIR-RA Monte Carlo Results' )
81
+ results_table .add_column ('Iteration #' , no_wrap = True , justify = 'center' )
82
+ for output in df .axes [1 ]:
83
+ results_table .add_column (output .replace (',' , '' ), no_wrap = True , justify = 'center' )
84
+
85
+ statistics_table = Table (title = 'GEOPHIRES/HIR-RA Monte Carlo Statistics' )
86
+ statistics_table .add_column ('Output Parameter Name' , no_wrap = True , justify = 'center' )
87
+ statistics_table .add_column ('minimum' , no_wrap = True , justify = 'center' )
88
+ statistics_table .add_column ('maximum' , no_wrap = True , justify = 'center' )
89
+ statistics_table .add_column ('median' , no_wrap = True , justify = 'center' )
90
+ statistics_table .add_column ('average' , no_wrap = True , justify = 'center' )
91
+ statistics_table .add_column ('mean' , no_wrap = True , justify = 'center' )
92
+ statistics_table .add_column ('standard deviation' , no_wrap = True , justify = 'center' )
93
+
94
+ # Iterate over the rows of the DataFrame and add them to the results table
95
+ for index , row in df .iterrows ():
96
+ data = row .values [0 : len (outputs )]
97
+
98
+ # have to deal with the special case where thr last column is actually
99
+ # a compound string with multiple columns in it that looks like this:
100
+ # ' (Gradient 1:47.219846973456924;Reservoir Temperature:264.7789623351493;...)'
101
+ str_to_parse = str (row .values [len (outputs )]).strip ().replace ('(' , '' ).replace (')' , '' )
102
+ fields = str_to_parse .split (';' )
103
+ for field in fields :
104
+ if len (field ) > 0 :
105
+ key , value = field .split (':' )
106
+ data = np .append (data , float (value ))
107
+
108
+ results_table .add_row (str (int (index )), * [render_default (d ) for d in data ])
109
+
110
+ for i in range (len (outputs )):
111
+ statistics_table .add_row (
112
+ outputs [i ],
113
+ render_default (mins [i ]),
114
+ render_default (maxs [i ]),
115
+ render_default (medians [i ]),
116
+ render_default (averages [i ]),
117
+ render_default (means [i ]),
118
+ render_default (std [i ]),
119
+ )
120
+
121
+ console = Console (style = 'bold white on black' , force_terminal = True , record = True , width = 500 )
122
+ console .print (results_table )
123
+ console .print (' ' )
124
+ console .print (statistics_table )
125
+ console .save_html (html_path )
126
+
127
+ # Write a reference to the image(s) into the HTML file by inserting before the "</body>" tag
128
+ # build the string to be inserted first
129
+ insert_string = ''
130
+ for _ in range (len (full_names )):
131
+ name_to_use = short_names .pop ()
132
+ insert_string = insert_string + f'<img src="{ name_to_use } .png" alt="{ name_to_use } ">\n '
133
+
134
+ match_string = '</body>'
135
+ with open (html_path , 'r+' , encoding = 'UTF-8' ) as html_file :
136
+ contents = html_file .readlines ()
137
+ if match_string in contents [- 1 ]: # Handle last line to prevent IndexError
138
+ pass
139
+ else :
140
+ for index , line in enumerate (contents ):
141
+ if match_string in line and insert_string not in contents [index + 1 ]:
142
+ contents .insert (index , insert_string )
143
+ break
144
+ html_file .seek (0 )
145
+ html_file .writelines (contents )
146
+
147
+
148
+ def UpgradeSymbologyOfUnits (unit : str ) -> str :
149
+ """
150
+ UpgradeSymbologyOfUnits is a function that takes a string that represents a unit and replaces the **2 and **3
151
+ with the appropriate unicode characters for superscript 2 and 3, and replaces "deg" with the unicode character
152
+ for degrees.
153
+ :param unit: a string that represents a unit
154
+ :return: a string that represents a unit with the appropriate unicode characters for superscript 2 and 3, and
155
+ replaces "deg" with the unicode character for degrees.
156
+ """
157
+ return unit .replace ('**2' , '\u00b2 ' ).replace ('**3' , '\u00b3 ' ).replace ('deg' , '\u00b0 ' )
158
+
159
+
160
+ def render_default (p : float , unit : str = '' ) -> str :
161
+ """
162
+ RenderDefault - render a float as a string with 2 decimal places, or in scientific notation if it is greater than
163
+ 10,000 with the unit appended to it if it is not an empty string (the default)
164
+ :param p: the float to render
165
+ :type p: float
166
+ :param unit: the unit to append to the string
167
+ :type unit: str
168
+ :return: the string representation of the float
169
+ :rtype: str
170
+ """
171
+ unit = UpgradeSymbologyOfUnits (unit )
172
+ # if the number is greater than 10,000, render it in scientific notation
173
+ if p > 10_000 :
174
+ return f'{ p :10.2e} { unit } ' .strip ()
175
+ # otherwise, render it with 2 decimal places
176
+ else :
177
+ return f'{ p :10.2f} { unit } ' .strip ()
178
+
179
+
180
+ def render_scientific (p : float , unit : str = '' ) -> str :
181
+ """
182
+ RenderScientific - render a float as a string in scientific notation with 2 decimal places
183
+ and the unit appended to it if it is not an empty string (the default)
184
+ :param p: the float to render
185
+ :type p: float
186
+ :param unit: the unit to append to the string
187
+ :type unit: str
188
+ :return: the string representation of the float
189
+ :rtype: str
190
+ """
191
+ unit = UpgradeSymbologyOfUnits (unit )
192
+ return f'{ p :10.2e} { unit } ' .strip ()
193
+
194
+
195
+ def render_Parameter_default (p : floatParameter | OutputParameter ) -> str :
196
+ """
197
+ RenderDefault - render a float as a string with 2 decimal places, or in scientific notation if it is greater than
198
+ 10,000 with the unit appended to it if it is not an empty string (the default) by calling the render_default base
199
+ function
200
+ :param p: the parameter to render
201
+ :type p: float
202
+ :return: the string representation of the float
203
+ """
204
+ return render_default (p .value , p .CurrentUnits .value )
205
+
206
+
207
+ def render_parameter_scientific (p : floatParameter | OutputParameter ) -> str :
208
+ """
209
+ RenderScientific - render a float as a string in scientific notation with 2 decimal places
210
+ and the unit appended to it if it is not an empty string (the default) by calling the render_scientific base function
211
+ :param p: the parameter to render
212
+ :type p: float
213
+ :return: the string representation of the float
214
+ """
215
+ return render_scientific (p .value , p .CurrentUnits .value )
216
+
217
+
36
218
def check_and_replace_mean (input_value , args ) -> list :
37
219
"""
38
220
CheckAndReplaceMean - check to see if the user has requested that a value be replaced by a mean value by specifying
@@ -268,7 +450,9 @@ def main(command_line_args=None):
268
450
if 'MC_OUTPUT_FILE' in args and args .MC_OUTPUT_FILE is not None
269
451
else str (Path (Path (args .Input_file ).parent , 'MC_Result.txt' ).absolute ())
270
452
)
453
+ args .MC_OUTPUT_FILE = output_file
271
454
python_path = 'python'
455
+ html_path = ''
272
456
273
457
for line in flist :
274
458
clean = line .strip ()
@@ -284,6 +468,8 @@ def main(command_line_args=None):
284
468
output_file = pair [1 ]
285
469
elif pair [0 ].startswith ('PYTHON_PATH' ):
286
470
python_path = pair [1 ]
471
+ elif pair [0 ].startswith ('HTML_PATH' ):
472
+ html_path = pair [1 ]
287
473
288
474
# check to see if there is a "#" in an input, if so, use the results file to replace it with the value
289
475
for input_value in inputs :
@@ -375,6 +561,8 @@ def main(command_line_args=None):
375
561
# write them out
376
562
annotations = ''
377
563
outputs_result : dict [str , dict ] = {}
564
+ full_names : set = set ()
565
+ short_names : set = set ()
378
566
with open (output_file , 'a' ) as f :
379
567
if iterations != actual_records_count :
380
568
f .write (
@@ -408,10 +596,17 @@ def main(command_line_args=None):
408
596
f .write (f'bin values (as percentage): { ret [0 ]!s} \n ' )
409
597
f .write (f'bin edges: { ret [1 ]!s} \n ' )
410
598
fname = df .columns [i ].strip ().replace ('/' , '-' )
411
- plt .savefig (Path (Path (output_file ).parent , f'{ fname } .png' ))
412
-
599
+ save_path = Path (Path (output_file ).parent , f'{ fname } .png' )
600
+ if html_path :
601
+ save_path = Path (Path (html_path ).parent , f'{ fname } .png' )
602
+ plt .savefig (save_path )
603
+ full_names .add (save_path )
604
+ short_names .add (fname )
413
605
annotations = ''
414
606
607
+ if html_path :
608
+ Write_HTML_Output (html_path , df , outputs , mins , maxs , medians , averages , means , std , full_names , short_names )
609
+
415
610
with open (Path (output_file ).with_suffix ('.json' ), 'w' ) as json_output_file :
416
611
json_output_file .write (json .dumps (outputs_result ))
417
612
logger .info (f'Wrote JSON results to { json_output_file .name } ' )
0 commit comments