diff --git a/src/alfasim_score/common.py b/src/alfasim_score/common.py index 3d5f559..d5b0aa4 100644 --- a/src/alfasim_score/common.py +++ b/src/alfasim_score/common.py @@ -1,11 +1,33 @@ from typing import Any from typing import Dict +from barril.curve.curve import Curve from barril.units import Array from barril.units import Scalar from enum import Enum +class WellItemType(str, Enum): + DRILLING = "DRILLING" + CASING = "CASING" + NONE = "NONE" + + +class WellItemFunction(str, Enum): + CONDUCTOR = "CONDUCTOR" + SURFACE = "SURFACE" + PRODUCTION = "PRODUCTION" + OPEN = "OPEN" + + +# TODO PWPA-1983: need more examples of SCORE files to know the label for method +# the method is in the file tree in the path operation/data/method +class LiftMethod(str, Enum): + # NATURAL_FLOW = "???" + # BCS_PUMP = "???" + GAS_LIFT = "GASLIFT" + + def prepare_for_regression(values: Dict[str, Any]) -> Dict[str, Any]: """ "Prepare Scalar and Array to the be used in regression test""" regression_values = {} @@ -20,6 +42,13 @@ def prepare_for_regression(values: Dict[str, Any]) -> Dict[str, Any]: "values": value.values, "unit": value.unit, } + elif isinstance(value, Curve): + regression_values[key] = { + "image_values": value.image.values, + "image_unit": value.image.unit, + "domain_values": value.domain.values, + "domain_unit": value.domain.unit, + } elif isinstance(value, Enum): regression_values[key] = value.value else: diff --git a/src/alfasim_score/constants.py b/src/alfasim_score/constants.py index 2126348..dd40287 100644 --- a/src/alfasim_score/constants.py +++ b/src/alfasim_score/constants.py @@ -1,12 +1,16 @@ from barril.units import Scalar +from alfasim_score.units import MASS_FLOW_RATE_UNIT +from alfasim_score.units import PRESSURE from alfasim_score.units import ROUGHNESS_UNIT +from alfasim_score.units import STD_VOLUMETRIC_FLOW_RATE_UNIT +from alfasim_score.units import TEMPERATURE WELLBORE_NAME = "WELLBORE" -WELLBORE_TOP_NODE = "WELBORE_TOP_NODE" -WELLBORE_BOTTOM_NODE = "WELBORE_BOTTOM_NODE" +WELLBORE_TOP_NODE_NAME = "WELBORE_TOP_NODE" +WELLBORE_BOTTOM_NODE_NAME = "WELBORE_BOTTOM_NODE" ANNULUS_TOP_NODE_NAME = "WELLBORE_ANNULUS_TOP_NODE" - +GAS_LIFT_MASS_NODE_NAME = "GAS_LIFT_MASS_NODE" CEMENT_NAME = "cement" ROCK_DEFAULT_ROUGHNESS = Scalar(0.1, ROUGHNESS_UNIT) @@ -15,3 +19,9 @@ # This default fluid name for packer and fluid above filler FLUID_DEFAULT_NAME = "fluid_default" + +# nodes data +BASE_PVT_TABLE_NAME = "base" +GAS_LIFT_PVT_TABLE_NAME = "gas_lift" +NULL_VOLUMETRIC_FLOW_RATE = Scalar(0.0, STD_VOLUMETRIC_FLOW_RATE_UNIT) +NULL_MASS_FLOW_RATE = Scalar(0.0, MASS_FLOW_RATE_UNIT) diff --git a/src/alfasim_score/converter/alfacase/_tests/test_convert_alfacase/test_create_alfacase.alfacase b/src/alfasim_score/converter/alfacase/_tests/test_convert_alfacase/test_create_alfacase.alfacase index 4368d8b..b1a92f9 100644 --- a/src/alfasim_score/converter/alfacase/_tests/test_convert_alfacase/test_create_alfacase.alfacase +++ b/src/alfasim_score/converter/alfacase/_tests/test_convert_alfacase/test_create_alfacase.alfacase @@ -101,7 +101,270 @@ outputs: value: 0.1 unit: s pipes: [] -nodes: [] +nodes: +- name: WELBORE_TOP_NODE + node_type: mass_source_boundary + pvt_model: base + pressure_properties: + pressure: + value: 100000.0 + unit: Pa + temperature: + value: 288.6 + unit: K + tracer_mass_fraction: + values: [] + unit: '-' + split_type: mass_inflow_split_type_constant_volume_fraction + gas_liquid_ratio: + value: 0.0 + unit: sm3/sm3 + gas_oil_ratio: + value: 0.0 + unit: sm3/sm3 + water_cut: + value: 0.0 + unit: '-' + mass_source_properties: + tracer_mass_fraction: + values: [] + unit: '-' + temperature: + value: 288.6 + unit: K + source_type: mass_source_type_all_volumetric_flow_rates + volumetric_flow_rates_std: + gas: + value: 0.0 + unit: sm3/d + oil: + value: 0.0 + unit: sm3/d + water: + value: 0.0 + unit: sm3/d + total_mass_flow_rate: + value: 1.0 + unit: kg/s + water_cut: + value: 0.0 + unit: '-' + gas_oil_ratio: + value: 0.0 + unit: sm3/sm3 + separator_properties: + environment_temperature: + value: 25.0 + unit: degC + geometry: vertical_cylinder + length: + value: 1.0 + unit: m + overall_heat_transfer_coefficient: + value: 0.0 + unit: W/m2.K + diameter: + value: 1.0 + unit: m + initial_phase_volume_fractions: + gas: + value: 0.5 + unit: '-' + oil: + value: 0.5 + unit: '-' + gas_separation_efficiency: + value: 1.0 + unit: '-' + liquid_separation_efficiency: + value: 1.0 + unit: '-' + controller_properties: + type: pid + gain: 0.0001 + setpoint: 0.0 + integral_time: + value: 10.0 + unit: s + derivative_time: + value: 1.0 + unit: s + output_signal_properties: + min_value: -1e+50 + max_value: 1e+50 + max_rate_of_change: 1e+50 +- name: WELBORE_BOTTOM_NODE + node_type: pressure_boundary + pvt_model: base + pressure_properties: + pressure: + value: 100000.0 + unit: Pa + temperature: + value: 288.6 + unit: K + tracer_mass_fraction: + values: [] + unit: '-' + split_type: mass_inflow_split_type_pvt + gas_liquid_ratio: + value: 0.0 + unit: sm3/sm3 + gas_oil_ratio: + value: 0.0 + unit: sm3/sm3 + water_cut: + value: 0.0 + unit: '-' + mass_source_properties: + tracer_mass_fraction: + values: [] + unit: '-' + temperature: + value: 288.6 + unit: K + source_type: mass_source_type_mass_flow_rates + total_mass_flow_rate: + value: 1.0 + unit: kg/s + water_cut: + value: 0.0 + unit: '-' + gas_oil_ratio: + value: 0.0 + unit: sm3/sm3 + separator_properties: + environment_temperature: + value: 25.0 + unit: degC + geometry: vertical_cylinder + length: + value: 1.0 + unit: m + overall_heat_transfer_coefficient: + value: 0.0 + unit: W/m2.K + diameter: + value: 1.0 + unit: m + initial_phase_volume_fractions: + gas: + value: 0.5 + unit: '-' + oil: + value: 0.5 + unit: '-' + gas_separation_efficiency: + value: 1.0 + unit: '-' + liquid_separation_efficiency: + value: 1.0 + unit: '-' + controller_properties: + type: pid + gain: 0.0001 + setpoint: 0.0 + integral_time: + value: 10.0 + unit: s + derivative_time: + value: 1.0 + unit: s + output_signal_properties: + min_value: -1e+50 + max_value: 1e+50 + max_rate_of_change: 1e+50 +- name: GAS_LIFT_MASS_NODE + node_type: mass_source_boundary + pvt_model: gas_lift + pressure_properties: + pressure: + value: 100000.0 + unit: Pa + temperature: + value: 288.6 + unit: K + tracer_mass_fraction: + values: [] + unit: '-' + split_type: mass_inflow_split_type_constant_volume_fraction + gas_liquid_ratio: + value: 0.0 + unit: sm3/sm3 + gas_oil_ratio: + value: 0.0 + unit: sm3/sm3 + water_cut: + value: 0.0 + unit: '-' + mass_source_properties: + tracer_mass_fraction: + values: [] + unit: '-' + temperature: + value: 288.6 + unit: K + source_type: mass_source_type_all_volumetric_flow_rates + volumetric_flow_rates_std: + gas: + value: 0.0 + unit: sm3/d + oil: + value: 0.0 + unit: sm3/d + water: + value: 0.0 + unit: sm3/d + total_mass_flow_rate: + value: 1.0 + unit: kg/s + water_cut: + value: 0.0 + unit: '-' + gas_oil_ratio: + value: 0.0 + unit: sm3/sm3 + separator_properties: + environment_temperature: + value: 25.0 + unit: degC + geometry: vertical_cylinder + length: + value: 1.0 + unit: m + overall_heat_transfer_coefficient: + value: 0.0 + unit: W/m2.K + diameter: + value: 1.0 + unit: m + initial_phase_volume_fractions: + gas: + value: 0.5 + unit: '-' + oil: + value: 0.5 + unit: '-' + gas_separation_efficiency: + value: 1.0 + unit: '-' + liquid_separation_efficiency: + value: 1.0 + unit: '-' + controller_properties: + type: pid + gain: 0.0001 + setpoint: 0.0 + integral_time: + value: 10.0 + unit: s + derivative_time: + value: 1.0 + unit: s + output_signal_properties: + min_value: -1e+50 + max_value: 1e+50 + max_rate_of_change: 1e+50 wells: - name: WELLBORE profile: diff --git a/src/alfasim_score/converter/alfacase/_tests/test_convert_nodes.py b/src/alfasim_score/converter/alfacase/_tests/test_convert_nodes.py new file mode 100644 index 0000000..efa0a85 --- /dev/null +++ b/src/alfasim_score/converter/alfacase/_tests/test_convert_nodes.py @@ -0,0 +1,21 @@ +import attr +from alfasim_sdk import NodeCellType +from pytest_regressions.data_regression import DataRegressionFixture + +from alfasim_score.common import prepare_for_regression +from alfasim_score.converter.alfacase.convert_alfacase import ScoreAlfacaseConverter +from alfasim_score.converter.alfacase.score_input_reader import ScoreInputReader + + +def test_convert_nodes( + data_regression: DataRegressionFixture, + score_input_example: ScoreInputReader, +) -> None: + builder = ScoreAlfacaseConverter(score_input_example) + nodes = builder.build_nodes() + data_regression.check( + [ + {"name": node.name, "type": node.node_type.value, "pvt_model": node.pvt_model} + for node in nodes + ] + ) diff --git a/src/alfasim_score/converter/alfacase/_tests/test_convert_nodes/test_convert_nodes.yml b/src/alfasim_score/converter/alfacase/_tests/test_convert_nodes/test_convert_nodes.yml new file mode 100644 index 0000000..9a893e4 --- /dev/null +++ b/src/alfasim_score/converter/alfacase/_tests/test_convert_nodes/test_convert_nodes.yml @@ -0,0 +1,9 @@ +- name: WELBORE_TOP_NODE + pvt_model: base + type: mass_source_boundary +- name: WELBORE_BOTTOM_NODE + pvt_model: base + type: pressure_boundary +- name: GAS_LIFT_MASS_NODE + pvt_model: gas_lift + type: mass_source_boundary diff --git a/src/alfasim_score/converter/alfacase/convert_alfacase.py b/src/alfasim_score/converter/alfacase/convert_alfacase.py index 457f8da..60f8f60 100644 --- a/src/alfasim_score/converter/alfacase/convert_alfacase.py +++ b/src/alfasim_score/converter/alfacase/convert_alfacase.py @@ -6,25 +6,37 @@ from alfasim_sdk import CasingSectionDescription from alfasim_sdk import FormationDescription from alfasim_sdk import FormationLayerDescription +from alfasim_sdk import MassInflowSplitType +from alfasim_sdk import MassSourceNodePropertiesDescription +from alfasim_sdk import MassSourceType from alfasim_sdk import MaterialDescription from alfasim_sdk import MaterialType +from alfasim_sdk import MultiInputType +from alfasim_sdk import NodeCellType +from alfasim_sdk import NodeDescription from alfasim_sdk import OpenHoleDescription from alfasim_sdk import PackerDescription +from alfasim_sdk import PressureNodePropertiesDescription from alfasim_sdk import ProfileDescription from alfasim_sdk import TubingDescription from alfasim_sdk import WellDescription from alfasim_sdk import XAndYDescription from barril.units import Scalar +from alfasim_score.common import LiftMethod from alfasim_score.constants import ANNULUS_TOP_NODE_NAME +from alfasim_score.constants import BASE_PVT_TABLE_NAME from alfasim_score.constants import CASING_DEFAULT_ROUGHNESS from alfasim_score.constants import CEMENT_NAME from alfasim_score.constants import FLUID_DEFAULT_NAME +from alfasim_score.constants import GAS_LIFT_MASS_NODE_NAME +from alfasim_score.constants import GAS_LIFT_PVT_TABLE_NAME +from alfasim_score.constants import NULL_VOLUMETRIC_FLOW_RATE from alfasim_score.constants import ROCK_DEFAULT_ROUGHNESS from alfasim_score.constants import TUBING_DEFAULT_ROUGHNESS -from alfasim_score.constants import WELLBORE_BOTTOM_NODE +from alfasim_score.constants import WELLBORE_BOTTOM_NODE_NAME from alfasim_score.constants import WELLBORE_NAME -from alfasim_score.constants import WELLBORE_TOP_NODE +from alfasim_score.constants import WELLBORE_TOP_NODE_NAME from alfasim_score.converter.alfacase.score_input_reader import ScoreInputReader from alfasim_score.units import LENGTH_UNIT @@ -75,8 +87,10 @@ def convert_materials(self) -> List[MaterialDescription]: ) return filter_duplicated_materials(material_descriptions) - # TODO PWPA-1937: implement this method def _convert_annulus(self) -> AnnulusDescription: + # TODO PWPA-1937: implement this method + # TODO PWPA-1937: Use the GAS_LIFT_MASS_NODE, check for the gas lift presence + # and set flow rate zero with the flag false for annulus flow. return AnnulusDescription(has_annulus_flow=False, top_node=ANNULUS_TOP_NODE_NAME) def _convert_formation(self) -> FormationDescription: @@ -176,6 +190,52 @@ def _convert_casings(self) -> CasingDescription: open_holes=self._convert_open_hole_list(), ) + def build_nodes(self) -> List[NodeDescription]: + """Create the description for the node list.""" + nodes = [ + NodeDescription( + name=WELLBORE_TOP_NODE_NAME, + node_type=NodeCellType.MassSource, + pvt_model=BASE_PVT_TABLE_NAME, + mass_source_properties=MassSourceNodePropertiesDescription( + temperature_input_type=MultiInputType.Constant, + source_type=MassSourceType.AllVolumetricFlowRates, + volumetric_flow_rates_std={ + "gas": NULL_VOLUMETRIC_FLOW_RATE, + "oil": NULL_VOLUMETRIC_FLOW_RATE, + "water": NULL_VOLUMETRIC_FLOW_RATE, + }, + ), + ), + NodeDescription( + name=WELLBORE_BOTTOM_NODE_NAME, + node_type=NodeCellType.Pressure, + pvt_model=BASE_PVT_TABLE_NAME, + pressure_properties=PressureNodePropertiesDescription( + split_type=MassInflowSplitType.Pvt, + ), + ), + ] + operation_input_data = self.score_input.read_operation_data() + if operation_input_data["lift_method"] == LiftMethod.GAS_LIFT: + nodes.append( + NodeDescription( + name=GAS_LIFT_MASS_NODE_NAME, + node_type=NodeCellType.MassSource, + pvt_model=GAS_LIFT_PVT_TABLE_NAME, + mass_source_properties=MassSourceNodePropertiesDescription( + temperature_input_type=MultiInputType.Constant, + source_type=MassSourceType.AllVolumetricFlowRates, + volumetric_flow_rates_std={ + "gas": NULL_VOLUMETRIC_FLOW_RATE, + "oil": NULL_VOLUMETRIC_FLOW_RATE, + "water": NULL_VOLUMETRIC_FLOW_RATE, + }, + ), + ) + ) + return nodes + def build_well(self) -> WellDescription: """Create the description for the well.""" return WellDescription( @@ -184,13 +244,14 @@ def build_well(self) -> WellDescription: casing=self._convert_casings(), annulus=self._convert_annulus(), formation=self._convert_formation(), - top_node=WELLBORE_TOP_NODE, - bottom_node=WELLBORE_BOTTOM_NODE, + top_node=WELLBORE_TOP_NODE_NAME, + bottom_node=WELLBORE_BOTTOM_NODE_NAME, ) def build_case_description(self) -> CaseDescription: return CaseDescription( name=self.case_name, + nodes=self.build_nodes(), wells=[self.build_well()], materials=self.convert_materials(), ) diff --git a/src/alfasim_score/converter/alfacase/score_input_reader.py b/src/alfasim_score/converter/alfacase/score_input_reader.py index 8141825..955eb27 100644 --- a/src/alfasim_score/converter/alfacase/score_input_reader.py +++ b/src/alfasim_score/converter/alfacase/score_input_reader.py @@ -7,9 +7,11 @@ import json from barril.units import Array from barril.units import Scalar -from enum import Enum from pathlib import Path +from alfasim_score.common import LiftMethod +from alfasim_score.common import WellItemFunction +from alfasim_score.common import WellItemType from alfasim_score.constants import CEMENT_NAME from alfasim_score.constants import FLUID_DEFAULT_NAME from alfasim_score.units import DENSITY_UNIT @@ -22,19 +24,6 @@ from alfasim_score.units import YOUNG_MODULUS_UNIT -class WellItemType(str, Enum): - DRILLING = "DRILLING" - CASING = "CASING" - NONE = "NONE" - - -class WellItemFunction(str, Enum): - CONDUCTOR = "CONDUCTOR" - SURFACE = "SURFACE" - PRODUCTION = "PRODUCTION" - OPEN = "OPEN" - - class ScoreInputReader: def __init__(self, score_filepath: Path): self.score_filepath = score_filepath @@ -244,3 +233,7 @@ def read_formations(self) -> List[Dict[str, Scalar]]: } for lithology in self.input_content["lithologies"] ] + + def read_operation_data(self) -> Dict[str, Any]: + """Read data for operation registered in SCORE input file.""" + return {"lift_method": LiftMethod(self.input_content["operation"]["data"]["method"])} diff --git a/src/alfasim_score/units.py b/src/alfasim_score/units.py index 300d04c..51f0a68 100644 --- a/src/alfasim_score/units.py +++ b/src/alfasim_score/units.py @@ -8,3 +8,7 @@ YOUNG_MODULUS_UNIT = "psi" FRACTION_UNIT = "-" ROUGHNESS_UNIT = "mm" +STD_VOLUMETRIC_FLOW_RATE_UNIT = "sm3/d" +MASS_FLOW_RATE_UNIT = "kg/s" +PRESSURE = "kgf/cm2" +TEMPERATURE = "degC"