Commit 78078ea4 authored by John Tourtellott's avatar John Tourtellott
Browse files

Merge branch 'export-test' into 'master'

Export test

See merge request cmb/plugins/ace3p-extensions!1
parents 45d58286 82783614
cmake_minimum_required(VERSION 3.12)
project(ace3p-extension VERSION 1.0)
project(ace3p-extensions VERSION 1.0)
option(BUILD_SHARED_LIBS "Build CMB using shared libraries" ON)
......@@ -19,12 +19,12 @@ if (ENABLE_TESTING)
include(TestingMacros)
endif()
# Nlohmann json
find_package(nlohmann_json REQUIRED)
# Find smtk
find_package(smtk REQUIRED)
# Nlohmann json
# find_package(nlohmann_json REQUIRED)
if(WIN32 AND MSVC)
#setup windows exception handling so we can compile properly with boost
#enabled
......@@ -101,3 +101,8 @@ else()
DESTINATION "${SIMULATION_WORKFLOWS_ROOT}/ACE3P"
)
endif ()
if (ENABLE_TESTING)
enable_testing()
add_subdirectory(testing)
endif()
......@@ -209,8 +209,10 @@ def ExportCMB(export_op):
raise Exception(msg)
print('Writing %s' % analysis)
# Since category string == analyis string...
scope.categories = [analysis]
# Get categories
smtk_analyses = scope.sim_atts.analyses()
smtk_analysis = smtk_analyses.find(analysis)
scope.categories = list(smtk_analysis.categories())
print('Using categories: %s' % scope.categories)
# Initialize output file
......@@ -378,7 +380,7 @@ def write_boundarycondition(scope):
# Check for sigma and frequency items
added_text = None
sigma_item = att.findDouble('Sigma')
if sigma_item is not None and sigma_item.isMemberOf(scope.categories):
if sigma_item is not None and utils.passes_categories(sigma_item, scope.categories):
sigma = sigma_item.value(0)
line1 = ' ReferenceNumber: %s\n' % ent_string
line2 = ' Sigma: %g\n' % sigma
......@@ -386,7 +388,7 @@ def write_boundarycondition(scope):
# Frequency only set when Sigma is set (T3P Impedance)
freq_item = att.findDouble('Frequency')
if freq_item is not None and freq_item.isMemberOf(scope.categories):
if freq_item is not None and utils.passes_categories(freq_item, scope.categories):
freq = freq_item.value(0)
line3 = ' Frequency: %d\n' % freq
added_text += line3
......@@ -455,7 +457,7 @@ def write_materials(scope):
for item_info in items_todo:
name, label = item_info
item = att.findDouble(name)
if item and item.isEnabled() and item.isMemberOf(scope.categories):
if item and item.isEnabled() and utils.passes_categories(item, scope.categories):
value = item.value(0)
scope.output.write(' %s: %g\n' % (label, value))
......@@ -726,7 +728,7 @@ def write_monitor(scope):
['Point', 'TimeStart', 'TimeEnd', 'TimeStep', 'StartContour', 'EndContour', 'Smax']
for item_type in item_type_list:
item = att.find(item_type)
if item is None or not item.isMemberOf(scope.categories):
if item is None or not utils.passes_categories(item, scope.categories):
continue
if not item.isEnabled():
continue
......@@ -853,7 +855,7 @@ def write_postprocess(scope):
att = att_list[0]
# Not all solvers use PostProcess
if not att.isMemberOf(scope.categories):
if not utils.passes_categories(att, scope.categories):
return
print('Write PostProcess')
......
......@@ -7,6 +7,7 @@ import os
from . import cardformat
reload(cardformat)
from .cardformat import CardFormat
from . import utils
import smtk
......@@ -124,7 +125,7 @@ class BaseWriter(object):
if attribute_item.name() in set(skip_list):
return False
if not attribute_item.isMemberOf(self.scope.categories):
if not utils.passes_categories(attribute_item, self.scope.categories):
return False
if not attribute_item.isEnabled():
return False
......
......@@ -38,7 +38,7 @@ class CardFormat:
'''
# Check categories
if not item.isMemberOf(scope.categories):
if not utils.passes_categories(item, scope.categories):
return False
# Write void type
......
......@@ -272,7 +272,7 @@ class Tem3PWriter(basewriter.BaseWriter):
for att in att_list:
# Filter by cateogry
if not att.isMemberOf(self.scope.categories):
if not utils.passes_categories(att, scope.categories):
continue
att_type = att.type()
......
......@@ -7,6 +7,24 @@ import smtk
import smtk.model
# ---------------------------------------------------------------------
def passes_categories(item, categories):
"""Backward- and list/set-compatible category check
The isMemberOf() method was deleted early Jan 2020.
There is also some uncertainty whether the API argument
should be a list or set.
"""
if hasattr(item, 'isMemberOf'):
if isinstance(categories, list):
return item.isMemberOf(categories)
# (else)
return item.isMemberOf(list(categories))
elif isinstance(categories, set):
return item.categories().passes(categories)
else:
return item.categories().passes(set(categories))
# ---------------------------------------------------------------------
def get_model_info(scope):
'''DEPRECATED Finds/updates model-related info on input scope object:
......
add_subdirectory(python)
#=============================================================================
#
# Copyright (c) Kitware, Inc.
# All rights reserved.
# See LICENSE.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even
# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the above copyright notice for more information.
#
#=============================================================================
import os
import re
RE_KEYWORD = r'^\s*(\w+)\s*(:)' # left-hand side for either section or assignment
class TokenType:
KEYWORD = 'keyword'
END_SECTION = 'end-section'
NULL = 'null'
class TokenInfo:
def __init__(self, token_type, line_number, keyword=None, rest_of_line=None):
self.type = token_type
self.line_number = line_number
self.keyword = keyword
self.rest_of_line = rest_of_line
def __str__(self):
s = 'Token type {} line {}'.format(self.type, self.line_number)
if self.type == TokenType.KEYWORD:
s = ' keyword {} res_of_line \"{}\"'.format(s, self.keyword, self.rest_of_line)
return s
class Section:
"""Stores contents of individual section"""
def __init__(self, name, first_line):
self.name = name
self.first_line = first_line
self.current_line = 0
self.last_line = None
self.children = list() # children sections
self.data = dict() # <keyword, value>
def __str__(self):
return 'Section {} lines {}-{}'.format(
self.name, self.first_line, self.last_line)
class ACE3PFileValidator:
""""""
def __init__(self, path):
""""""
self.current_pos = 0 # current line number
self.exists = False # file exists
self.lines = list() # file contents
self.sections = dict() # <keyword, set-of-sections>
self.number_of_lines = 0
self._parse_file(path)
def file_exists(self):
return self.exists
def get_number_of_lines(self):
return self.number_of_lines
def get_sections(self, name):
"""Returns set of sections with given name, or None"""
return self.sections.get(name)
def has_section(self, name, min_count=1, max_count=1):
""""""
dict_item = self.sections.get(name)
if dict_item is None:
return False
count = len(dict_item)
if min_count is not None and count < min_count:
return False
if max_count is not None and count > max_count:
return False
# (else)
return True
def section_has_bool(self, section, keyword, bool_value):
value = section.data.get(keyword)
if value is None:
print('{} section has no keyword {}'.format(section.name, keyword))
return False
match_value = '.true.' if bool_value else '.false'
return value == match_value
def section_has_double(self, section, keyword, double_value, tolerance=1e-6):
value = section.data.get(keyword)
if value is None:
print('{} section has no keyword {}'.format(section.name, keyword))
return False
dvalue = float(value) # raises ValueError if not valid
return abs(dvalue - double_value) < tolerance
def section_has_int(self, section, keyword, int_value):
value = section.data.get(keyword)
if value is None:
print('{} section has no keyword {}'.format(section.name, keyword))
return False
ivalue = int(value) # raises ValueError if not valid
return ivalue == int_value
def section_has_string(self, section, keyword, string_value):
value = section.data.get(keyword)
if value is None:
print('{} section has no keyword {}'.format(section.name, keyword))
return False
return value == string_value
def _get_next_line(self):
""""""
while self.lines:
raw_line = self.lines.pop()
self.current_pos += 1
line = raw_line.strip()
if len(line) == 0 or line.startswith('//'):
continue
# (else)
# print(self.current_pos, line)
return line
# End of input
return None
def _get_next_token(self):
"""Parse input until next token
Should be one of: keyword, end of section brace, end of input
"""
line = self._get_next_line()
while line is not None:
match = re.match(RE_KEYWORD, line)
if match:
keyword = match.group(1)
rest = line[match.end(2):].strip()
return TokenInfo(TokenType.KEYWORD, self.current_pos, keyword=keyword, rest_of_line=rest)
match = re.match('\s*}', line)
if match:
return TokenInfo(TokenType.END_SECTION, self.current_pos)
line = self._get_next_line()
return TokenInfo(TokenType.NULL, self.current_pos)
def _is_new_section(self, info):
""""""
rest = info.rest_of_line.strip()
return rest == '' or rest.startswith('{')
def _parse_file(self, path):
if not os.path.exists(path):
self.exists = False
return;
self.exists = True
with open(path) as fp:
self.lines = fp.readlines()
self.number_of_lines = len(self.lines)
# To traverse, reverse self.lines and pop one line at a time
# until self.lines is empty
self.lines.reverse()
# current_section = None
while self.lines:
info = self._get_next_token()
if info.type == TokenType.NULL:
break
if info.type == TokenType.KEYWORD:
# print('* Keyword', info.keyword)
if self._is_new_section(info):
self._parse_section(info)
def _parse_section(self, token_info):
"""Parse contents of section, which can be sub-sections and/or assignments"""
# print('** Start section', token_info.keyword)
# Parse forward to the starting brace
self._parse_section_start(token_info)
# Initialize section
name = token_info.keyword
section = Section(name, token_info.line_number)
dict_item = self.sections.get(name)
if dict_item is None:
dict_item = set()
dict_item.add(section)
self.sections[name] = dict_item
# Continue parsing for assignments until ending brace
next_info = self._get_next_token()
while next_info:
# print('next_info', next_info)
if next_info.type == TokenType.END_SECTION:
# print('**End section', name)
return
if next_info.type == TokenType.NULL:
raise RuntimeError('Unexpected end of file parsing section')
if not next_info.type == TokenType.KEYWORD:
raise RuntimeError('Unexpected token type {}'.format(next_info.type))
if self._is_new_section(next_info):
# Parse subsection
child_section = self._parse_section(next_info)
section.children.append(child_section)
else:
# Add regular assignment to section data
section.data[next_info.keyword] = next_info.rest_of_line
next_info = self._get_next_token()
return section
def _parse_section_start(self, token_info):
"""Parses to the end of line with section start brace ('{')"""
if token_info.rest_of_line.strip().startswith('{'):
return
line = self._get_next_line()
while line:
if line.strip().startswith('{'):
return
line = self._get_next_line()
raise RuntimeError('expected left brace but reached end of file')
set(ace3p_python_tests)
if (SMTK_ENABLE_VTK_SUPPORT)
list(APPEND ace3p_python_tests
omega3p_test1
)
endif()
set(smtk_pythonpath ${smtk_DIR}/${SMTK_PYTHONPATH})
if(NOT IS_ABSOLUTE ${smtk_pythonpath})
get_filename_component(smtk_pythonpath
${PROJECT_BINARY_DIR}/${smtk_DIR}/${SMTK_PYTHON_MODULEDIR} ABSOLUTE)
endif()
set(pyenv
${PROJECT_BINARY_DIR}
${smtk_pythonpath}
$ENV{PYTHONPATH})
if (WIN32)
string(REPLACE ";" "\;" pyenv "${pyenv}")
else ()
string(REPLACE ";" ":" pyenv "${pyenv}")
endif()
set(pathenv)
if (WIN32)
set(pathenv
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
$ENV{PATH})
string(REPLACE ";" "\;" pathenv "${pathenv}")
endif()
foreach (test ${ace3p_python_tests})
add_test(
NAME ${test}_py
COMMAND "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/${test}.py"
--data-dir=${PROJECT_SOURCE_DIR}/data
--temp-dir=${CMAKE_BINARY_DIR}/Testing/Temporary
--src-dir=${CMAKE_SOURCE_DIR}
)
set_tests_properties("${test}_py"
PROPERTIES
ENVIRONMENT "PYTHONPATH=${pyenv}"
LABELS "ACE3P Simulation"
)
if (pathenv)
set_property(TEST "${test}_py" APPEND
PROPERTY
ENVIRONMENT "PATH=${pathenv}"
)
endif ()
endforeach()
#=============================================================================
#
# Copyright (c) Kitware, Inc.
# All rights reserved.
# See LICENSE.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even
# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the above copyright notice for more information.
#
#=============================================================================
import os
import shutil
import sys
import unittest
import smtk
import smtk.attribute
import smtk.io
import smtk.operation
import smtk.resource
import smtk.session.vtk
import smtk.testing
from ACE3PFileValidator import ACE3PFileValidator
import util
OPERATION_SUCCEEDED = int(smtk.operation.Operation.SUCCEEDED) # 3
MODEL_NAME = 'pillbox4'
OUTPUT_FOLDER = 'export_{}'.format(MODEL_NAME)
class Omega3PTest1(smtk.testing.TestCase):
def setUp(self):
# Make sure last test result is removed
export_folder = os.path.join(smtk.testing.TEMP_DIR, OUTPUT_FOLDER)
if os.path.exists(export_folder):
shutil.rmtree(export_folder)
# Initialize smtk managers
self.res_manager = smtk.resource.Manager.create()
self.op_manager = smtk.operation.Manager.create()
smtk.attribute.Registrar.registerTo(self.res_manager)
smtk.attribute.Registrar.registerTo(self.op_manager)
smtk.session.vtk.Registrar.registerTo(self.res_manager)
smtk.session.vtk.Registrar.registerTo(self.op_manager)
smtk.operation.Registrar.registerTo(self.op_manager)
self.op_manager.registerResourceManager(self.res_manager)
def tearDown(self):
self.res_manager = None
self.op_manager = None
def import_resource(self, path):
import_op = self.op_manager.createOperation(
'smtk::operation::ImportResource')
import_op.parameters().find('filename').setValue(path)
import_result = import_op.operate()
import_outcome = import_result.findInt('outcome').value(0)
self.assertEqual(import_outcome, OPERATION_SUCCEEDED)
resource = import_result.find('resource').value()
self.assertIsNotNone(resource)
return resource
def import_python_op(self, path):
import_op = self.op_manager.createOperation(
'smtk::operation::ImportPythonOperation')
self.assertIsNotNone(import_op)
import_op.parameters().find('filename').setValue(path)
import_result = import_op.operate()
import_outcome = import_result.findInt('outcome').value(0)
self.assertEqual(import_outcome, OPERATION_SUCCEEDED)
op_unique_name = import_result.findString("unique_name").value()
op = self.op_manager.createOperation(op_unique_name)
return op
def set_attributes(self, att_resource, model_resource):
"""Populate simulation attributes"""
# Bounday conditions
bc_lookup = {
1: 'Electric',
2: 'Electric'
}
bc_default = 'Exterior'
uuids = model_resource.entitiesMatchingFlags(smtk.model.FACE, True)
for uuid in uuids:
# print('UUID {}'.format(uuid))
prop_list = model_resource.integerProperty(uuid, 'pedigree id')
face_id = prop_list[0]
# print('Face ID {}'.format(face_id))
bc_type = bc_lookup.get(face_id, bc_default)
bc_att = att_resource.createAttribute(bc_type)
self.assertIsNotNone(bc_att)
self.assertTrue(bc_att.associateEntity(uuid))
# Material
uuids = model_resource.entitiesMatchingFlags(smtk.model.VOLUME, True)
uuid = uuids.pop()
mat_att = att_resource.createAttribute('Material')
epsilon_item = mat_att.findDouble('Epsilon')
self.assertTrue(epsilon_item.setValue(2.2))
mu_item = mat_att.findDouble('Mu')
self.assertTrue(mu_item.setValue(0.5))
self.assertTrue(mat_att.associateEntity(uuid))
# Analysis items
finfo_att = att_resource.findAttributes('FrequencyInfo')[0]
num_item = finfo_att.findInt('NumEigenvalues')
self.assertTrue(num_item.setValue(3))
pp_att = att_resource.findAttributes('PostProcess')[0]
toggle_item = pp_att.findGroup('Toggle')
prefix_item = toggle_item.find('ModeFilePrefix')
prefix_item.setIsEnabled(True)
def check_results(self, path):
validator = ACE3PFileValidator(path)
self.assertGreater(validator.get_number_of_lines(), 12)
sections = [
'ModelInfo', 'BoundaryCondition', 'FiniteElement', 'EigenSolver', 'PostProcess',
'SurfaceMaterial', 'Material'
]
for name in sections:
msg = 'output includes {} section'.format(name)
self.assertTrue(validator.has_section(name), msg)
def test_pillbox4(self):
# Load model file
gen_filename = '{}.gen'.format(MODEL_NAME)
gen_path = os.path.join(
smtk.testing.SOURCE_DIR, 'data', 'model', '3d', 'genesis', gen_filename)
resource = self.import_resource(gen_path)
model_resource = smtk.model.Resource.CastTo(resource)
self.assertIsNotNone(model_resource)
# Set the model location in lieu of writing it to the file system
model_filename = '{}.smtk'.format(MODEL_NAME)
model_location = os.path.join(smtk.testing.TEMP_DIR, model_filename)
model_resource.setLocation(model_location)
# Load attribute template
sbt_path = os.path.join(
smtk.testing.SOURCE_DIR, 'simulation-workflows', 'ACE3P.sbt')
self.assertTrue(os.path.exists(sbt_path))
att_resource = self.import_resource(sbt_path)
self.assertIsNotNone(att_resource)
# Associate model to attribute resource
self.assertTrue(att_resource.associate(model_resource))