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_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_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_from_dict(): fspec1 = FeatureSpec() fspec1.add_prop_spec('a', int, max_items=1) fspec1.add_prop_spec('b', str, max_items=1) fspec1.add_prop_spec('c', {'type': bool}, max_items=1) fspec2 = FeatureSpec() fspec2.add_prop_spec('one', int, max_items=1) fspec2.add_prop_spec('two', str, max_items=1) fspec2.add_prop_spec('three', {'type': bool}, max_items=1) mspec = ModelSpec() mspec.add_feature_spec('A', fspec1, max_items=1) mspec.add_feature_spec('B', fspec2, max_items=1) 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
# ===== 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'), uid_required=True, ) # ===== Cross-section ===== fspec = FeatureSpec() fspec.add_prop_spec('A', S.pos_number, required=True, doc="Area [m²]") fspec.add_prop_spec('Iy', S.pos_number, required=True, doc="Second moment of area about the local y-axis [m⁴]") fspec.add_prop_spec('Iz', S.pos_number, required=True, doc="Second moment of area about the local z-axis [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, doc='Thrust specific fuel consumption') mspec.add_feature_spec('propulsion', fspec, doc='Profusion properties')
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)
{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 ===== fspec = FeatureSpec() fspec.add_prop_spec('A', S.pos_number, max_items=1, doc="Area [m²]") fspec.add_prop_spec('Iy', S.pos_number, max_items=1, doc="Second moment of area about the local y-axis [m⁴]") fspec.add_prop_spec('Iz', S.pos_number, max_items=1, doc="Second moment of area about the local z-axis [m⁴]")