def test_model_doc(): cross_section = FeatureSpec() cross_section.add_prop_spec('E', { 'type': float, '>': 0 }, doc="Young's modulus") cross_section.add_prop_spec('A', {'type': float, '>': 0}, doc="Area") geom = FeatureSpec() geom.add_prop_spec('point1', list, doc="Coordinates of point 1") geom.add_prop_spec('point2', list, doc="Coordinates of point 2") mspec = ModelSpec() mspec.add_feature_spec('CrossSection', cross_section, doc="Beam cross section") mspec.add_feature_spec('Geom', geom, doc="Beam geometry") docs = mspec.get_docs() assert docs['CrossSection']['main'] == "Beam cross section" assert docs['Geom']['main'] == "Beam geometry" assert docs['CrossSection']['sub']['E']['main'] == "Young's modulus" assert docs['CrossSection']['sub']['A']['main'] == "Area" assert docs['Geom']['sub']['point1']['main'] == "Coordinates of point 1" assert docs['Geom']['sub']['point2']['main'] == "Coordinates of point 2"
def test_to_dict_and_documentation(): """ Test basic functionality of 'ModelSpec' """ print() # ===== Specifications ===== fspec_beam = FeatureSpec() fspec_beam.add_prop_spec('A', { 'type': int, '>': 0 }, doc='Something about property A', max_items=1) fspec_beam.add_prop_spec('B', int, doc='Property B is also great') fspec_study = FeatureSpec() fspec_study.add_prop_spec('static', bool, max_items=1) mspec = ModelSpec() mspec.add_feature_spec('beam', fspec_beam, doc='A beam carries load') mspec.add_feature_spec('study', fspec_study, max_items=1, doc='Specify the type of study to run') class Model(mspec.user_class): def run(self): pass beam_model = Model() # ===== User logic ===== beam1 = beam_model.add_feature('beam') beam1.set('A', 1) beam1.add('B', 2) beam1.add('B', 3) beam2 = beam_model.add_feature('beam') beam2.set('A', 2) beam2.add('B', 4) beam2.add('B', 6) study = beam_model.set_feature('study') study.set('static', True) # ----- Serialization ----- model_dict = beam_model.to_dict() with open('test.json', 'w') as fp: dump_pretty_json(model_dict, fp) beam_model = Model().from_dict(model_dict) beam1 = beam_model.get('beam')[0] assert beam1.get('A') == 1 assert beam1.get('B') == [2, 3]
def test_errors_add_feature_spec(): """ Test 'add_feature_spec()' method """ fspec_door = FeatureSpec() fspec_door.add_prop_spec('color', str) fspec_motor = FeatureSpec() fspec_motor.add_prop_spec('type', str) mspec_car = ModelSpec() # ----- Wrong type: key ----- with pytest.raises(TypeError): mspec_car.add_feature_spec(22, fspec_door) # ----- Wrong type: feature_spec ----- with pytest.raises(TypeError): mspec_car.add_feature_spec('door', fspec_door.user_class) with pytest.raises(TypeError): mspec_car.add_feature_spec('door', fspec_door.user_class()) # ----- Wrong type: singleton ----- with pytest.raises(TypeError): mspec_car.add_feature_spec('door', fspec_door, singleton='yes') # Cannot add property twice... mspec_car.add_feature_spec('motor', fspec_motor) with pytest.raises(KeyError): mspec_car.add_feature_spec('motor', fspec_motor)
def test_error_set_feature(): """ """ print() fspec = FeatureSpec() mspec = ModelSpec() fspec.add_prop_spec('a', int, singleton=True) fspec.add_prop_spec('b', int, singleton=False) mspec.add_feature_spec('A', fspec, singleton=True) fspec.add_prop_spec('1', str, singleton=True) fspec.add_prop_spec('2', str, singleton=False) mspec.add_feature_spec('B', fspec, singleton=False) class Model(mspec.user_class): def run(self): pass m = Model() # Feature 'A' is non-singleton with pytest.raises(RuntimeError): m.add_feature('A') m.set_feature('A') # Feature 'B' is singleton with pytest.raises(RuntimeError): m.set_feature('B') m.add_feature('B') m.add_feature('B') m.add_feature('B') fa = m.get('A') fa.set('a', 2) fb = m.get('B') assert len(fb) == 3 with pytest.raises(KeyError): for _ in m.iter('A'): pass for _ in m.iter('B'): pass
def test_basic(): """ Test basic functionality of 'ModelSpec' """ print() # ===== Specifications ===== fspec_beam = FeatureSpec() fspec_beam.add_prop_spec('A', int) fspec_beam.add_prop_spec('B', int, singleton=False) fspec_study = FeatureSpec() fspec_study.add_prop_spec('static', bool) mspec = ModelSpec() mspec.add_feature_spec('beam', fspec_beam, singleton=False) mspec.add_feature_spec('study', fspec_study, singleton=True) class Model(mspec.user_class): def run(self): pass beam_model = Model() # ===== User logic ===== beam1 = beam_model.add_feature('beam') beam1.set('A', 1) beam1.add('B', 2) beam1.add('B', 3) beam2 = beam_model.add_feature('beam') beam2.set('A', 2) beam2.add('B', 4) beam2.add('B', 6) study = beam_model.set_feature('study') study.set('static', True) # Beam 1 and 2 have different values for the same property assert beam1.get('A') == 1 assert beam2.get('A') == 2 # Beams are different, but the parent specification is the same assert beam1.uid != beam2.uid assert beam1._parent_uid == beam2._parent_uid
def test_required_items(): """ Check that model can only be run if features and properties are well-defined """ print() # ===== Specifications ===== fspec_beam = FeatureSpec() fspec_beam.add_prop_spec('A', int, required=1, max_items=1) fspec_wing = FeatureSpec() fspec_wing.add_prop_spec('B', int, required=3, max_items=3) # fspec_wing.add_prop_spec('C', int, required=-1) mspec = ModelSpec() mspec.add_feature_spec('beam', fspec_beam, required=2) mspec.add_feature_spec('wing', fspec_wing, required=1) class Model(mspec.user_class): def run(self): super().run() beam_model = Model() # ===== User logic ===== beam1 = beam_model.add_feature('beam') beam1.set('A', 2) beam2 = beam_model.add_feature('beam') beam2.set('A', 2) # Feature 'wing' needs to be defined with pytest.raises(RuntimeError): beam_model.run() wing1 = beam_model.add_feature('wing') # Property 'B' is needs to be defined with pytest.raises(RuntimeError): beam_model.run() wing1.add('B', 11) wing1.add('B', 22) wing1.add('B', 33) # Cannot add more items of type 'B' with pytest.raises(RuntimeError): wing1.add('B', 44) # Model is now well-defined beam_model.run()
def test_error_provide_user_class(): """ Test method 'user_class' """ print() fspec = FeatureSpec() fspec.add_prop_spec('a', int, max_items=1) fspec.add_prop_spec('b', int) Feature = fspec.user_class f = Feature() f.set('a', 12) f.add_many('b', 11, 22, 33) with pytest.raises(KeyError): f.set('x', 55) with pytest.raises(RuntimeError): f.add('a', 55) with pytest.raises(RuntimeError): f.set('b', 55) assert f.len('a') == 1 assert f.len('b') == 3 assert f.get('a') == 12 assert f.get('b') == [11, 22, 33]
def test_from_dict(): fspec = FeatureSpec() fspec.add_prop_spec('a', int) fspec.add_prop_spec('b', str) fspec.add_prop_spec('c', {'type': bool}) Feature = fspec.user_class props = { 'a': [ 42, ], 'b': [ 'snake', ], 'c': [ True, ], } f = Feature().from_dict(props) assert f.get('a') == 42 assert f.get('b') == 'snake' assert f.get('c') is True
def test_feature_doc(): fspec = FeatureSpec() fspec.add_prop_spec('E', {'type': float, '>': 0}, doc="Young's modulus") fspec.add_prop_spec('A', {'type': float, '>': 0}, doc="Area") docs = fspec.get_docs() assert len(docs) == 2 assert docs['E']['main'] == "Young's modulus" assert docs['A']['main'] == "Area" assert docs['E']['sub'] is None assert docs['A']['sub'] is None
def test_complex_schema(): """ Test variations of more complex property schemas """ print() # ===== Specifications ===== schema_global = { 'name': {'type': str, 'min_len': 1}, 'mass': {'type': float, '>': 0}, } schema_wing = { 'id': {'type': str, 'min_len': 3}, 'span': {'type': float, '>': 0}, 'area': {'type': float, '>': 0}, } fspec_aircraft = FeatureSpec() fspec_aircraft.add_prop_spec('global', schema_global, max_items=1) fspec_aircraft.add_prop_spec('wing', schema_wing) Aircraft = fspec_aircraft.user_class aircraft = Aircraft() # ===== User logic ===== aircraft.set('global', {'name': 'AD42', 'mass': 50e3}) aircraft.add('wing', {'id': 'MainWing', 'span': 32.4, 'area': 55.0}) aircraft.add('wing', {'id': 'HorizTail', 'span': 7.5, 'area': 18.2}) # Overwrite 'global' aircraft.set('global', {'name': 'AD8888', 'mass': 49.9e3}) # Add method does not apply to 'global' with pytest.raises(RuntimeError): aircraft.add('global', {'name': 'abc', 'mass': 1.0}) # Set method does not apply to 'wing' with pytest.raises(RuntimeError): aircraft.set('wing', {'id': 'abc', 'span': 1.0, 'area': 1.0}) # Cause schemadict error with pytest.raises(TypeError): aircraft.set('wing', {'id': 'abc', 'span': 'WRONG_VALUE', 'area': 1.0})
seg.len / self.len) # TODO: duplicate yield Point(p.coord, rel_coord=eta_poly, uid=p.uid) # ===== Abstract beam element ===== schema_load = { 'load': S.vector6x1, 'node': S.pos_int, 'local_sys': { 'type': bool } } schema_mass = {'mass': {'type': S.pos_number}, 'node': S.pos_int} fspec = FeatureSpec() for p in Element.PROP_TYPES: fspec.add_prop_spec(p, S.pos_number) fspec.add_prop_spec('up', S.vector3x1) fspec.add_prop_spec('point_load', schema_load, singleton=False) fspec.add_prop_spec('dist_load', schema_load, singleton=False) fspec.add_prop_spec('point_mass', schema_mass, singleton=False) # ================================= class AbstractEdgeElement(fspec.user_class): def __init__(self, p1, p2): """
yield Point(p.coord, rel_coord=eta_poly, uid=p.uid) # ===== Abstract beam element ===== schema_load = { 'load': S.vector6x1, 'node': S.pos_int, 'local_sys': { 'type': bool } } schema_distr_load = {'load': S.vector6x1, 'local_sys': {'type': bool}} schema_mass = {'mass': {'type': S.pos_number}, 'node': S.pos_int} fspec = FeatureSpec() for p in Element.PROP_TYPES: fspec.add_prop_spec(p, S.pos_number, max_items=1) fspec.add_prop_spec('up', S.vector3x1, max_items=1) fspec.add_prop_spec('point_load', schema_load) fspec.add_prop_spec('distr_load', schema_load) fspec.add_prop_spec('dist_load', schema_load) fspec.add_prop_spec('point_mass', schema_mass) # ================================= class AbstractEdgeElement(fspec.user_class): def __init__(self, p1, p2):
def test_basic(): """ Test basic functionality of 'FeatureSpec' """ print() # ===== Specifications ===== fspec = FeatureSpec() fspec.add_prop_spec('A', int, max_items=1) fspec.add_prop_spec('B', int, max_items=1) fspec.add_prop_spec('C', int) fspec.add_prop_spec('D', {'type': int, '>': 0}, max_items=1) # Check that schemas cannot be defined twice with pytest.raises(KeyError): fspec.add_prop_spec('D', str) Feature = fspec.user_class f = Feature() # ===== User logic ===== # ----- set() method ----- f.set('A', 5) f.set('B', 8) f.set('B', 9) # Check the added values assert f.get('A') == 5 assert f.get('B') == 9 # Property 'B' can only have one entry assert f.len('B') == 1 # Check that keys which are not allowed cannot be set with pytest.raises(KeyError): f.set("PROPERTY_DOES_NOT_EXIST", 10) # Value for 'D' can only be positive (see specification) f.set('D', 4) with pytest.raises(ValueError): f.set('D', -4) # ----- add() method ----- f.add('C', 11) f.add('C', 22) f.add('C', 33) # Property 'C' has three entries assert f.len('C') == 3 # Check that keys which are not allowed cannot be added with pytest.raises(KeyError): f.add("PROPERTY_DOES_NOT_EXIST", 10) # Check the added values assert f.get('C') == [11, 22, 33] exp_items = [11, 22, 33] for i, item in enumerate(f.iter('C')): assert item == exp_items[i] # Cannot iterate over unique property with pytest.raises(KeyError): for _ in f.iter('A'): pass
# TODO: # - acceleration state (define on beam level, or 'global'?) # ================= # ===== MODEL ===== # ================= mspec = ModelSpec() tmp_doc = "The *{X}* feature is optional and allows to define sets of constant \ {X} properties. When defining the properties for a specific beam \ (or parts of it), you may refer to a {X} set using its UID. You may \ define as many sets as you like." # ===== Material ===== fspec = FeatureSpec() fspec.add_prop_spec('E', S.pos_number, required=True, doc="Young's modulus [N/m²]") fspec.add_prop_spec('G', S.pos_number, required=True, doc="Shear modulus [N/m²]") fspec.add_prop_spec('rho', S.pos_number, required=True, doc="Density [kg/m³]") mspec.add_feature_spec( 'material', fspec, singleton=False, required=False, doc=tmp_doc.format(X='material'),
#!/usr/bin/env python3 # -*- coding: utf-8 -*-m # _model.py from mframework import FeatureSpec, ModelSpec from ._run import run_model # Here, we only have numerical user input. We only allow positive floats. SCHEMA_POS_FLOAT = {'type': float, '>': 0} # ===== MODEL ===== mspec = ModelSpec() # Create the first feature 'ambiance' fspec = FeatureSpec() fspec.add_prop_spec('g', SCHEMA_POS_FLOAT, doc='Gravitational acceleration') fspec.add_prop_spec('a', SCHEMA_POS_FLOAT, doc='Speed of sound') mspec.add_feature_spec('ambiance', fspec, doc='Ambient flight conditions') # Feature 'aerodynamics' fspec = FeatureSpec() fspec.add_prop_spec('CL', SCHEMA_POS_FLOAT, doc='Cruise lift coefficient') fspec.add_prop_spec('CD', SCHEMA_POS_FLOAT, doc='Cruise drag coefficient') fspec.add_prop_spec('Mach', SCHEMA_POS_FLOAT, doc='Cruise Mach number') mspec.add_feature_spec('aerodynamics', fspec, doc='Aerodynamic properties') # Feature 'propulsion' fspec = FeatureSpec() fspec.add_prop_spec('cT', SCHEMA_POS_FLOAT,
def test_from_dict(): fspec1 = FeatureSpec() fspec1.add_prop_spec('a', int) fspec1.add_prop_spec('b', str) fspec1.add_prop_spec('c', {'type': bool}) fspec2 = FeatureSpec() fspec2.add_prop_spec('one', int) fspec2.add_prop_spec('two', str) fspec2.add_prop_spec('three', {'type': bool}) mspec = ModelSpec() mspec.add_feature_spec('A', fspec1) mspec.add_feature_spec('B', fspec2) class Model(mspec.user_class): def run(self): pass props1 = { 'a': [ 42, ], 'b': [ 'snake', ], 'c': [ True, ], } props2 = { 'one': [ 43, ], 'two': [ 'Snake', ], 'three': [ False, ], } model_dict = { 'A': [ props1, ], 'B': [ props2, ], } m = Model().from_dict(model_dict) m_fa = m.get('A') m_fb = m.get('B') assert m_fa.get('a') == 42 assert m_fa.get('b') == 'snake' assert m_fa.get('c') is True assert m_fb.get('one') == 43 assert m_fb.get('two') == 'Snake' assert m_fb.get('three') is False
def test_error_add_property_spec(): """ Test method 'add_prop_spec()' """ fspec = FeatureSpec() # ----- Wrong type: key ----- with pytest.raises(TypeError): fspec.add_prop_spec(44, int) # ----- Wrong type: schema ----- class MySpecialNumber: pass with pytest.raises(TypeError): fspec.add_prop_spec('number', MySpecialNumber) # ----- Wrong type: max_items ----- with pytest.raises(TypeError): fspec.add_prop_spec('number', int, max_items='yes') # ----- Wrong type: required ----- with pytest.raises(TypeError): fspec.add_prop_spec('number', int, required='yes', max_items=1) # Cannot add property twice... fspec.add_prop_spec('color', str) with pytest.raises(KeyError): fspec.add_prop_spec('color', str)
SchemadictValidators.register_type(np.ndarray) SchemadictValidators.register_type(sparse.csr_matrix) SchemadictValidators[str]['is_dir'] = is_dir # ================= # ===== MODEL ===== # ================= mspec = ModelSpec() tmp_doc = "The *{X}* feature is optional and allows to define sets of constant \ {X} properties. When defining the properties for a specific beam \ (or parts of it), you may refer to a {X} set using its UID. You may \ define as many sets as you like." # ===== Material ===== fspec = FeatureSpec() fspec.add_prop_spec('E', S.pos_number, max_items=1, doc="Young's modulus [N/m²]") fspec.add_prop_spec('G', S.pos_number, max_items=1, doc="Shear modulus [N/m²]") fspec.add_prop_spec('rho', S.pos_number, max_items=1, doc="Density [kg/m³]") mspec.add_feature_spec( 'material', fspec, required=0, doc=tmp_doc.format(X='material'), uid_required=True, ) # ===== Cross-section =====