Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions example_cycles/tests/test_all_examples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""
This script is designed to test run scripts located in a specified directory.
It uses Python's unittest framework to dynamically generate test cases for each
script that ends with '.py'.
"""

import os
import subprocess
import unittest
from pathlib import Path

from openmdao.utils.testing_utils import use_tempdirs
from parameterized import parameterized

# Address any issue that requires a skip.
SKIP_EXAMPLES = {
"high_bypass_turbofan.py" : "Runtime is more than 25 min." ,
"tab_thermo_data_generator.py" : "Runtime is more than 25 min." ,
}


def find_examples():
"""
Find and return a list of run scripts in the specified directory.

Returns
-------
list
A list of pathlib.Path objects pointing to the run scripts.
"""
base_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), '.')

run_files = []
for root, _, files in os.walk(base_dir):
for file in files:
if file.endswith('.py'):
run_files.append(Path(root) / file)

# Don't recurse into tests directory.
break

return run_files


def example_name(testcase_func, param_num, param):
"""
Returns a formatted case name for unit testing with decorator @parameterized.expand().
It is intended to be used when expand() is called with a list of strings
representing test case names.

Parameters
----------
testcase_func : Any
This parameter is ignored.
param_num : Any
This parameter is ignored.
param : param
The param object containing the case name to be formatted.
"""
return 'benchmark_example_' + param.args[0].name.replace('.py', '')


@use_tempdirs
class RunScriptTest(unittest.TestCase):
"""
A test case class that uses unittest to run and test scripts with a timeout.

Attributes
----------
base_directory : str
The base directory where the run scripts are located.
run_files : list
A list of paths to run scripts found in the base directory.

Methods
-------
setUpClass()
Class method to find all run scripts before tests are run.
find_run_files(base_dir)
Finds and returns all run scripts in the specified directory.
run_script(script_path)
Attempts to run a script with a timeout and handles errors.
test_run_scripts()
Generates a test for each run script with a timeout.
"""

def run_script(self, script_path, max_allowable_time=1500):
"""
Attempt to run a script with a 1500-second timeout and handle errors.

Parameters
----------
script_path : pathlib.Path
The path to the script to be run.

Raises
------
Exception
Any exception other than ImportError or TimeoutExpired that occurs while running the script.
"""
with open(os.devnull, 'w') as devnull:
proc = subprocess.Popen(['python', script_path], stdout=devnull, stderr=subprocess.PIPE)
proc.wait(timeout=max_allowable_time)
(stdout, stderr) = proc.communicate()

if proc.returncode != 0:
if 'ImportError' in str(stderr):
self.skipTest(f'Skipped {script_path.name} due to ImportError')
else:
raise Exception(f'Error running {script_path.name}:\n{stderr.decode("utf-8")}')

@parameterized.expand(find_examples(), name_func=example_name)
def benchmark_run_scripts(self, example_path):
"""Test each run script to ensure it executes without error."""
if example_path.name in SKIP_EXAMPLES:
reason = SKIP_EXAMPLES[example_path.name]
self.skipTest(f'Skipped {example_path.name}: {reason}.')

self.run_script(example_path)


if __name__ == '__main__':
unittest.main()
6 changes: 3 additions & 3 deletions pycycle/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pycycle.constants import (AIR_FUEL_MIX, AIR_MIX, WET_AIR_MIX, BTU_s2HP, HP_per_RPM_to_FT_LBF,
R_UNIVERSAL_SI, R_UNIVERSAL_ENG, g_c, MIN_VALID_CONCENTRATION,
T_STDeng, P_STDeng, P_REF, CEA_AIR_COMPOSITION, CEA_AIR_FUEL_COMPOSITION,
from pycycle.constants import (AIR_FUEL_MIX, AIR_MIX, WET_AIR_MIX, BTU_s2HP, HP_per_RPM_to_FT_LBF,
R_UNIVERSAL_SI, R_UNIVERSAL_ENG, g_c, MIN_VALID_CONCENTRATION,
T_STDeng, P_STDeng, P_REF, CEA_AIR_COMPOSITION, CEA_AIR_FUEL_COMPOSITION,
CEA_WET_AIR_COMPOSITION, AIR_JETA_TAB_SPEC, TAB_AIR_FUEL_COMPOSITION)

from pycycle.thermo.cea import species_data
Expand Down
100 changes: 77 additions & 23 deletions pycycle/viewers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,42 @@
import numpy as np

# protection incase env doesn't have matplotlib installed, since its not strictly required
try:
try:
import matplotlib
import matplotlib.pyplot as plt
except ImportError:
except ImportError:
plt = None


def get_val(prob, point, element, var_name, units=None):
"""
Get the value of the requested element from the OpenMDAO model.

Parameters
----------
prob: <Problem>
OpenMDAO problem that contains the pycycle model.
point: str
OpenMDAO pathname of the cycle point.
element: str
Name of the pcycle element system.
var_name: str
Name of the variable to get.
units: str or None
Units for the return value. Default is None, which will return using the delared units.

