From 89de2087467db4c923fd4f89853fa22204cf98e4 Mon Sep 17 00:00:00 2001 From: Jostein Solaas Date: Mon, 11 Sep 2023 19:45:33 +0200 Subject: [PATCH] fix: expression type in system v2 --- .../common/libecalc/expression/expression.py | 8 +- .../input/yaml_types/components/base.py | 9 +- .../components/compressor_system.py | 9 +- .../yaml_types/components/pump_system.py | 12 +- .../test_json_schema_changed/schemas.json | 276 +++++++++++++++--- 5 files changed, 265 insertions(+), 49 deletions(-) diff --git a/src/ecalc/libraries/libecalc/common/libecalc/expression/expression.py b/src/ecalc/libraries/libecalc/common/libecalc/expression/expression.py index 718c2a63ba..51289e7c22 100644 --- a/src/ecalc/libraries/libecalc/common/libecalc/expression/expression.py +++ b/src/ecalc/libraries/libecalc/common/libecalc/expression/expression.py @@ -17,6 +17,8 @@ RIGHT_PARENTHESIS_TOKEN = Token(tag=TokenTag.operator, value=Operators.right_parenthesis.value) MULTIPLICATION_TOKEN = Token(tag=TokenTag.operator, value=Operators.multiply.value) +ExpressionType = Union[str, float, int] + class Expression: def __init__( @@ -28,7 +30,7 @@ def __init__( @classmethod def setup_from_expression( cls, - value: Union[str, float, int], + value: ExpressionType, ) -> Expression: tokens = cls.validate(value) return cls(tokens=tokens) @@ -62,7 +64,7 @@ def multiply(cls, expression1: Expression, expression2: Expression) -> Expressio return cls(tokens=tokens_multiplied) @classmethod - def validate(cls, expression: Union[str, float, int]) -> List[Token]: + def validate(cls, expression: ExpressionType) -> List[Token]: expression = _expression_as_number_if_number(expression_input=expression) if not isinstance(expression, (str, float, int)): @@ -123,7 +125,7 @@ def __get_validators__(cls): yield cls.validator -def _expression_as_number_if_number(expression_input: Union[str, float, int]) -> Union[str, float, int]: +def _expression_as_number_if_number(expression_input: ExpressionType) -> ExpressionType: """Expressions may be either pure numbers, booleans or strings which define a combination of numbers, operators and references as a string. If very small numbers are parsed and represented in scientific notation, the expression parsing will wrongfully treat these as expressions with references/operators instead of pure numeric values. Thus, diff --git a/src/ecalc/libraries/libecalc/common/libecalc/input/yaml_types/components/base.py b/src/ecalc/libraries/libecalc/common/libecalc/input/yaml_types/components/base.py index 7360b1a38e..853a2f6eee 100644 --- a/src/ecalc/libraries/libecalc/common/libecalc/input/yaml_types/components/base.py +++ b/src/ecalc/libraries/libecalc/common/libecalc/input/yaml_types/components/base.py @@ -1,5 +1,6 @@ from typing import List, Optional +from libecalc.expression.expression import ExpressionType from libecalc.input.yaml_types import YamlBase from pydantic import Field @@ -21,7 +22,7 @@ def to_dto(self, **kwargs): class OperationalConditionBase(YamlBase): - condition: Optional[str] = Field( + condition: Optional[ExpressionType] = Field( None, title="Condition", description=""" @@ -32,7 +33,7 @@ class OperationalConditionBase(YamlBase): but is required to evaluate to True/False or 1/0.\n """, ) - power_loss_factor: Optional[str] = Field( + power_loss_factor: Optional[ExpressionType] = Field( None, title="Power loss factor", alias="POWERLOSSFACTOR", # Legacy support @@ -49,7 +50,7 @@ def to_dto(self): class ConsumerSystemOperationalConditionBase(OperationalConditionBase): - conditions: Optional[str] = Field( + conditions: Optional[ExpressionType] = Field( None, title="Conditions", description=""" @@ -60,7 +61,7 @@ class ConsumerSystemOperationalConditionBase(OperationalConditionBase): but is required to evaluate to True/False or 1/0.\n """, ) - power_loss_factors: Optional[List[str]] = Field( + power_loss_factors: Optional[List[ExpressionType]] = Field( None, title="Power loss factors", alias="POWERLOSSFACTORS", # Legacy support diff --git a/src/ecalc/libraries/libecalc/common/libecalc/input/yaml_types/components/compressor_system.py b/src/ecalc/libraries/libecalc/common/libecalc/input/yaml_types/components/compressor_system.py index 38278fa7ec..007c27719c 100644 --- a/src/ecalc/libraries/libecalc/common/libecalc/input/yaml_types/components/compressor_system.py +++ b/src/ecalc/libraries/libecalc/common/libecalc/input/yaml_types/components/compressor_system.py @@ -6,6 +6,7 @@ from libecalc.dto.base import ComponentType from libecalc.dto.types import ConsumptionType from libecalc.expression import Expression +from libecalc.expression.expression import ExpressionType from libecalc.input.mappers.utils import resolve_and_validate_reference from libecalc.input.yaml_entities import References from libecalc.input.yaml_types.components.base import ( @@ -16,11 +17,11 @@ from libecalc.input.yaml_types.temporal_model import TemporalModel from pydantic import Field, confloat, root_validator, validator -opt_expr_list = Optional[List[str]] +opt_expr_list = Optional[List[ExpressionType]] class OperationalSettings(ConsumerSystemOperationalConditionBase): - total_system_rate: Optional[str] = Field( + total_system_rate: Optional[ExpressionType] = Field( None, title="Total system rate", description="The total system rate expression." @@ -38,7 +39,7 @@ class OperationalSettings(ConsumerSystemOperationalConditionBase): title="Rates", description="Rates [Sm3/day] as a list of expressions" "\n\nThis is mutually exclusive with RATE_FRACTIONS.", ) - inlet_pressure: Optional[str] = Field( + inlet_pressure: Optional[ExpressionType] = Field( None, title="Inlet pressure", description="Inlet pressure [bara] as a single expression" @@ -47,7 +48,7 @@ class OperationalSettings(ConsumerSystemOperationalConditionBase): inlet_pressures: opt_expr_list = Field( None, title="Inlet pressures", description="Inlet pressures [bara] as a list of expressions." ) - outlet_pressure: Optional[str] = Field( + outlet_pressure: Optional[ExpressionType] = Field( None, title="Outlet pressure", description="Outlet pressure [bara] as a single expression" diff --git a/src/ecalc/libraries/libecalc/common/libecalc/input/yaml_types/components/pump_system.py b/src/ecalc/libraries/libecalc/common/libecalc/input/yaml_types/components/pump_system.py index ffe6fdfee6..0f2ddafcce 100644 --- a/src/ecalc/libraries/libecalc/common/libecalc/input/yaml_types/components/pump_system.py +++ b/src/ecalc/libraries/libecalc/common/libecalc/input/yaml_types/components/pump_system.py @@ -6,6 +6,7 @@ from libecalc.dto.base import ComponentType from libecalc.dto.types import ConsumptionType from libecalc.expression import Expression +from libecalc.expression.expression import ExpressionType from libecalc.input.mappers.utils import resolve_and_validate_reference from libecalc.input.yaml_entities import References from libecalc.input.yaml_types.components.base import ( @@ -16,7 +17,7 @@ from libecalc.input.yaml_types.temporal_model import TemporalModel from pydantic import Field, confloat, root_validator, validator -opt_expr_list = Optional[List[str]] +opt_expr_list = Optional[List[ExpressionType]] class OperationalSettings(ConsumerSystemOperationalConditionBase): @@ -38,7 +39,7 @@ class OperationalSettings(ConsumerSystemOperationalConditionBase): title="Rates", description="Rates [Sm3/day] as a list of expressions" "\n\nThis is mutually exclusive with RATE_FRACTIONS.", ) - inlet_pressure: Optional[str] = Field( + inlet_pressure: Optional[ExpressionType] = Field( None, title="Inlet pressure", description="Inlet pressure [bara] as a single expression" @@ -47,7 +48,7 @@ class OperationalSettings(ConsumerSystemOperationalConditionBase): inlet_pressures: opt_expr_list = Field( None, title="Inlet pressures", description="Inlet pressures [bara] as a list of expressions." ) - outlet_pressure: Optional[str] = Field( + outlet_pressure: Optional[ExpressionType] = Field( None, title="Outlet pressure", description="Outlet pressure [bara] as a single expression" @@ -56,7 +57,7 @@ class OperationalSettings(ConsumerSystemOperationalConditionBase): outlet_pressures: opt_expr_list = Field( None, title="Outlet pressures", description="Outlet pressures [bara] as a list of expressions." ) - fluid_density: Optional[str] = Field( + fluid_density: Optional[ExpressionType] = Field( None, title="FluidStream density", description="The fluid density [kg/m3] as a single expression." ) fluid_densities: opt_expr_list = Field( @@ -204,7 +205,8 @@ def to_dto( else: rates = [ Expression.multiply( - Expression.setup_from_expression(self.rate), Expression.setup_from_expression(rate_fraction) + Expression.setup_from_expression(operational_setting.total_system_rate), + Expression.setup_from_expression(rate_fraction), ) for rate_fraction in operational_setting.rate_fractions ] diff --git a/src/ecalc/libraries/libecalc/common/tests/input/validation/snapshots/test_validation_json_schemas/test_json_schema_changed/schemas.json b/src/ecalc/libraries/libecalc/common/tests/input/validation/snapshots/test_validation_json_schemas/test_json_schema_changed/schemas.json index 05d41026ab..9ab0e431e0 100644 --- a/src/ecalc/libraries/libecalc/common/tests/input/validation/snapshots/test_validation_json_schemas/test_json_schema_changed/schemas.json +++ b/src/ecalc/libraries/libecalc/common/tests/input/validation/snapshots/test_validation_json_schemas/test_json_schema_changed/schemas.json @@ -684,14 +684,34 @@ "additionalProperties": false, "properties": { "CONDITION": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ], "description": "\nAll consumers may have a keyword CONDITION which specifies conditions for the consumer to be used. \n\nAt points in the time series where the condition evaluates to 0 (or False), the energy consumption will be 0. \n\nThis is practical for some otherwise constant consumers, for example, fixed production loads, which have a constant \n\nload whenever there is production. CONDITION supports the functionality described in Expressions, \n\nbut is required to evaluate to True/False or 1/0.\n\n", - "title": "Condition", - "type": "string" + "title": "Condition" }, "CONDITIONS": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ], "description": "\n A consumer system my have the keywords CONDITIONS which specifies conditions for the consumers to be used. \n\n At points in the time series where the condition evaluates to 0 (or False), the energy consumption will be 0. \n\n This is practical for some otherwise constant consumers, for example, fixed production loads, which have a constant \n\n load whenever there is production. CONDITIONS supports the functionality described in Expressions, \n\n but is required to evaluate to True/False or 1/0.\n\n ", - "title": "Conditions", - "type": "string" + "title": "Conditions" }, "CROSSOVER": { "description": "CROSSOVER specifies if rates are to be crossed over to another consumer if rate capacity is exceeded. If the energy consumption calculation is not successful for a consumer, and the consumer has a valid cross-over defined, the consumer will be allocated its maximum rate and the exceeding rate will be added to the cross-over consumer.\nTo avoid loops, a consumer can only be either receiving or giving away rate. For a cross-over to be valid, the discharge pressure at the consumer \"receiving\" overshooting rate must be higher than or equal to the discharge pressure of the \"sending\" consumer. This is because it is possible to choke pressure down to meet the outlet pressure in a flow line with lower pressure, but not possible to \"pressure up\" in the crossover flow line.\nSome examples show how the crossover logic works:\nCrossover is given as and list of integer values for the first position is the first consumer, second position is the second consumer, etc. The number specifies which consumer to send cross-over flow to, and 0 signifies no cross-over possible. Note that we use 1-index here.\nExample 1:\nTwo consumers where there is a cross-over such that if the rate for the first consumer exceeds its capacity, the excess rate will be processed by the second consumer. The second consumer can not cross-over to anyone.\nCROSSOVER: [2, 0]\nExample 2:\nThe first and second consumers may both send exceeding rate to the third consumer if their capacity is exceeded.\nCROSSOVER: [3,3,0]", @@ -702,40 +722,100 @@ "type": "array" }, "INLET_PRESSURE": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ], "description": "Inlet pressure [bara] as a single expression This inlet pressure will be the same for all components in the consumer system.", - "title": "Inlet pressure", - "type": "string" + "title": "Inlet pressure" }, "INLET_PRESSURES": { "description": "Inlet pressures [bara] as a list of expressions.", "items": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ] }, "title": "Inlet pressures", "type": "array" }, "OUTLET_PRESSURE": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ], "description": "Outlet pressure [bara] as a single expression This inlet pressure will be the same for all components in the consumer system.", - "title": "Outlet pressure", - "type": "string" + "title": "Outlet pressure" }, "OUTLET_PRESSURES": { "description": "Outlet pressures [bara] as a list of expressions.", "items": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ] }, "title": "Outlet pressures", "type": "array" }, "POWERLOSSFACTOR": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ], "description": "\nA factor that may be added to account for power transmission losses. E.g. if you have a subsea installation with a \n\npower line to another installation, there may be line losses. For a power line loss of 5%, POWER_LOSS_FACTOR is set to \n\n0.05 and the power required from the power source (generator set) will be:\n\n\npower_requirement = power_before_loss / (1 - power_loss_factor)\n", - "title": "Power loss factor", - "type": "string" + "title": "Power loss factor" }, "POWERLOSSFACTORS": { "description": "\n A consumer system may have list of POWER_LOSS_FACTOR that may be added to account for power transmission losses.\n E.g. if you have a subsea installation with a power line to another installation, there may be line losses. \n\n For a power line loss of 5%, POWER_LOSS_FACTOR is set to 0.05 and the power required from the power source \n\n (generator set) will be:\n\n\n power_requirement = power_before_loss / (1 - power_loss_factor)\n ", "items": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ] }, "title": "Power loss factors", "type": "array" @@ -743,7 +823,17 @@ "RATES": { "description": "Rates [Sm3/day] as a list of expressions\n\nThis is mutually exclusive with RATE_FRACTIONS.", "items": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ] }, "title": "Rates", "type": "array" @@ -759,9 +849,19 @@ "type": "array" }, "TOTAL_SYSTEM_RATE": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ], "description": "The total system rate expression.\n\nShould be used with RATE_FRACTIONS in OPERATIONAL_SETTINGS.", - "title": "Total system rate", - "type": "string" + "title": "Total system rate" } }, "title": "OperationalSettings", @@ -771,14 +871,34 @@ "additionalProperties": false, "properties": { "CONDITION": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ], "description": "\nAll consumers may have a keyword CONDITION which specifies conditions for the consumer to be used. \n\nAt points in the time series where the condition evaluates to 0 (or False), the energy consumption will be 0. \n\nThis is practical for some otherwise constant consumers, for example, fixed production loads, which have a constant \n\nload whenever there is production. CONDITION supports the functionality described in Expressions, \n\nbut is required to evaluate to True/False or 1/0.\n\n", - "title": "Condition", - "type": "string" + "title": "Condition" }, "CONDITIONS": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ], "description": "\n A consumer system my have the keywords CONDITIONS which specifies conditions for the consumers to be used. \n\n At points in the time series where the condition evaluates to 0 (or False), the energy consumption will be 0. \n\n This is practical for some otherwise constant consumers, for example, fixed production loads, which have a constant \n\n load whenever there is production. CONDITIONS supports the functionality described in Expressions, \n\n but is required to evaluate to True/False or 1/0.\n\n ", - "title": "Conditions", - "type": "string" + "title": "Conditions" }, "CROSSOVER": { "description": "CROSSOVER specifies if rates are to be crossed over to another consumer if rate capacity is exceeded. If the energy consumption calculation is not successful for a consumer, and the consumer has a valid cross-over defined, the consumer will be allocated its maximum rate and the exceeding rate will be added to the cross-over consumer.\nTo avoid loops, a consumer can only be either receiving or giving away rate. For a cross-over to be valid, the discharge pressure at the consumer \"receiving\" overshooting rate must be higher than or equal to the discharge pressure of the \"sending\" consumer. This is because it is possible to choke pressure down to meet the outlet pressure in a flow line with lower pressure, but not possible to \"pressure up\" in the crossover flow line.\nSome examples show how the crossover logic works:\nCrossover is given as and list of integer values for the first position is the first consumer, second position is the second consumer, etc. The number specifies which consumer to send cross-over flow to, and 0 signifies no cross-over possible. Note that we use 1-index here.\nExample 1:\nTwo consumers where there is a cross-over such that if the rate for the first consumer exceeds its capacity, the excess rate will be processed by the second consumer. The second consumer can not cross-over to anyone.\nCROSSOVER: [2, 0]\nExample 2:\nThe first and second consumers may both send exceeding rate to the third consumer if their capacity is exceeded.\nCROSSOVER: [3,3,0]", @@ -791,51 +911,131 @@ "FLUID_DENSITIES": { "description": "The fluid density [kg/m3] as a list of expressions.", "items": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ] }, "title": "FluidStream densities", "type": "array" }, "FLUID_DENSITY": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ], "description": "The fluid density [kg/m3] as a single expression.", - "title": "FluidStream density", - "type": "string" + "title": "FluidStream density" }, "INLET_PRESSURE": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ], "description": "Inlet pressure [bara] as a single expression This inlet pressure will be the same for all components in the consumer system.", - "title": "Inlet pressure", - "type": "string" + "title": "Inlet pressure" }, "INLET_PRESSURES": { "description": "Inlet pressures [bara] as a list of expressions.", "items": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ] }, "title": "Inlet pressures", "type": "array" }, "OUTLET_PRESSURE": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ], "description": "Outlet pressure [bara] as a single expression This inlet pressure will be the same for all components in the consumer system.", - "title": "Outlet pressure", - "type": "string" + "title": "Outlet pressure" }, "OUTLET_PRESSURES": { "description": "Outlet pressures [bara] as a list of expressions.", "items": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ] }, "title": "Outlet pressures", "type": "array" }, "POWERLOSSFACTOR": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ], "description": "\nA factor that may be added to account for power transmission losses. E.g. if you have a subsea installation with a \n\npower line to another installation, there may be line losses. For a power line loss of 5%, POWER_LOSS_FACTOR is set to \n\n0.05 and the power required from the power source (generator set) will be:\n\n\npower_requirement = power_before_loss / (1 - power_loss_factor)\n", - "title": "Power loss factor", - "type": "string" + "title": "Power loss factor" }, "POWERLOSSFACTORS": { "description": "\n A consumer system may have list of POWER_LOSS_FACTOR that may be added to account for power transmission losses.\n E.g. if you have a subsea installation with a power line to another installation, there may be line losses. \n\n For a power line loss of 5%, POWER_LOSS_FACTOR is set to 0.05 and the power required from the power source \n\n (generator set) will be:\n\n\n power_requirement = power_before_loss / (1 - power_loss_factor)\n ", "items": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ] }, "title": "Power loss factors", "type": "array" @@ -843,7 +1043,17 @@ "RATES": { "description": "Rates [Sm3/day] as a list of expressions\n\nThis is mutually exclusive with RATE_FRACTIONS.", "items": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + } + ] }, "title": "Rates", "type": "array"