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 Parameter
27
30
from geophires_x_client import GeophiresInputParameters
28
31
from geophires_x_client import GeophiresXClient
29
32
from geophires_x_client import GeophiresXResult
33
36
from hip_ra_x import HipRaXClient
34
37
35
38
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
+
36
217
def check_and_replace_mean (input_value , args ) -> list :
37
218
"""
38
219
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):
268
449
if 'MC_OUTPUT_FILE' in args and args .MC_OUTPUT_FILE is not None
269
450
else str (Path (Path (args .Input_file ).parent , 'MC_Result.txt' ).absolute ())
270
451
)
452
+ args .MC_OUTPUT_FILE = output_file
271
453
python_path = 'python'
454
+ html_path = ''
272
455
273
456
for line in flist :
274
457
clean = line .strip ()
@@ -284,6 +467,8 @@ def main(command_line_args=None):
284
467
output_file = pair [1 ]
285
468
elif pair [0 ].startswith ('PYTHON_PATH' ):
286
469
python_path = pair [1 ]
470
+ elif pair [0 ].startswith ('HTML_PATH' ):
471
+ html_path = pair [1 ]
287
472
288
473
# check to see if there is a "#" in an input, if so, use the results file to replace it with the value
289
474
for input_value in inputs :
@@ -375,6 +560,8 @@ def main(command_line_args=None):
375
560
# write them out
376
561
annotations = ''
377
562
outputs_result : dict [str , dict ] = {}
563
+ full_names : set = set ()
564
+ short_names : set = set ()
378
565
with open (output_file , 'a' ) as f :
379
566
if iterations != actual_records_count :
380
567
f .write (
@@ -408,10 +595,17 @@ def main(command_line_args=None):
408
595
f .write (f'bin values (as percentage): { ret [0 ]!s} \n ' )
409
596
f .write (f'bin edges: { ret [1 ]!s} \n ' )
410
597
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 )
413
604
annotations = ''
414
605
606
+ if html_path :
607
+ Write_HTML_Output (html_path , df , outputs , mins , maxs , medians , averages , means , std , full_names , short_names )
608
+
415
609
with open (Path (output_file ).with_suffix ('.json' ), 'w' ) as json_output_file :
416
610
json_output_file .write (json .dumps (outputs_result ))
417
611
logger .info (f'Wrote JSON results to { json_output_file .name } ' )
0 commit comments