Returns
float
Value of requested element.
"""
try:
val = prob.get_val(f"{point}.{element}.{var_name}", units=units)
except KeyError:
# This is a cycle parameter.
val = prob.get_val(f"{element}.{var_name}", units=units)

return val[0]

def print_flow_station(prob, fs_names, file=sys.stdout):
names = ['tot:P', 'tot:T', 'tot:h', 'tot:S', 'stat:P', 'stat:W', 'stat:MN', 'stat:V', 'stat:area']

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

# line_tmpl = '{:<20}| '+'{:13.3f}'*4
line_tmpl = '{:<20}| {:13.4f}{:13.2f}{:13.4f}{:13.5f}'

for e_name in element_names:
W_fuel = prob[e_name+'.Wfuel'][0]
W_tot = prob[e_name+'.Fl_O:stat:W'][0]

point, _, element = e_name.rpartition(".")

W_fuel = get_val(prob, point, element, 'Wfuel')
W_tot = get_val(prob, point, element, 'Fl_O:stat:W')
W_air = W_tot - W_fuel
FAR = W_fuel/W_air
print(line_tmpl.format(e_name, prob[e_name+'.dPqP'][0],
prob[e_name+'.Fl_O:tot:T'][0],
W_fuel, FAR),
dPqP = get_val(prob, point, element, 'dPqP')
T_tot = get_val(prob, point, element, 'Fl_O:tot:T')

print(line_tmpl.format(e_name, dPqP, T_tot, W_fuel, FAR),
file=file, flush=True)


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

line_tmpl = '{:<14}| '+'{:13.3f}'*9
for e_name in element_names:

point, _, element = e_name.rpartition(".")

sys = prob.model._get_subsystem(e_name)
if sys.options['design']:
PR_temp = prob[e_name+'.map.scalars.PR'][0]
eff_temp = prob[e_name+'.map.scalars.eff'][0]
PR_temp = get_val(prob, point, element, 'map.scalars.PR')
eff_temp = get_val(prob, point, element, 'map.scalars.eff')
else:
PR_temp = prob[e_name+'.PR'][0]
eff_temp = prob[e_name+'.eff'][0]

print(line_tmpl.format(e_name, prob[e_name+'.Wp'][0], PR_temp,
eff_temp, prob[e_name+'.eff_poly'][0], prob[e_name+'.Np'][0], prob[e_name+'.power'][0],
prob[e_name+'.map.NpMap'][0], prob[e_name+'.map.PRmap'][0], prob[e_name+'.map.alphaMap'][0]),
PR_temp = get_val(prob, point, element, 'PR')
eff_temp = get_val(prob, point, element, 'eff')

Wp = get_val(prob, point, element, 'Wp')
eff_poly = get_val(prob, point, element, 'eff_poly')
Np = get_val(prob, point, element, 'Np')
power = get_val(prob, point, element, 'power')
NpMap = get_val(prob, point, element, 'map.NpMap')
PRmap = get_val(prob, point, element, 'map.PRmap')
alphaMap = get_val(prob, point, element, 'map.alphaMap')

print(line_tmpl.format(e_name, Wp, PR_temp,
eff_temp, eff_poly, Np, power,
NpMap, PRmap, alphaMap),
file=file, flush=True)


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


for e_name in element_names:

point, _, element = e_name.rpartition(".")

sys = prob.model._get_subsystem(e_name)
if sys.options['lossCoef'] == 'Cv':
Cv_val = prob[e_name+'.Cv'][0]

Cv_val = get_val(prob, point, element, 'Cv')
Cfg_val = ' N/A '
line_tmpl = '{:<14}| ' + '{:13.3f}'*2 + '{}' + '{:13.3f}'*5

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

print(line_tmpl.format(e_name, prob[e_name+'.PR'][0], Cv_val, Cfg_val,
prob[e_name+'.Throat:stat:area'][0], prob[e_name+'.Throat:stat:MN'][0],
prob[e_name+'.Fl_O:stat:MN'][0],
prob[e_name+'.Fl_O:stat:V'][0], prob[e_name+'.Fg'][0]),
PR = get_val(prob, point, element, 'PR')
area = get_val(prob, point, element, 'Throat:stat:area')
throat_MN = get_val(prob, point, element, 'Throat:stat:MN')
MN = get_val(prob, point, element, 'Fl_O:stat:MN')
V = get_val(prob, point, element, 'Fl_O:stat:V')
Fg = get_val(prob, point, element, 'Fg')

print(line_tmpl.format(e_name, PR, Cv_val, Cfg_val,
area, throat_MN, MN, V, Fg),
file=file, flush=True)


Expand Down Expand Up @@ -287,7 +341,7 @@ def plot_compressor_maps(prob, element_names, eff_vals=np.array([0,0.5,0.55,0.6,
plt.ylabel('PR')
plt.title(e_name)
# plt.show()
plt.savefig(e_name+'.pdf')
plt.savefig(e_name+'.pdf')


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]):
Expand Down Expand Up @@ -321,5 +375,5 @@ def plot_turbine_maps(prob, element_names, eff_vals=np.array([0,0.5,0.55,0.6,0.6
plt.ylabel('PR')
plt.title(e_name)
# plt.show()
plt.savefig(e_name+'.pdf')
plt.savefig(e_name+'.pdf')

Loading