def default_well() -> case_description.WellDescription: """ Minimum valid WellDescription """ return case_description.WellDescription( name="Well 1", profile=case_description.ProfileDescription( length_and_elevation=case_description.LengthAndElevationDescription( length=Array([0.0] + [1000] * 2, "m"), elevation=Array([0.0] + [1.2] * 2, "m"), ) ), stagnant_fluid="Lift Gas", top_node="Node 1", bottom_node="Node 2", annulus=case_description.AnnulusDescription( has_annulus_flow=False, top_node="Node 1" ), formation=case_description.FormationDescription( reference_y_coordinate=Scalar(0, "m") ), environment=case_description.EnvironmentDescription( thermal_model=constants.PipeThermalModelType.SteadyState ), )
def test_cv_table_description(): expected_msg = "Opening and Flow Coefficient must have the same size, got 2 items for flow_coefficient and 1 for opening" with pytest.raises(ValueError, match=re.escape(expected_msg)): case_description.CvTableDescription( opening=Array([0.0], "-"), flow_coefficient=Array([0.0, 2], "(galUS/min)/(psi^0.5)"), )
def test_convert_description_to_alfacase_with_empty_dict( datadir: Path) -> None: """ Ensure that the conversion from a description into a Alfacase it's not generating empty dict. Since it's not a valid syntax for strictyaml resulting in an InconsistentIndentationDisallowed error """ simple_case = case_description.CaseDescription( name="Simple Case", pipes=[ case_description.PipeDescription( name="pipe", source="mass_source_inlet", target="pressure_outlet", segments=build_simple_segment(), profile=case_description.ProfileDescription( x_and_y=case_description.XAndYDescription( x=Array([0], "m"), y=Array([0], "m"))), ) ], ) simple_case_alfacase_content = convert_description_to_alfacase(simple_case) assert "wall_description: {}" not in simple_case_alfacase_content assert "tables: {}" not in simple_case_alfacase_content # Smoke check, ensures that the alfacase is loaded correctly without errors simple_case_alfacase_file = datadir / "simple_case.alfacase" simple_case_alfacase_file.write_text(data=simple_case_alfacase_content, encoding="UTF-8") loaded_alfacase = DescriptionDocument.from_file(simple_case_alfacase_file) assert loaded_alfacase.content["name"].data == simple_case.name
def build_constant_initial_tracers_mass_fractions_description(values, unit): return case_description.InitialTracersMassFractionsDescription( position_input_type=constants.TableInputType.length, table_length=case_description.TracersMassFractionsContainerDescription( positions=Array([0.0], "m"), tracers_mass_fractions=[Array(values, unit)]), )
def build_compressor_pressure_table_description( speed_entries, corrected_mass_flow_rate_entries, pressure_ratio_table, isentropic_efficiency_table, ): """ Helper to build a table for CompressorPressureTable from a parametrized input of `speed_entries` and `corrected_mass_flow_rate_entries`, correcting it so len(pressure_ratio_table) and len(isentropic_efficiency_table) have elements = len(speed_entries) * len(corrected_mass_flow_rate_entries), making it a explicit table with all combinations. """ expected_size = len(speed_entries) * len(corrected_mass_flow_rate_entries) assert expected_size == len( pressure_ratio_table ), f"Missing pressure entries. Expected {expected_size} but got {len(pressure_ratio_table)}" assert expected_size == len( isentropic_efficiency_table ), f"Missing isentropic efficiency entries. Expected {expected_size} but got {len(isentropic_efficiency_table)}" adjusted_speeds = [] adjusted_flow_rates = [] for speed in speed_entries: for flow_rate in corrected_mass_flow_rate_entries: adjusted_speeds.append(speed) adjusted_flow_rates.append(flow_rate) return case_description.CompressorPressureTableDescription( speed_entries=Array(adjusted_speeds, speed_entries.unit), corrected_mass_flow_rate_entries=Array( adjusted_flow_rates, corrected_mass_flow_rate_entries.unit), pressure_ratio_table=pressure_ratio_table, isentropic_efficiency_table=isentropic_efficiency_table, )
def testNumberOverArray() -> None: a = Array([2.0, 2.0], "m") b = Array([3.0, 3.0], "m") c = 1.0 / a assert approx(c.GetValues("1/m")) == [0.5, 0.5] assert approx((3.0 / a).GetValues("1/m")) == [1.5, 1.5] assert b / a == b * 1 / a == b * (1 / a)
def test_build_compressor_pressure_table_description_invalid( pressure_ratios, isentropic_efficiencies, error_msg): with pytest.raises(AssertionError, match=error_msg): build_compressor_pressure_table_description( speed_entries=Array([0, 1000], "rpm"), corrected_mass_flow_rate_entries=Array([1, 2, 3], "kg/s"), pressure_ratio_table=Array(pressure_ratios, "-"), isentropic_efficiency_table=Array(isentropic_efficiencies, "-"), )
def build_constant_initial_volume_fractions_description(values): return case_description.InitialVolumeFractionsDescription( position_input_type=constants.TableInputType.length, table_length=case_description.VolumeFractionsContainerDescription( positions=Array([0.0], "m"), fractions={k: Array([v.value], v.unit) for k, v in values.items()}, ), )
def testDefaultValues(unit_database_len) -> None: # Not raises exception because by default validation is False on the copy operation array = Array[List[float]]("flow rate") assert array.values == [] array = Array(ObtainQuantity("m")) assert array.values == [] with pytest.raises(AssertionError): Array(ObtainQuantity("m"), unit="m") # type:ignore[call-overload]
def testDefaultValues(unit_database_len): # Not raises exception because by default validation is False on the copy operation array = Array("flow rate") assert array.values == [] array = Array(ObtainQuantity("m")) assert array.values == [] with pytest.raises(AssertionError): Array(ObtainQuantity("m"), unit="m")
def build_constant_initial_velocities_description(values): return case_description.InitialVelocitiesDescription( position_input_type=constants.TableInputType.length, table_length=case_description.VelocitiesContainerDescription( positions=Array([0.0], "m"), velocities={ key: Array([v.value], v.unit) for key, v in values.items() }, ), )
def build_simple_segment(): """ Return an instance of PipeSegmentsDescription with a pre-populated value. The pre-filled value was the default value used by all tests before refactoring, which removed `diameter` and `absolute_roughness` from PipeDescription. """ return case_description.PipeSegmentsDescription( start_positions=Array([0.0], "m"), diameters=Array([0.1], "m"), roughnesses=Array([1e-5], "m"), )
def test_pvt_model_default_behavior(self, default_case, default_well): """ 1) CaseDescription should accept components that uses PvtModel as None while default_model is configured. 2) default_model must be a valid pvt_model defined on pvt_models 3) If default_model is None, all components that uses pvt_model must be assigined. """ from alfasim_sdk._internal.constants import NodeCellType case = attr.evolve( default_case, pvt_models=attr.evolve(default_case.pvt_models, default_model="PVT2"), nodes=[ case_description.NodeDescription( name="Node 1", node_type=NodeCellType.Pressure, pvt_model=None) ], pipes=[ case_description.PipeDescription( name="Pipe 1", pvt_model=None, source="in", target="out", segments=build_simple_segment(), profile=case_description.ProfileDescription( x_and_y=case_description.XAndYDescription( x=Array([0], "m"), y=Array([0], "m"))), ) ], wells=[ attr.evolve( default_well, pvt_model=None, annulus=attr.evolve(default_well.annulus, pvt_model=None), ) ], ) # Check 1) If default_model assigned, elements that uses pvt_model can be None case.ensure_valid_references() assert case.pvt_models.default_model == "PVT2" assert case.nodes[0].pvt_model is None assert case.pipes[0].pvt_model is None assert case.wells[0].pvt_model is None assert case.wells[0].annulus.pvt_model is None # Check 2) default_model must be filled with a valid pvt_model case_with_invalid_default_pvt_model = attr.evolve( case, pvt_models=attr.evolve(case.pvt_models, default_model="Acme"))
def test_to_array(): type_error_msg = r"Expected pair \(values, unit\) or Array" assert to_array(None, is_optional=True) is None with pytest.raises(TypeError, match=type_error_msg): to_array(None) assert to_array(([1, 2, 3], "m")) == Array("length", [1, 2, 3], "m") with pytest.raises(TypeError, match=type_error_msg): to_array(("foo", [1, 2, 3], "m")) with pytest.raises(TypeError, match=type_error_msg): to_array("foo") array = Array("time", [0, 1.1, 2.2], "s") assert to_array(array) is array
def testNumpyWithMinMax(unit_database_custom_conversion) -> None: unit_database = unit_database_custom_conversion unit_database.AddCategory( "my length", "length", ["mm", "m"], default_unit="m", default_value=2, min_value=0, max_value=15, is_min_exclusive=False, is_max_exclusive=False, caption="My Length", ) import numpy values = numpy.array(list(range(10)), numpy.float32) Array("my length", values, "mm") values = numpy.array(list(range(20000, 20010)), numpy.float32) # Raise validation error another = Array("my length", values, "mm") assert not another.IsValid() with pytest.raises(ValueError): another.CheckValidity() # Don't raise validation error if validate = False. arr = Array("my length", values, "mm") assert not arr.IsValid() another = arr.CreateCopy(values=values, unit="mm") assert not another.IsValid() # Same checks on FixedArray. values = numpy.array(list(range(10)), numpy.float32) FixedArray(len(values), "my length", values, "mm") values = numpy.array(list(range(20000, 20010)), numpy.float32) # Raise validation error another = FixedArray(len(values), "my length", values, "mm") assert not another.IsValid() # Don't raise validation error if validate = False. arr = FixedArray(len(values), "my length", values, "mm") assert not arr.IsValid() another = arr.CreateCopy(values=values, unit="mm") assert not another.IsValid()
def to_array(value: ArrayLike, is_optional: bool = False, *, error_context: Optional[str] = None) -> Array: """ Converter to be used with attr.ib, accepts tuples and Array as input. If `is_optional` is defined, the converter will also accept None, same as `to_scalar`. The `error_context` has the same behavior as in `to_scalar`. Ex.: @attr.s class Foo: bar = attr.id(converter=to_array) Foo(bar=Array([1,2,3],"m")) Foo(bar=([1,2,3],"m")) Foo(bar=((1,2,3),"m")) """ if is_optional and value is None: return value if is_two_element_tuple(value): return Array(*value) elif isinstance(value, Array): return value message = prepare_error_message( f"Expected pair (values, unit) or Array, got {value!r} (type: {type(value)})", error_context, ) raise TypeError(message)
def test_check_empty_array(self) -> None: assert Array.FromScalars(scalars=[]) == Array.CreateEmptyArray() assert Array.FromScalars(scalars=[], unit="m") == Array([], "m") expected_msg = "If category and value are given, the unit must be specified too." with pytest.raises(AssertionError, match=expected_msg): Array.FromScalars(scalars=[], category="length")
def testCopyPropertiesAndValidation(unit_database_len) -> None: # Not raises exception because by default validation is False on the copy operation array_source = Array("flow rate", values=[-1, -2, -3], unit="m3/s") assert not array_source.IsValid() array_dest = array_source.CreateCopy() assert array_dest.values == [-1, -2, -3]
def testNumpyConversion(unit_database_custom_conversion): unit_database = unit_database_custom_conversion from barril.units.posc import MakeBaseToCustomary, MakeCustomaryToBase f_unit_to_base = MakeCustomaryToBase(273.15, 1, 1, 0) f_base_to_unit = MakeBaseToCustomary(273.15, 1, 1, 0) unit_database.AddUnit( "My Temperature", "degrees Celsius", "degC", f_base_to_unit, f_unit_to_base, default_category=None, ) f_unit_to_base = MakeCustomaryToBase(2298.35, 5, 9, 0) f_base_to_unit = MakeBaseToCustomary(2298.35, 5, 9, 0) unit_database.AddUnit( "My Temperature", "degree Fahrenheit", "degF", f_base_to_unit, f_unit_to_base, default_category=None, ) f_unit_to_base = MakeCustomaryToBase(0, 1, 1, 0) f_base_to_unit = MakeBaseToCustomary(0, 1, 1, 0) unit_database.AddUnit( "My Temperature", "my Kelvin", "myK", f_base_to_unit, f_unit_to_base, default_category=None, ) unit_database.AddCategory( "My Temperature", quantity_type="My Temperature", valid_units=["myK", "degC", "degF"], default_unit="myK", default_value=2, min_value=0, max_value=15, is_min_exclusive=False, is_max_exclusive=False, caption="My Temperature", ) import numpy values = numpy.array(list(range(10, 13)), numpy.float32) arr = Array("My Temperature", values, "degC") obtained = arr.GetValues("degF") expected = [49.99995041, 51.79994965, 53.5999527] assert approx(obtained) == expected
def test_table_pump_description_length(): expected_msg = dedent("""\ speeds, void_fractions, flow_rates and pressure_boosts must have the same size, got: - 2 items for speeds - 2 items for void_fractions - 2 items for flow_rates - 1 items for pressure_boosts """) with pytest.raises(ValueError, match=re.escape(expected_msg)): case_description.TablePumpDescription( speeds=Array([1, 2], "rpm"), void_fractions=Array([1, 2], "-"), flow_rates=Array([1, 2], "m3/s"), pressure_boosts=Array([1], "bar"), ) # Check if the defaults values works well case_description.TablePumpDescription()
def test_compressor_pressure_table_description_length(): expected_msg = dedent("""\ speed_entries, corrected_mass_flow_rate_entries, pressure_ratio_table and isentropic_efficiency_table must have the same size, got: - 2 items for speed_entries - 2 items for corrected_mass_flow_rate_entries - 2 items for pressure_ratio_table - 1 items for isentropic_efficiency_table """) with pytest.raises(ValueError, match=re.escape(expected_msg)): case_description.CompressorPressureTableDescription( speed_entries=Array([1, 2], "rpm"), corrected_mass_flow_rate_entries=Array([1, 2], "kg/s"), pressure_ratio_table=Array([1, 2], "-"), isentropic_efficiency_table=Array([1], "-"), ) # Check if the defaults values works well case_description.CompressorPressureTableDescription()
def test_get_value_and_unit_from_length_and_elevation_description_and_xand_ydescription( ): """ Ensure that GetValueAndUnit returns a pair of values along with their units. """ length_and_elevation = case_description.LengthAndElevationDescription( length=Array([0.0, 5.0, 7.0], "m"), elevation=Array([1.0] * 3, "m")) assert list(length_and_elevation.iter_values_and_unit()) == [ ((0.0, "m"), (1.0, "m")), ((5.0, "m"), (1.0, "m")), ((7.0, "m"), (1.0, "m")), ] x_and_y = case_description.XAndYDescription(x=Array([1.0, 10.0, 100.0], "m"), y=Array([42.0] * 3, "m")) assert list(x_and_y.iter_values_and_unit()) == [ ((1.0, "m"), (42.0, "m")), ((10.0, "m"), (42.0, "m")), ((100.0, "m"), (42.0, "m")), ]
def test_to_curve(): type_error_msg = r"Expected pair \(image_array, domain_array\) or Curve" assert to_curve(None, is_optional=True) is None with pytest.raises(TypeError, match=type_error_msg): to_curve(None) image = Array("length", [1, 2, 3], "m") domain = Array("time", [0, 1.1, 2.2], "s") curve = Curve(image, domain) assert to_curve((([1, 2, 3], "m"), domain)) == curve with pytest.raises( TypeError, match=r"Curve image: Expected pair \(values, unit\) or Array" ): to_curve((("foo", [1, 2, 3], "m"), domain)) with pytest.raises(TypeError, match=type_error_msg): to_curve(("foo", [1, 2, 3], "m")) with pytest.raises(TypeError, match=type_error_msg): to_curve("foo") assert to_curve(curve) is curve
def build_linear_initial_temperatures_description( from_temperature, to_temperature, unit, final_position, position_unit, start_position=0.0, ): return case_description.InitialTemperaturesDescription( position_input_type=constants.TableInputType.length, table_length=case_description.TemperaturesContainerDescription( positions=Array( [ Scalar(start_position, position_unit).GetValue("m"), Scalar(final_position, position_unit).GetValue("m"), ], "m", ), temperatures=Array([from_temperature, to_temperature], unit), ), )
def test_check_profile_description(default_case): """ Ensures that the ProfileDescription only accepts either XAndYDescription or a LengthAndElevationDescription Check 1: length_and_elevation and x_and_y are mutually exclusive """ msg = ( "length_and_elevation and x_and_y are mutually exclusive and you must configure only one of them, got " "length_and_elevation=LengthAndElevationDescription(length=Array(length, [0.0, 5.0, 7.0], m), elevation=Array(length, [1.0, 1.0, 1.0], m)) " "and x_and_y=XAndYDescription(x=Array(length, [1.0, 10.0, 100.0], m), y=Array(length, [42.0, 42.0, 42.0], m))" ) with pytest.raises(ValueError, match=re.escape(msg)): case_description.ProfileDescription( length_and_elevation=case_description. LengthAndElevationDescription(length=Array([0.0, 5.0, 7.0], "m"), elevation=Array([1.0] * 3, "m")), x_and_y=case_description.XAndYDescription( x=Array([1.0, 10.0, 100.0], "m"), y=Array([42.0] * 3, "m")), ) # Empty Profile is allowed. profile = case_description.ProfileDescription(length_and_elevation=None, x_and_y=None) assert profile.length_and_elevation is None assert profile.x_and_y is None # Empty Array is allowed profile_2 = case_description.ProfileDescription( length_and_elevation=case_description.LengthAndElevationDescription( length=Array([], "m"), elevation=Array([], "m")), x_and_y=None, ) assert profile_2.x_and_y is None assert profile_2.length_and_elevation.length.GetValues("m") == [] assert profile_2.length_and_elevation.elevation.GetValues("m") == []
def test_build_compressor_pressure_table_description_valid(): description = build_compressor_pressure_table_description( speed_entries=Array([0, 1000], "rpm"), corrected_mass_flow_rate_entries=Array([1, 2, 3], "kg/s"), pressure_ratio_table=Array([1, 2, 3, 4, 5, 6], "-"), isentropic_efficiency_table=Array([0.4, 0.5, 0.6, 0.7, 0.8, 0.9], "-"), ) assert description.speed_entries == Array([0, 0, 0, 1000, 1000, 1000], "rpm") assert description.corrected_mass_flow_rate_entries == Array( [1, 2, 3, 1, 2, 3], "kg/s") assert description.pressure_ratio_table == Array([1, 2, 3, 4, 5, 6], "-") assert description.isentropic_efficiency_table == Array( [0.4, 0.5, 0.6, 0.7, 0.8, 0.9], "-")
def test_get_array_loader(): alfacase_content = YAML( value={"foo": {"values": YAML(value=[1, 2]), "unit": YAML(value="m")}} ) description_document = DescriptionDocument( content=alfacase_content, file_path=Path() ) # Loading Scalar passing ``category`` array_loader = get_array_loader(category="length") assert array_loader(key="foo", alfacase_content=description_document) == Array( "length", [1.0, 2.0], "m" ) # Load Scalar passing ``from_unit`` array_loader = get_array_loader(from_unit="m") assert array_loader(key="foo", alfacase_content=description_document) == Array( "length", [1.0, 2.0], "m" ) expected_msg = "Either 'category' or 'from_unit' parameter must be defined" with pytest.raises(ValueError, match=expected_msg): get_array_loader()
def testCurves(unit_database) -> None: import numpy r = ObtainQuantity("m", "length") values10 = Array(r, values=numpy.array(list(range(10)), dtype=numpy.int32)) values9 = Array(r, values=numpy.array(list(range(9)), dtype=numpy.int32)) domain9 = Array("time", values=numpy.array(list(range(9)), dtype=numpy.int32), unit="s") domain10 = Array("time", values=numpy.array(list(range(10)), dtype=numpy.int32), unit="s") with pytest.raises(ValueError): Curve(values10, domain9) # type:ignore[arg-type] c = Curve(values10, domain10) # type:ignore[arg-type] with pytest.raises(ValueError): c.SetDomain(domain9) # type:ignore[arg-type] with pytest.raises(ValueError): c.SetImage(values9) # type:ignore[arg-type]
def testUnitSystemManager(unit_manager, units_mapping_1, units_mapping_2, units_mapping_3): CreateUnitSystemTemplate(unit_manager) unit_manager.AddUnitSystem("system1", "system1", units_mapping_1, False) unit_manager.AddUnitSystem("system2", "system2", units_mapping_2, False) with pytest.raises(UnitSystemIDError): unit_manager.AddUnitSystem("system1", "system3", units_mapping_3, False) with pytest.raises(UnitSystemCategoriesError): unit_manager.AddUnitSystem("system3", "system3", units_mapping_3, False) scalar = Scalar("length", unit="cm") array = Array("length", unit="cm") fixed_array = FixedArray(3, "length", unit="cm") fraction_scalar = FractionScalar("length") unit_systems = unit_manager.GetUnitSystems() system1 = unit_systems["system1"] system2 = unit_systems["system2"] unit_manager.current = system1 assert unit_manager.GetUnitSystemById("system1") is system1 assert unit_manager.GetUnitSystemById("system2") is system2 # make sure the unit system manager is not holding a strong ref to its objects scalar_ref = weakref.ref(scalar) array_ref = weakref.ref(array) fixed_array_ref = weakref.ref(fixed_array) fraction_scalar_ref = weakref.ref(fraction_scalar) del scalar del array del fixed_array del fraction_scalar assert scalar_ref() is None assert array_ref() is None assert fixed_array_ref() is None assert fraction_scalar_ref() is None
def testCurveRepr(unit_database): q1 = ObtainQuantity("m", "length") q2 = ObtainQuantity("d", "time") curve = Curve(Array(q1, []), Array(q2, [])) assert "Curve(m, d)[]" == repr(curve) curve = Curve(Array(q1, list(range(3))), Array(q2, list(range(3)))) assert "Curve(m, d)[(0, 0) (1, 1) (2, 2)]" == repr(curve) curve = Curve(Array(q1, list(range(100))), Array(q2, list(range(100)))) expected = ("Curve(m, d)[(0, 0) (1, 1) (2, 2) (3, 3) (4, 4) (5, 5) " "(6, 6) (7, 7) (8, 8) (9, 9) (10, 10) (11, 11) " "(12, 12) (13, 13) (14, 14) (15, 15) (16, 16) " "(17, 17) (18, 18) (19, 19) (20, 20) ... ]") assert repr(curve) == expected