Skip to content

Commit a4149a0

Browse files
Merge remote-tracking branch 'upstream/master'
2 parents 8bfd67f + 65faca6 commit a4149a0

File tree

4 files changed

+204
-26
lines changed

4 files changed

+204
-26
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""
2+
This script is designed to test run scripts located in a specified directory.
3+
It uses Python's unittest framework to dynamically generate test cases for each
4+
script that ends with '.py'.
5+
"""
6+
7+
import os
8+
import subprocess
9+
import unittest
10+
from pathlib import Path
11+
12+
from openmdao.utils.testing_utils import use_tempdirs
13+
from parameterized import parameterized
14+
15+
# Address any issue that requires a skip.
16+
SKIP_EXAMPLES = {
17+
"high_bypass_turbofan.py" : "Runtime is more than 25 min." ,
18+
"tab_thermo_data_generator.py" : "Runtime is more than 25 min." ,
19+
}
20+
21+
22+
def find_examples():
23+
"""
24+
Find and return a list of run scripts in the specified directory.
25+
26+
Returns
27+
-------
28+
list
29+
A list of pathlib.Path objects pointing to the run scripts.
30+
"""
31+
base_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), '.')
32+
33+
run_files = []
34+
for root, _, files in os.walk(base_dir):
35+
for file in files:
36+
if file.endswith('.py'):
37+
run_files.append(Path(root) / file)
38+
39+
# Don't recurse into tests directory.
40+
break
41+
42+
return run_files
43+
44+
45+
def example_name(testcase_func, param_num, param):
46+
"""
47+
Returns a formatted case name for unit testing with decorator @parameterized.expand().
48+
It is intended to be used when expand() is called with a list of strings
49+
representing test case names.
50+
51+
Parameters
52+
----------
53+
testcase_func : Any
54+
This parameter is ignored.
55+
param_num : Any
56+
This parameter is ignored.
57+
param : param
58+
The param object containing the case name to be formatted.
59+
"""
60+
return 'benchmark_example_' + param.args[0].name.replace('.py', '')
61+
62+
63+
@use_tempdirs
64+
class RunScriptTest(unittest.TestCase):
65+
"""
66+
A test case class that uses unittest to run and test scripts with a timeout.
67+
68+
Attributes
69+
----------
70+
base_directory : str
71+
The base directory where the run scripts are located.
72+
run_files : list
73+
A list of paths to run scripts found in the base directory.
74+
75+
Methods
76+
-------
77+
setUpClass()
78+
Class method to find all run scripts before tests are run.
79+
find_run_files(base_dir)
80+
Finds and returns all run scripts in the specified directory.
81+
run_script(script_path)
82+
Attempts to run a script with a timeout and handles errors.
83+
test_run_scripts()
84+
Generates a test for each run script with a timeout.
85+
"""
86+
87+
def run_script(self, script_path, max_allowable_time=1500):
88+
"""
89+
Attempt to run a script with a 1500-second timeout and handle errors.
90+
91+
Parameters
92+
----------
93+
script_path : pathlib.Path
94+
The path to the script to be run.
95+
96+
Raises
97+
------
98+
Exception
99+
Any exception other than ImportError or TimeoutExpired that occurs while running the script.
100+
"""
101+
with open(os.devnull, 'w') as devnull:
102+
proc = subprocess.Popen(['python', script_path], stdout=devnull, stderr=subprocess.PIPE)
103+
proc.wait(timeout=max_allowable_time)
104+
(stdout, stderr) = proc.communicate()
105+
106+
if proc.returncode != 0:
107+
if 'ImportError' in str(stderr):
108+
self.skipTest(f'Skipped {script_path.name} due to ImportError')
109+
else:
110+
raise Exception(f'Error running {script_path.name}:\n{stderr.decode("utf-8")}')
111+
112+
@parameterized.expand(find_examples(), name_func=example_name)
113+
def benchmark_run_scripts(self, example_path):
114+
"""Test each run script to ensure it executes without error."""
115+
if example_path.name in SKIP_EXAMPLES:
116+
reason = SKIP_EXAMPLES[example_path.name]
117+
self.skipTest(f'Skipped {example_path.name}: {reason}.')
118+
119+
self.run_script(example_path)
120+
121+
122+
if __name__ == '__main__':
123+
unittest.main()

pycycle/api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from pycycle.constants import (AIR_FUEL_MIX, AIR_MIX, WET_AIR_MIX, BTU_s2HP, HP_per_RPM_to_FT_LBF,
2-
R_UNIVERSAL_SI, R_UNIVERSAL_ENG, g_c, MIN_VALID_CONCENTRATION,
3-
T_STDeng, P_STDeng, P_REF, CEA_AIR_COMPOSITION, CEA_AIR_FUEL_COMPOSITION,
1+
from pycycle.constants import (AIR_FUEL_MIX, AIR_MIX, WET_AIR_MIX, BTU_s2HP, HP_per_RPM_to_FT_LBF,
2+
R_UNIVERSAL_SI, R_UNIVERSAL_ENG, g_c, MIN_VALID_CONCENTRATION,
3+
T_STDeng, P_STDeng, P_REF, CEA_AIR_COMPOSITION, CEA_AIR_FUEL_COMPOSITION,
44
CEA_WET_AIR_COMPOSITION, AIR_JETA_TAB_SPEC, TAB_AIR_FUEL_COMPOSITION)
55

66
from pycycle.thermo.cea import species_data

pycycle/viewers.py

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,42 @@
33
import numpy as np
44

55
# protection incase env doesn't have matplotlib installed, since its not strictly required
6-
try:
6+
try:
77
import matplotlib
88
import matplotlib.pyplot as plt
9-
except ImportError:
9+
except ImportError:
1010
plt = None
1111

1212

13+
def get_val(prob, point, element, var_name, units=None):
14+
"""
15+
Get the value of the requested element from the OpenMDAO model.
16+
17+
Parameters
18+
----------
19+
prob: <Problem>
20+
OpenMDAO problem that contains the pycycle model.
21+
point: str
22+
OpenMDAO pathname of the cycle point.
23+
element: str
24+
Name of the pcycle element system.
25+
var_name: str
26+
Name of the variable to get.
27+
units: str or None
28+
Units for the return value. Default is None, which will return using the delared units.
29+
30+
Returns
31+
float
32+
Value of requested element.
33+
"""
34+
try:
35+
val = prob.get_val(f"{point}.{element}.{var_name}", units=units)
36+
except KeyError:
37+
# This is a cycle parameter.
38+
val = prob.get_val(f"{element}.{var_name}", units=units)
39+
40+
return val[0]
41+
1342
def print_flow_station(prob, fs_names, file=sys.stdout):
1443
names = ['tot:P', 'tot:T', 'tot:h', 'tot:S', 'stat:P', 'stat:W', 'stat:MN', 'stat:V', 'stat:area']
1544

@@ -83,14 +112,19 @@ def print_burner(prob, element_names, file=sys.stdout):
83112

84113
# line_tmpl = '{:<20}| '+'{:13.3f}'*4
85114
line_tmpl = '{:<20}| {:13.4f}{:13.2f}{:13.4f}{:13.5f}'
115+
86116
for e_name in element_names:
87-
W_fuel = prob[e_name+'.Wfuel'][0]
88-
W_tot = prob[e_name+'.Fl_O:stat:W'][0]
117+
118+
point, _, element = e_name.rpartition(".")
119+
120+
W_fuel = get_val(prob, point, element, 'Wfuel')
121+
W_tot = get_val(prob, point, element, 'Fl_O:stat:W')
89122
W_air = W_tot - W_fuel
90123
FAR = W_fuel/W_air
91-
print(line_tmpl.format(e_name, prob[e_name+'.dPqP'][0],
92-
prob[e_name+'.Fl_O:tot:T'][0],
93-
W_fuel, FAR),
124+
dPqP = get_val(prob, point, element, 'dPqP')
125+
T_tot = get_val(prob, point, element, 'Fl_O:tot:T')
126+
127+
print(line_tmpl.format(e_name, dPqP, T_tot, W_fuel, FAR),
94128
file=file, flush=True)
95129

96130

@@ -108,17 +142,28 @@ def print_turbine(prob, element_names, file=sys.stdout):
108142

109143
line_tmpl = '{:<14}| '+'{:13.3f}'*9
110144
for e_name in element_names:
145+
146+
point, _, element = e_name.rpartition(".")
147+
111148
sys = prob.model._get_subsystem(e_name)
112149
if sys.options['design']:
113-
PR_temp = prob[e_name+'.map.scalars.PR'][0]
114-
eff_temp = prob[e_name+'.map.scalars.eff'][0]
150+
PR_temp = get_val(prob, point, element, 'map.scalars.PR')
151+
eff_temp = get_val(prob, point, element, 'map.scalars.eff')
115152
else:
116-
PR_temp = prob[e_name+'.PR'][0]
117-
eff_temp = prob[e_name+'.eff'][0]
118-
119-
print(line_tmpl.format(e_name, prob[e_name+'.Wp'][0], PR_temp,
120-
eff_temp, prob[e_name+'.eff_poly'][0], prob[e_name+'.Np'][0], prob[e_name+'.power'][0],
121-
prob[e_name+'.map.NpMap'][0], prob[e_name+'.map.PRmap'][0], prob[e_name+'.map.alphaMap'][0]),
153+
PR_temp = get_val(prob, point, element, 'PR')
154+
eff_temp = get_val(prob, point, element, 'eff')
155+
156+
Wp = get_val(prob, point, element, 'Wp')
157+
eff_poly = get_val(prob, point, element, 'eff_poly')
158+
Np = get_val(prob, point, element, 'Np')
159+
power = get_val(prob, point, element, 'power')
160+
NpMap = get_val(prob, point, element, 'map.NpMap')
161+
PRmap = get_val(prob, point, element, 'map.PRmap')
162+
alphaMap = get_val(prob, point, element, 'map.alphaMap')
163+
164+
print(line_tmpl.format(e_name, Wp, PR_temp,
165+
eff_temp, eff_poly, Np, power,
166+
NpMap, PRmap, alphaMap),
122167
file=file, flush=True)
123168

124169

@@ -134,21 +179,30 @@ def print_nozzle(prob, element_names, file=sys.stdout):
134179

135180

136181
for e_name in element_names:
182+
183+
point, _, element = e_name.rpartition(".")
184+
137185
sys = prob.model._get_subsystem(e_name)
138186
if sys.options['lossCoef'] == 'Cv':
139-
Cv_val = prob[e_name+'.Cv'][0]
187+
188+
Cv_val = get_val(prob, point, element, 'Cv')
140189
Cfg_val = ' N/A '
141190
line_tmpl = '{:<14}| ' + '{:13.3f}'*2 + '{}' + '{:13.3f}'*5
142191

143192
else:
144193
Cv_val = ' N/A '
145-
Cfg_val = prob[e_name+'.Cfg'][0]
194+
Cfg_val = get_val(prob, point, element, 'Cfg')
146195
line_tmpl = '{:<14}| ' + '{:13.3f}'*1 + '{}' + '{:13.3f}'*6
147196

148-
print(line_tmpl.format(e_name, prob[e_name+'.PR'][0], Cv_val, Cfg_val,
149-
prob[e_name+'.Throat:stat:area'][0], prob[e_name+'.Throat:stat:MN'][0],
150-
prob[e_name+'.Fl_O:stat:MN'][0],
151-
prob[e_name+'.Fl_O:stat:V'][0], prob[e_name+'.Fg'][0]),
197+
PR = get_val(prob, point, element, 'PR')
198+
area = get_val(prob, point, element, 'Throat:stat:area')
199+
throat_MN = get_val(prob, point, element, 'Throat:stat:MN')
200+
MN = get_val(prob, point, element, 'Fl_O:stat:MN')
201+
V = get_val(prob, point, element, 'Fl_O:stat:V')
202+
Fg = get_val(prob, point, element, 'Fg')
203+
204+
print(line_tmpl.format(e_name, PR, Cv_val, Cfg_val,
205+
area, throat_MN, MN, V, Fg),
152206
file=file, flush=True)
153207

154208

@@ -287,7 +341,7 @@ def plot_compressor_maps(prob, element_names, eff_vals=np.array([0,0.5,0.55,0.6,
287341
plt.ylabel('PR')
288342
plt.title(e_name)
289343
# plt.show()
290-
plt.savefig(e_name+'.pdf')
344+
plt.savefig(e_name+'.pdf')
291345

292346

293347
def plot_turbine_maps(prob, element_names, eff_vals=np.array([0,0.5,0.55,0.6,0.65,0.7,0.75,0.8,0.85,0.9,0.95,1.0]),alphas=[0]):
@@ -321,5 +375,5 @@ def plot_turbine_maps(prob, element_names, eff_vals=np.array([0,0.5,0.55,0.6,0.6
321375
plt.ylabel('PR')
322376
plt.title(e_name)
323377
# plt.show()
324-
plt.savefig(e_name+'.pdf')
378+
plt.savefig(e_name+'.pdf')
325379

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
optional_dependencies = {
1313
'test': [
1414
'testflo>=1.3.6',
15+
'parameterized',
1516
]
1617
}
1718

0 commit comments

Comments
 (0)