From 70632c723b00546b5975de47ce65a296849ab955 Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Tue, 2 Sep 2025 16:08:51 -0600 Subject: [PATCH 1/6] chore(style): run `ruff check --fix opgee/ tests/` --- opgee/bin/stream_contents.py | 1 - opgee/built_ins/gensim_plugin.py | 1 - opgee/built_ins/gui_plugin.py | 6 +++--- opgee/built_ins/run_plugin.py | 3 +-- opgee/core.py | 2 +- opgee/field.py | 3 +-- opgee/manager.py | 4 ++-- opgee/mcs/distributed_mcs_dask.py | 2 +- opgee/mcs/simulation.py | 2 +- opgee/model.py | 1 - opgee/model_file.py | 4 ++-- opgee/process.py | 6 +++--- opgee/processes/__init__.py | 1 - opgee/processes/acid_gas_removal.py | 2 +- opgee/processes/demethanizer.py | 4 ++-- opgee/processes/steam_generation.py | 2 +- opgee/stream.py | 1 - opgee/tool.py | 4 ++-- opgee/utils.py | 2 +- tests/test_audit.py | 7 +------ tests/test_coeff.py | 1 - tests/test_core.py | 2 +- tests/test_csv2xml.py | 2 +- tests/test_field_groups.py | 1 - tests/test_graph.py | 2 +- tests/test_impute.py | 1 - tests/test_opgee_xml.py | 1 - tests/test_post_proc_plugin.py | 3 +-- tests/test_shared.py | 1 - tests/test_stream.py | 2 +- tests/test_utils.py | 8 ++++---- 31 files changed, 32 insertions(+), 50 deletions(-) diff --git a/opgee/bin/stream_contents.py b/opgee/bin/stream_contents.py index 57beaf28..fe7c19ef 100755 --- a/opgee/bin/stream_contents.py +++ b/opgee/bin/stream_contents.py @@ -2,7 +2,6 @@ # # Read opgee.xml and write out a sorted list of unique stream content names # -from lxml import etree as ET from opgee.config import getParam from opgee.pkg_utils import resourceStream diff --git a/opgee/built_ins/gensim_plugin.py b/opgee/built_ins/gensim_plugin.py index c731b337..affa9359 100644 --- a/opgee/built_ins/gensim_plugin.py +++ b/opgee/built_ins/gensim_plugin.py @@ -60,7 +60,6 @@ def addArgs(self, parser): def run(self, args, tool): from ..error import McsUserError, CommandlineError from ..mcs.simulation import Simulation, read_distributions - from ..mcs.parameter_list import ParameterList use_default_model = not args.no_default_model model_files = args.model_file diff --git a/opgee/built_ins/gui_plugin.py b/opgee/built_ins/gui_plugin.py index aad24026..d41cb264 100644 --- a/opgee/built_ins/gui_plugin.py +++ b/opgee/built_ins/gui_plugin.py @@ -28,7 +28,7 @@ def addArgs(self, parser): help=f'''The field to display. Default (for testing) is "{DFLT_FIELD}"''') parser.add_argument('-m', '--model-file', default=None, - help=f'''The OPGEE model XML file to read. By default it is merged with the built-in + help='''The OPGEE model XML file to read. By default it is merged with the built-in model file, "etc/opgee.xml". If no model file is specified, etc/opgee.xml is read. Use --no-default-model to avoid reading the default model file.''') @@ -37,10 +37,10 @@ def addArgs(self, parser): # TBD: apparently action=argparse.BooleanOptionalAction requires py 3.9 parser.add_argument('--add-stream-components', action='store_true', - help=f'''Include additional stream components listed in config variable "OPGEE.StreamComponents"''') + help='''Include additional stream components listed in config variable "OPGEE.StreamComponents"''') parser.add_argument('--use-class-path', action='store_true', - help=f'''Search for Process subclasses in Python files found in the path(s) listed in config variable "OPGEE.ClassPath"''') + help='''Search for Process subclasses in Python files found in the path(s) listed in config variable "OPGEE.ClassPath"''') return parser diff --git a/opgee/built_ins/run_plugin.py b/opgee/built_ins/run_plugin.py index cad631f0..479e59d6 100644 --- a/opgee/built_ins/run_plugin.py +++ b/opgee/built_ins/run_plugin.py @@ -6,7 +6,7 @@ """ from ..subcommand import SubcommandABC from opgee.constants import SIMPLE_RESULT -from ..log import getLogger, setLogFile +from ..log import getLogger _logger = getLogger(__name__) @@ -229,7 +229,6 @@ def run(self, args, tool): from ..manager import Manager, save_results, TrialPacket, FieldPacket from ..utils import parseTrialString, mkdirs from ..mcs.simulation import Simulation, model_file_path - from ..post_processor import PostProcessor analysis_names = args.analyses or [] batch_size = args.batch_size diff --git a/opgee/core.py b/opgee/core.py index e6de912f..b6edae5f 100644 --- a/opgee/core.py +++ b/opgee/core.py @@ -80,7 +80,7 @@ def split_attr_name(attr_name): count = len(splits) if count == 0: - raise OpgeeException(f"Attribute name is empty") + raise OpgeeException("Attribute name is empty") if count == 1: class_name, attr_name = None, splits[0] diff --git a/opgee/field.py b/opgee/field.py index 8a21ce43..4210cdcf 100644 --- a/opgee/field.py +++ b/opgee/field.py @@ -1248,7 +1248,6 @@ def run_procs_in_order(processes): # Walk the cycle, starting at the indicated start process to generate an ordered list unvisited = procs_in_cycles.copy() start_proc = start_procs[0] - import opgee # TBD: what is this doing here? if any(isinstance(obj, Reservoir) for obj in unvisited): for obj in unvisited: @@ -1574,7 +1573,7 @@ def visit(process): for stream in process.outputs: debug(f" * {stream}") dst = stream.dst_proc - if not dst in visited: + if dst not in visited: next.append(dst) for proc in next: diff --git a/opgee/manager.py b/opgee/manager.py index bcc05467..2a698793 100644 --- a/opgee/manager.py +++ b/opgee/manager.py @@ -9,7 +9,7 @@ import asyncio import dask from dask_jobqueue import SLURMCluster -from dask.distributed import Client, SubprocessCluster, as_completed, TimeoutError +from dask.distributed import Client, SubprocessCluster, as_completed from glob import glob import os import pandas as pd @@ -276,7 +276,7 @@ def start_cluster(self, num_workers=None, minutes_per_task=None): # print('.', sep='', end='') client.wait_for_workers(1, 15) # wait for 1 worker with 15 sec timeout break - except (dask.distributed.TimeoutError, asyncio.exceptions.TimeoutError) as e: + except (dask.distributed.TimeoutError, asyncio.exceptions.TimeoutError): pass #print(e) # prints "Only 0/1 workers arrived after 15" diff --git a/opgee/mcs/distributed_mcs_dask.py b/opgee/mcs/distributed_mcs_dask.py index c592ab00..fbb207b6 100644 --- a/opgee/mcs/distributed_mcs_dask.py +++ b/opgee/mcs/distributed_mcs_dask.py @@ -200,7 +200,7 @@ def start_cluster(self, num_engines=None, minutes_per_task=None): # print('.', sep='', end='') client.wait_for_workers(1, 15) # wait for 1 worker with 15 sec timeout break - except (dask.distributed.TimeoutError, asyncio.exceptions.TimeoutError) as e: + except (dask.distributed.TimeoutError, asyncio.exceptions.TimeoutError): pass #print(e) # prints "Only 0/1 workers arrived after 15" diff --git a/opgee/mcs/simulation.py b/opgee/mcs/simulation.py index b1c2b235..75508c58 100644 --- a/opgee/mcs/simulation.py +++ b/opgee/mcs/simulation.py @@ -164,7 +164,7 @@ def __init__(self, full_name, rv): self.full_name = full_name try: self.class_name, self.attr_name = split_attr_name(full_name) - except OpgeeException as e: + except OpgeeException: raise McsUserError( f"attribute name format is 'ATTR' (same as 'Field.ATTR) or 'CLASS.ATTR'; got '{full_name}'" ) diff --git a/opgee/model.py b/opgee/model.py index 14299cf0..2646b7ad 100644 --- a/opgee/model.py +++ b/opgee/model.py @@ -6,7 +6,6 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -import pint from .units import ureg from .analysis import Analysis diff --git a/opgee/model_file.py b/opgee/model_file.py index 4bf4cd24..f86a89e2 100644 --- a/opgee/model_file.py +++ b/opgee/model_file.py @@ -67,7 +67,7 @@ def model_analysis_names(model_xml): xml_file_obj = ModelCache.get_xml_file(model_xml) root = xml_file_obj.getRoot() - analyses = root.xpath(f'/Model/Analysis/@name') + analyses = root.xpath('/Model/Analysis/@name') return analyses def fields_for_analysis(model_xml, analysis_name): @@ -282,7 +282,7 @@ def __init__(self, pathnames, xml_string=None, add_stream_components=True, pathnames = [] if pathnames is None else [pathnames] if not (pathnames or use_default_model or xml_string): - raise OpgeeException(f"ModelFile: no model XML file or string specified") + raise OpgeeException("ModelFile: no model XML file or string specified") opgee_xml = getParam('OPGEE.ModelFile') # default is 'etc/opgee.xml' attributes_xml = getParam('OPGEE.AttributesFile') # default is 'etc/attributes.xml' diff --git a/opgee/process.py b/opgee/process.py index 7a92a9c3..6d584c7c 100644 --- a/opgee/process.py +++ b/opgee/process.py @@ -580,7 +580,7 @@ def _find_streams_by_type(self, direction, stream_type, :raises: OpgeeException if no processes handling `stream_type` are found and `raiseError` is True """ if combine and as_list: - raise OpgeeException(f"_find_streams_by_type: both 'combine' and 'as_list' cannot be True") + raise OpgeeException("_find_streams_by_type: both 'combine' and 'as_list' cannot be True") assert direction in {self.INPUT, self.OUTPUT} stream_list = self.inputs if direction == self.INPUT else self.outputs @@ -787,7 +787,7 @@ def check_iterator_convergence(cls): :raises OpgeeIterationConverged: if all processes have converged. """ if all([proc.iteration_converged for proc in cls.iterating_processes]): - raise OpgeeIterationConverged(f"Change <= maximum_change in all iterating processes") + raise OpgeeIterationConverged("Change <= maximum_change in all iterating processes") @classmethod def reset_all_iteration(cls): @@ -990,7 +990,7 @@ class Boundary(Process): def __init__(self, *args, **kwargs): boundary = kwargs.get("boundary") if not boundary: - raise OpgeeException(f"XML elements of class 'Boundary' must define a 'boundary' attribute") + raise OpgeeException("XML elements of class 'Boundary' must define a 'boundary' attribute") name = f"{boundary}Boundary" # e.g., "ProductionBoundary" super().__init__(name, **kwargs) diff --git a/opgee/processes/__init__.py b/opgee/processes/__init__.py index 74d90368..2293bbd7 100644 --- a/opgee/processes/__init__.py +++ b/opgee/processes/__init__.py @@ -11,7 +11,6 @@ from .crude_oil_dewatering import CrudeOilDewatering from .crude_oil_stabilization import CrudeOilStabilization from .crude_oil_storage import CrudeOilStorage -from .crude_oil_storage import CrudeOilStorage from .crude_oil_transport import CrudeOilTransport from .demethanizer import Demethanizer from .downhole_pump import DownholePump diff --git a/opgee/processes/acid_gas_removal.py b/opgee/processes/acid_gas_removal.py index 87d4faf7..97f3653d 100644 --- a/opgee/processes/acid_gas_removal.py +++ b/opgee/processes/acid_gas_removal.py @@ -182,7 +182,7 @@ def run(self, analysis): mol_frac_CO2 = feed_gas_mol_frac["CO2"] if "CO2" in feed_gas_mol_frac else ureg.Quantity(0, "frac") if mol_frac_H2S.m == 0.0 and mol_frac_CO2 == 0.0: - _logger.warning(f"Feed gas does not contain H2S and CO2, please consider using non-AGR gas processing path") + _logger.warning("Feed gas does not contain H2S and CO2, please consider using non-AGR gas processing path") return if mol_frac_H2S.m <= 0.15 and mol_frac_CO2 <= 0.2: diff --git a/opgee/processes/demethanizer.py b/opgee/processes/demethanizer.py index ba8204bb..191ecbfd 100644 --- a/opgee/processes/demethanizer.py +++ b/opgee/processes/demethanizer.py @@ -139,14 +139,14 @@ def run(self, analysis): feed_gas_mol_frac = self.gas.component_molar_fractions(input) if "C1" not in feed_gas_mol_frac.index: - _logger.warning(f"Feed gas does not contain C1") + _logger.warning("Feed gas does not contain C1") inlet_C1_mol_frac = 0 else: inlet_C1_mol_frac =\ get_bounded_value(feed_gas_mol_frac["C1"].to("frac").m, "inlet_C1_mol_frac", variable_bound_dict) if "C2" not in feed_gas_mol_frac.index: - _logger.warning(f"Feed gas does not contain C2") + _logger.warning("Feed gas does not contain C2") inlet_C2_mol_frac = 0 else: inlet_C2_mol_frac =\ diff --git a/opgee/processes/steam_generation.py b/opgee/processes/steam_generation.py index b67b2011..62f6f7cd 100644 --- a/opgee/processes/steam_generation.py +++ b/opgee/processes/steam_generation.py @@ -136,7 +136,7 @@ def run(self, analysis): ureg.Quantity(max(steam_quality_diff_between_blowndown_and_outlet.to("frac").m, 0.0), "frac") if steam_quality_diff_between_blowndown_and_outlet.m < 0: - _logger.warning(f"steam quality after blowdown is smaller than steam quality at outlet") + _logger.warning("steam quality after blowdown is smaller than steam quality at outlet") blowdown_water_mass_rate = \ water_mass_rate_for_injection * steam_quality_diff_between_blowndown_and_outlet / self.steam_quality_outlet diff --git a/opgee/stream.py b/opgee/stream.py index 759ec62d..257bbb2a 100644 --- a/opgee/stream.py +++ b/opgee/stream.py @@ -11,7 +11,6 @@ import pandas as pd import pint -import pint_pandas from .units import ureg, magnitude from .attributes import AttributeMixin diff --git a/opgee/tool.py b/opgee/tool.py index d74b263d..a91b44e2 100644 --- a/opgee/tool.py +++ b/opgee/tool.py @@ -298,7 +298,7 @@ def _main(argv=None): # Set specified config vars for arg in ns.configVars: - if not '=' in arg: + if '=' not in arg: raise CommandlineError(f'--set requires an argument of the form variable=value, got "{arg}"') name, value = arg.split('=') @@ -332,7 +332,7 @@ def main(argv=None, raiseError=False): except CommandlineError as e: print(e) - except Exception as e: + except Exception: if raiseError: raise diff --git a/opgee/utils.py b/opgee/utils.py index cbbe9ea1..640cfe9c 100644 --- a/opgee/utils.py +++ b/opgee/utils.py @@ -216,7 +216,7 @@ def coercible(value, pytype, raiseError=True): try: value = pytype_func(value) - except (TypeError, ValueError) as e: + except (TypeError, ValueError): if raiseError: raise OpgeeException("%s: %r is not coercible to %s" % (getFuncName(1), value, pytype)) else: diff --git a/tests/test_audit.py b/tests/test_audit.py index 71236ab2..74eca40f 100644 --- a/tests/test_audit.py +++ b/tests/test_audit.py @@ -1,19 +1,14 @@ import os from pathlib import Path -from typing import TYPE_CHECKING, Any from unittest.mock import patch import pytest -from pandas import DataFrame import pandas as pd from opgee.audit import AuditRow, _generate_field_audit_report, audit_field -from opgee.config import getParam, setParam, getConfig -from opgee.constants import DETAILED_RESULT +from opgee.config import setParam, getConfig from opgee.error import OpgeeException from opgee.model_file import ModelFile -from opgee.field import Field -from opgee.units import ureg from tests.utils_for_tests import path_to_test_file diff --git a/tests/test_coeff.py b/tests/test_coeff.py index 6e84e4fa..a3ebff12 100644 --- a/tests/test_coeff.py +++ b/tests/test_coeff.py @@ -1,4 +1,3 @@ -import pytest import pandas as pd from opgee.process import run_corr_eqns diff --git a/tests/test_core.py b/tests/test_core.py index 8900c1b5..a2f4fde8 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -23,7 +23,7 @@ def test_dict_from_list_error(): dict_from_list(items) def test_from_xml_error(): - with pytest.raises(AbstractMethodError, match=f"Abstract method XmlInstantiable.from_xml was called.*"): + with pytest.raises(AbstractMethodError, match="Abstract method XmlInstantiable.from_xml was called.*"): XmlInstantiable("foo").from_xml(None) def test_find_parent_error(): diff --git a/tests/test_csv2xml.py b/tests/test_csv2xml.py index b198e45e..cf4484c4 100644 --- a/tests/test_csv2xml.py +++ b/tests/test_csv2xml.py @@ -1,7 +1,7 @@ import os from opgee.tool import opg from opgee.model_file import ModelFile -from .utils_for_tests import tmpdir, path_to_test_file, tempdir +from .utils_for_tests import path_to_test_file, tempdir def test_csv2xml(): with tempdir() as output_dir: diff --git a/tests/test_field_groups.py b/tests/test_field_groups.py index 497a2cee..fc7f37d9 100644 --- a/tests/test_field_groups.py +++ b/tests/test_field_groups.py @@ -1,4 +1,3 @@ -import pytest from .utils_for_tests import load_model_from_str field_groups_model_xml = """ diff --git a/tests/test_graph.py b/tests/test_graph.py index 190fad62..838e3722 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -23,7 +23,7 @@ def test_graphing(opgee_main, args): try: opgee_main.run(None, args) good = True - except Exception as e: + except Exception: # print(e) good = False diff --git a/tests/test_impute.py b/tests/test_impute.py index 3b915072..59b49184 100644 --- a/tests/test_impute.py +++ b/tests/test_impute.py @@ -1,5 +1,4 @@ import pytest -from opgee.error import OpgeeException from opgee.process import Process from .utils_for_tests import load_test_model from opgee.units import ureg diff --git a/tests/test_opgee_xml.py b/tests/test_opgee_xml.py index 0062fcd5..f5455772 100644 --- a/tests/test_opgee_xml.py +++ b/tests/test_opgee_xml.py @@ -1,6 +1,5 @@ import pytest -from opgee.units import ureg from opgee.model_file import ModelFile from tests.utils_for_tests import path_to_test_file diff --git a/tests/test_post_proc_plugin.py b/tests/test_post_proc_plugin.py index 9047e1e3..890a608d 100644 --- a/tests/test_post_proc_plugin.py +++ b/tests/test_post_proc_plugin.py @@ -1,10 +1,9 @@ import os import pytest -from opgee.analysis import Analysis from opgee.constants import DETAILED_RESULT from opgee.error import AbstractMethodError, McsUserError -from opgee.field import Field, FieldResult +from opgee.field import FieldResult from opgee.post_processor import PostProcessor from .utils_for_tests import load_test_model, path_to_test_file, tempdir diff --git a/tests/test_shared.py b/tests/test_shared.py index 9027a382..8b137891 100644 --- a/tests/test_shared.py +++ b/tests/test_shared.py @@ -1,2 +1 @@ -from opgee.processes.shared import get_energy_carrier, predict_blower_energy_use diff --git a/tests/test_stream.py b/tests/test_stream.py index 1dd72dfe..69c59252 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -75,7 +75,7 @@ def test_find_stream(stream_model): streams = proc3.find_output_streams('hydrogen', as_list=True) assert streams and type(streams) == list and len(streams) == 2 - with pytest.raises(OpgeeException, match=f".*both 'combine' and 'as_list' cannot be True"): + with pytest.raises(OpgeeException, match=".*both 'combine' and 'as_list' cannot be True"): proc3.find_output_streams('hydrogen', as_list=True, combine=True) streams = proc3.find_input_streams('gas.*', as_list=False, regex=True) diff --git a/tests/test_utils.py b/tests/test_utils.py index 3875c55f..31d310da 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -31,13 +31,13 @@ def test_coercible(value, pytype, result): def test_coercible_failure(): - with pytest.raises(OpgeeException, match=f".*is not coercible.*"): + with pytest.raises(OpgeeException, match=".*is not coercible.*"): coercible(10.7, "int") - with pytest.raises(OpgeeException, match=f".*not a recognized type string.*"): + with pytest.raises(OpgeeException, match=".*not a recognized type string.*"): coercible(10.7, "blah-blah-blah") - with pytest.raises(OpgeeException, match=f".*is not coercible.*"): + with pytest.raises(OpgeeException, match=".*is not coercible.*"): coercible("foobar", float) def test_mkdirs(): @@ -63,7 +63,7 @@ def test_mkdirs(): def test_load_module_failure(): - with pytest.raises(OpgeeException, match=f".*Can't load module.*"): + with pytest.raises(OpgeeException, match=".*Can't load module.*"): loadModuleFromPath("/not/a/rea/path.py", raiseError=True) loadModuleFromPath("/not/a/rea/path.py", raiseError=False) From c01b4c8b59db5def63bbea875e898ed779831ac7 Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Tue, 2 Sep 2025 16:17:56 -0600 Subject: [PATCH 2/6] style: run the ruff formatter on all files --- opgee/XMLFile.py | 41 +- opgee/__init__.py | 2 +- opgee/analysis.py | 19 +- opgee/attributes.py | 92 +-- opgee/audit.py | 28 +- opgee/bfs.py | 64 +-- opgee/bin/combine_csvs.py | 45 +- opgee/bin/combine_wor_data.py | 16 +- opgee/bin/gen-proc-doc.py | 20 +- opgee/bin/read-distros.py | 16 +- opgee/bin/save_stream_defs.py | 24 +- opgee/bin/stream_contents.py | 10 +- opgee/bin/venting_fugitive_dummy_csv.py | 2 +- opgee/built_ins/collect_plugin.py | 56 +- opgee/built_ins/compare_excel.py | 116 ++-- opgee/built_ins/compare_plugin.py | 154 +++--- opgee/built_ins/config_plugin.py | 81 ++- opgee/built_ins/csv2xml_plugin.py | 146 +++-- opgee/built_ins/gensim_plugin.py | 103 ++-- opgee/built_ins/graph_plugin.py | 90 +-- opgee/built_ins/gui_plugin.py | 91 ++- opgee/built_ins/merge_plugin.py | 50 +- opgee/built_ins/run_plugin.py | 64 +-- opgee/built_ins/update_plugin.py | 52 +- opgee/combine_streams.py | 34 +- opgee/config.py | 102 ++-- opgee/constants.py | 16 +- opgee/container.py | 18 +- opgee/core.py | 42 +- opgee/emissions.py | 25 +- opgee/energy.py | 27 +- opgee/error.py | 29 +- opgee/etc/pluginTemplate.py | 29 +- opgee/field.py | 262 +++------ opgee/graph.py | 22 +- opgee/gui/app.py | 203 +++---- opgee/gui/process_pane.py | 438 ++++++++------- opgee/gui/results_pane.py | 135 ++--- opgee/gui/settings_pane.py | 88 +-- opgee/gui/testapp.py | 65 +-- opgee/gui/widgets.py | 204 +++---- opgee/import_export.py | 66 ++- opgee/log.py | 64 ++- opgee/main.py | 4 +- opgee/manager.py | 217 ++++---- opgee/mcs/LHS.py | 28 +- opgee/mcs/distributed_mcs_dask.py | 94 ++-- opgee/mcs/distro.py | 195 ++++--- opgee/mcs/parameter_list.py | 82 +-- opgee/mcs/simulation.py | 73 +-- opgee/model.py | 27 +- opgee/model_file.py | 150 +++-- opgee/pkg_utils.py | 17 +- opgee/post_processor.py | 9 +- opgee/process.py | 195 ++++--- opgee/process_groups.py | 18 +- opgee/processes/CO2_injection_well.py | 11 +- opgee/processes/CO2_membrane.py | 9 +- opgee/processes/CO2_reinjection_compressor.py | 30 +- opgee/processes/LNG_liquefaction.py | 5 +- opgee/processes/LNG_regasification.py | 8 +- opgee/processes/LNG_transport.py | 10 +- opgee/processes/VF_partition.py | 8 +- opgee/processes/VRU_compressor.py | 29 +- opgee/processes/__init__.py | 1 - opgee/processes/acid_gas_removal.py | 97 ++-- opgee/processes/bitumen_mining.py | 96 ++-- opgee/processes/compressor.py | 27 +- opgee/processes/crude_oil_dewatering.py | 58 +- opgee/processes/crude_oil_stabilization.py | 68 ++- opgee/processes/crude_oil_storage.py | 75 +-- opgee/processes/crude_oil_transport.py | 25 +- opgee/processes/demethanizer.py | 114 ++-- opgee/processes/downhole_pump.py | 64 +-- opgee/processes/drilling.py | 21 +- opgee/processes/exploration.py | 47 +- opgee/processes/flaring.py | 7 +- opgee/processes/gas_dehydration.py | 92 +-- opgee/processes/gas_distribution.py | 16 +- opgee/processes/gas_gathering.py | 32 +- opgee/processes/gas_lifting_compressor.py | 17 +- opgee/processes/gas_partition.py | 122 ++-- opgee/processes/gas_reinjection_compressor.py | 18 +- opgee/processes/gas_reinjection_well.py | 4 +- opgee/processes/heavy_oil_dilution.py | 52 +- opgee/processes/heavy_oil_upgrading.py | 67 ++- opgee/processes/natural_gas_liquid.py | 3 +- opgee/processes/petrocoke_transport.py | 20 +- opgee/processes/post_storage_compressor.py | 13 +- opgee/processes/pre_membrane_chiller.py | 5 +- opgee/processes/pre_membrane_compressor.py | 10 +- opgee/processes/reservoir_well_interface.py | 40 +- opgee/processes/ryan_holmes.py | 25 +- opgee/processes/separation.py | 75 ++- opgee/processes/shared.py | 37 +- opgee/processes/sour_gas_compressor.py | 13 +- opgee/processes/steam_generation.py | 85 +-- opgee/processes/steam_generator.py | 522 ++++++++++-------- opgee/processes/storage_compressor.py | 11 +- opgee/processes/storage_separator.py | 7 +- opgee/processes/storage_well.py | 1 + opgee/processes/transmission_compressor.py | 27 +- opgee/processes/transport_energy.py | 221 ++++---- opgee/processes/venting.py | 21 +- opgee/processes/water_injection.py | 25 +- opgee/processes/water_treatment.py | 57 +- opgee/smart_defaults.py | 34 +- opgee/stream.py | 67 +-- opgee/subcommand.py | 13 +- opgee/table_manager.py | 40 +- opgee/table_update.py | 5 +- opgee/thermodynamics.py | 285 +++++----- opgee/tool.py | 93 ++-- opgee/units.py | 1 + opgee/utils.py | 50 +- opgee/version.py | 2 +- opgee/xml_utils.py | 80 +-- tests/BROKEN_test_smart_defaults.py | 32 +- tests/conftest.py | 8 +- tests/files/broken_post_proc_plugin.py | 2 +- .../post-proc-plugins/1_post_processor.py | 7 +- .../post-proc-plugins/2_post_processor.py | 7 +- tests/files/simple_post_processor.py | 7 +- tests/files/user_processes.py | 5 +- tests/processes/test_compressor.py | 20 +- tests/test_attr_constraints.py | 10 +- tests/test_attributes.py | 28 +- tests/test_audit.py | 28 +- tests/test_boundary.py | 6 +- tests/test_boundary_procs.py | 16 +- tests/test_coeff.py | 87 ++- tests/test_comparison.py | 5 +- tests/test_config.py | 82 ++- tests/test_core.py | 21 +- tests/test_csv2xml.py | 23 +- tests/test_distributed_mcs.py | 11 +- tests/test_emissions.py | 43 +- tests/test_energy.py | 30 +- tests/test_field.py | 23 +- tests/test_field_groups.py | 11 +- tests/test_graph.py | 21 +- tests/test_import_export.py | 21 +- tests/test_impute.py | 34 +- tests/test_intermediate_boundary.py | 10 +- tests/test_mcs.py | 69 +-- tests/test_merge_xml.py | 22 +- tests/test_model.py | 24 +- tests/test_model_file.py | 43 +- tests/test_molecule_names.py | 10 +- tests/test_opgee_xml.py | 7 +- tests/test_packet.py | 13 +- tests/test_post_proc_plugin.py | 111 ++-- tests/test_process_groups.py | 30 +- tests/test_process_loop.py | 19 +- tests/test_processes.py | 347 ++++++------ tests/test_run_subcmd.py | 138 +++-- tests/test_smart_defaults.py | 20 +- tests/test_stream.py | 55 +- tests/test_table_manager.py | 13 +- tests/test_thermofunction.py | 20 +- tests/test_utils.py | 30 +- tests/test_xml_file.py | 12 +- tests/utils_for_tests.py | 38 +- 163 files changed, 4871 insertions(+), 4226 deletions(-) diff --git a/opgee/XMLFile.py b/opgee/XMLFile.py index 01a78fb2..ba0c0721 100644 --- a/opgee/XMLFile.py +++ b/opgee/XMLFile.py @@ -1,10 +1,11 @@ -''' +""" .. Created as part of pygcam (2015) Imported into opgee (2021) .. Copyright (c) 2015-2022 Richard Plevin See the https://opensource.org/licenses/MIT for license details. -''' +""" + from io import BytesIO from lxml import etree as ET @@ -15,12 +16,20 @@ _logger = getLogger(__name__) -class XMLFile(object): - - parsed_schemas = {} # cache parsed schemas to avoid re-reading and parsing opgee.xsd - def __init__(self, filename, xml_string=None, load=True, schemaPath=None, - removeComments=True, conditionalXML=False, varDict=None): +class XMLFile(object): + parsed_schemas = {} # cache parsed schemas to avoid re-reading and parsing opgee.xsd + + def __init__( + self, + filename, + xml_string=None, + load=True, + schemaPath=None, + removeComments=True, + conditionalXML=False, + varDict=None, + ): """ Stores information about an XML file; provides wrapper to parse and access the file tree, and handle "conditional XML". @@ -41,10 +50,10 @@ def __init__(self, filename, xml_string=None, load=True, schemaPath=None, self.xml_string = str.encode(xml_string) if xml_string else None self.tree = None self.conditionalXML = conditionalXML - self.varDict = varDict or getConfigDict(section=getParam('OPGEE.DefaultProject')) + self.varDict = varDict or getConfigDict(section=getParam("OPGEE.DefaultProject")) self.removeComments = removeComments - self.schemaPath = schemaPath + self.schemaPath = schemaPath self.schemaStream = None # if filename and load: @@ -52,15 +61,15 @@ def __init__(self, filename, xml_string=None, load=True, schemaPath=None, self.read() def getRoot(self): - 'Return the root node of the parse tree' + "Return the root node of the parse tree" return self.tree.getroot() def getTree(self): - 'Return XML parse tree.' + "Return XML parse tree." return self.tree def getFilename(self): - 'Return the filename for this ``XMLFile``' + "Return the filename for this ``XMLFile``" return self.filename def read(self): @@ -86,7 +95,7 @@ def read(self): raise XmlFormatError(f"Can't read from XML {thing}: {e}") if self.removeComments: - for elt in tree.iterfind('.//comment'): + for elt in tree.iterfind(".//comment"): parent = elt.getparent() if parent is not None: parent.remove(elt) @@ -114,7 +123,7 @@ def validate(self, raiseOnError=True): # use the cached version if available schema = self.parsed_schemas.get(self.schemaPath) if not schema: - ref = imp.files('opgee') / self.schemaPath + ref = imp.files("opgee") / self.schemaPath with imp.as_file(ref) as path: xsd = ET.parse(path) @@ -126,7 +135,9 @@ def validate(self, raiseOnError=True): schema.assertValid(tree) return True except ET.DocumentInvalid as e: - raise XmlFormatError(f"Validation of '{self.filename}'\n using schema '{self.schemaPath}' failed:\n {e}") + raise XmlFormatError( + f"Validation of '{self.filename}'\n using schema '{self.schemaPath}' failed:\n {e}" + ) else: valid = schema.validate(tree) return valid diff --git a/opgee/__init__.py b/opgee/__init__.py index bedaa356..a831a8f2 100644 --- a/opgee/__init__.py +++ b/opgee/__init__.py @@ -2,4 +2,4 @@ # warnings.filterwarnings("ignore", category=DeprecationWarning) # warnings.filterwarnings("error", category=UserWarning) # turn warning into error to debug -warnings.filterwarnings("ignore", category=UserWarning) # turn warning into error to debug \ No newline at end of file +warnings.filterwarnings("ignore", category=UserWarning) # turn warning into error to debug diff --git a/opgee/analysis.py b/opgee/analysis.py index 71e611fd..55dff4f5 100644 --- a/opgee/analysis.py +++ b/opgee/analysis.py @@ -20,7 +20,7 @@ class Group(OpgeeObject): def __init__(self, elt): - self.is_regex = getBooleanXML(elt.attrib.get('regex', 0)) + self.is_regex = getBooleanXML(elt.attrib.get("regex", 0)) self.text = elt.text @@ -39,6 +39,7 @@ class Analysis(Container): See also :doc:`OPGEE XML documentation ` """ + def __init__(self, name, parent=None, attr_dict=None, field_names=None, groups=None): super().__init__(name, attr_dict=attr_dict, parent=parent) self.check_attr_constraints(self.attr_dict) @@ -49,22 +50,22 @@ def __init__(self, name, parent=None, attr_dict=None, field_names=None, groups=N self.model = model = parent # self.field_dict = None - self._field_names = field_names # may be extended in add_children() + self._field_names = field_names # may be extended in add_children() self.groups = [] if groups is None else groups self.fn_unit = self.attr("functional_unit") self.boundary = self.attr("boundary") # Create validation sets from system.cfg to avoid hard-coding these - self.functional_units = set(getParamAsList('OPGEE.FunctionalUnits')) + self.functional_units = set(getParamAsList("OPGEE.FunctionalUnits")) # This is set in use_GWP() below to a pandas Series holding the current # values in use, indexed by gas name. self.gwp = None # Use the GWP years and version specified in XML - gwp_horizon = self.attr('GWP_horizon') - gwp_version = self.attr('GWP_version') + gwp_horizon = self.attr("GWP_horizon") + gwp_version = self.attr("GWP_version") self.use_GWP(gwp_horizon, gwp_version) @@ -75,8 +76,7 @@ def __init__(self, name, parent=None, attr_dict=None, field_names=None, groups=N text = group.text if group.is_regex: prog = re.compile(text) - matches = [field for field in model.fields() for - name in field.group_names if prog.match(name)] + matches = [field for field in model.fields() for name in field.group_names if prog.match(name)] else: matches = [field for field in model.fields() if text in field.group_names] @@ -97,7 +97,6 @@ def restrict_fields(self, field_names): # Use list comprehension rather than set.intersection to maintain original order self._field_names = [name for name in self._field_names if name in names] - def get_field(self, name, raiseError=True) -> Field: """ Find a `Field` by name in an `Analysis`. @@ -227,8 +226,8 @@ def from_xml(cls, elt, parent=None, field_names=None): """ name = elt_name(elt) attr_dict = cls.instantiate_attrs(elt) - field_names = field_names or [elt_name(node) for node in elt.findall('FieldRef')] - groups = [Group(node) for node in elt.findall('Group')] + field_names = field_names or [elt_name(node) for node in elt.findall("FieldRef")] + groups = [Group(node) for node in elt.findall("Group")] obj = Analysis(name, attr_dict=attr_dict, parent=parent, field_names=field_names, groups=groups) return obj diff --git a/opgee/attributes.py b/opgee/attributes.py index 2c7929fc..31725110 100644 --- a/opgee/attributes.py +++ b/opgee/attributes.py @@ -18,6 +18,7 @@ _logger = getLogger(__name__) + class Options(XmlInstantiable): def __init__(self, name, default, options): super().__init__(name) @@ -26,24 +27,35 @@ def __init__(self, name, default, options): @classmethod def from_xml(cls, elt, parent=None): - option_elts = elt.findall('Option') - options = [(elt.text, elt.attrib.get('label', elt.text), elt.attrib.get('desc')) for elt in option_elts] - obj = Options(elt_name(elt), elt.attrib.get('default'), options) + option_elts = elt.findall("Option") + options = [(elt.text, elt.attrib.get("label", elt.text), elt.attrib.get("desc")) for elt in option_elts] + obj = Options(elt_name(elt), elt.attrib.get("default"), options) return obj + class AttrDef(XmlInstantiable): - def __init__(self, name, parent=None, value=None, pytype=None, option_set=None, unit=None, - constraints=None, exclusive=None, synchronized=None): + def __init__( + self, + name, + parent=None, + value=None, + pytype=None, + option_set=None, + unit=None, + constraints=None, + exclusive=None, + synchronized=None, + ): super().__init__(name, parent=parent) self.default = None - self.option_set = option_set # the name of the option set, if any + self.option_set = option_set # the name of the option set, if any self.unit = unit self.pytype = pytype - self.constraints = constraints # range constraints - self.synchronized = synchronized # the name of a "synchronization group" to link attributes - self.exclusive = exclusive # the name of an "exclusive group" to link attributes + self.constraints = constraints # range constraints + self.synchronized = synchronized # the name of a "synchronization group" to link attributes + self.exclusive = exclusive # the name of an "exclusive group" to link attributes - if value is not None: # if value is None, we set default later + if value is not None: # if value is None, we set default later self.set_default(value) def set_default(self, value): @@ -78,18 +90,20 @@ def from_xml(cls, elt, parent=None): """ a = elt.attrib - ops = ('GT', 'GE', 'LT', 'LE') + ops = ("GT", "GE", "LT", "LE") constraints = [(op, coercible(a[op], float)) for op in ops if a.get(op)] # if elt.text is None, we supply the default later in __init__() - obj = AttrDef(a['name'], - value=elt.text, - pytype=a.get('type'), - unit=a.get('unit'), - option_set=a.get('options'), - constraints=constraints, - exclusive=a.get('exclusive'), - synchronized=a.get('synchronized')) + obj = AttrDef( + a["name"], + value=elt.text, + pytype=a.get("type"), + unit=a.get("unit"), + option_set=a.get("options"), + constraints=constraints, + exclusive=a.get("exclusive"), + synchronized=a.get("synchronized"), + ) return obj @@ -97,6 +111,7 @@ class ClassAttrs(XmlInstantiable): """ Support for parsing attributes.xml metadata """ + def __init__(self, name, attr_dict, option_dict): super().__init__(name) self.attr_dict = attr_dict @@ -110,7 +125,7 @@ def __init__(self, name, attr_dict, option_dict): set_name = attr.option_set if attr.default is None and set_name: option_set = option_dict[set_name] - attr.set_default(option_set.default) # handles type coercion + attr.set_default(option_set.default) # handles type coercion if attr.synchronized: syncs[attr.synchronized].append(attr.name) @@ -136,7 +151,6 @@ def from_xml(cls, elt, parent=None): obj = cls(elt_name(elt), attr_dict, option_dict) return obj - @staticmethod def _lookup(obj, dict_name, key, raiseError=True): """ @@ -156,7 +170,7 @@ def _lookup(obj, dict_name, key, raiseError=True): return value def attribute(self, name, raiseError=True): - return self._lookup(self.attr_dict, 'definition', name, raiseError=raiseError) + return self._lookup(self.attr_dict, "definition", name, raiseError=raiseError) class AttrDefs(OpgeeObject): @@ -165,6 +179,7 @@ class AttrDefs(OpgeeObject): This is a singleton class: use ``AttrDefs.get_instance()`` rather than calling ``AttrDefs()`` directly. """ + instance = None def __init__(self, root): @@ -204,7 +219,7 @@ def class_attrs(self, classname, raiseError=True): return attrs -class AttributeMixin(): +class AttributeMixin: """ Consolidates attribute-related code shared by ``Container`` and ``Process`` classes. Note: must be mixed into classes that have both ``self.attr_dict`` and @@ -250,7 +265,7 @@ def attrs_with_prefix(self, prefix): unit = attr_dict[names[0]].unit dtype = f"pint[{unit}]" if unit else None - d = {name[prefix_len:] : attr_dict[name].value for name in names} + d = {name[prefix_len:]: attr_dict[name].value for name in names} s = pd.Series(d, dtype=dtype) return s @@ -279,14 +294,14 @@ def instantiate_attrs(cls, elt, is_process=False): """ attr_dict = {} attr_defs = AttrDefs.get_instance() - process_attrs = attr_defs.classes.get('Process') if is_process else None + process_attrs = attr_defs.classes.get("Process") if is_process else None classname = cls.__name__ class_attrs = attr_defs.class_attrs(classname, raiseError=False) if class_attrs or process_attrs: # Create a dict of explicit values to set attribute values below. - user_values = {elt_name(a) : a.text for a in elt.findall('A')} + user_values = {elt_name(a): a.text for a in elt.findall("A")} # first copy Process attributes, if relevant. Then overwrite with subprocess attributes combined_dict = process_attrs.attr_dict.copy() if process_attrs else {} @@ -315,25 +330,27 @@ def check_attr_constraints(cls, attr_dict): return # nothing to check funcs = { - 'LT': (lambda value, limit: value < limit, "<"), - 'LE': (lambda value, limit: value <= limit, "<="), - 'GT': (lambda value, limit: value > limit, ">"), - 'GE': (lambda value, limit: value >= limit, ">="), + "LT": (lambda value, limit: value < limit, "<"), + "LE": (lambda value, limit: value <= limit, "<="), + "GT": (lambda value, limit: value > limit, ">"), + "GE": (lambda value, limit: value >= limit, ">="), } def is_a_process(cls): for superclass in cls.__mro__: - if superclass.__name__ == 'Process': + if superclass.__name__ == "Process": return True return False - process_attr_dict = attr_defs.class_attrs('Process').attr_dict + process_attr_dict = attr_defs.class_attrs("Process").attr_dict # Check numeric constraints for attr_name, attr in attr_dict.items(): # If the definition of an attribute of a subprocess is not known, look at Process's attributes - attr_def = class_attrs.attr_dict.get(attr_name) or (process_attr_dict.get(attr_name) if is_a_process(cls) else None) + attr_def = class_attrs.attr_dict.get(attr_name) or ( + process_attr_dict.get(attr_name) if is_a_process(cls) else None + ) if not attr_def: raise ModelValidationError(f"Attribute '{attr_name}' not found for class '{cls.__name__}'") @@ -345,7 +362,9 @@ def is_a_process(cls): # print(f"Testing ({value} {op} {limit}) for attr {attr_name}") func, symbol = funcs[op] if not func(value, limit): - raise ModelValidationError(f"Attribute '{attr_name}': constraint failed: value {value} is not {symbol} {limit}") + raise ModelValidationError( + f"Attribute '{attr_name}': constraint failed: value {value} is not {symbol} {limit}" + ) # Check exclusive groups for group, attr_names in class_attrs.excludes.items(): @@ -356,7 +375,10 @@ def is_a_process(cls): raise ModelValidationError(f"Exclusive attribute group '{group}' has multiple items selected: {items}") # Check synchronized groups - for group, attr_names, in class_attrs.syncs.items(): + for ( + group, + attr_names, + ) in class_attrs.syncs.items(): values = [attr_dict[attr_name].value for attr_name in attr_names] if sum(values[1:]) != values[0]: raise ModelValidationError(f"Attributes in synchronized group '{group}' have differing values") diff --git a/opgee/audit.py b/opgee/audit.py index 89391c31..eabbadf6 100644 --- a/opgee/audit.py +++ b/opgee/audit.py @@ -27,6 +27,7 @@ class AuditRow(TypedDict): value: str unit: str | None + class AuditData(TypedDict): field: list[AuditRow] | None proc_graph: Dot | None @@ -77,9 +78,7 @@ def _translate_audit_level( return AuditFlag.NONE -def _generate_field_audit_report( - field: Field, original_field_element: etree._Element -) -> list[AuditRow]: +def _generate_field_audit_report(field: Field, original_field_element: etree._Element) -> list[AuditRow]: """ Generate a report detailing the source of each field-level attribute's final value. @@ -89,22 +88,16 @@ def _generate_field_audit_report( """ attr_defs = AttrDefs.get_instance() if not attr_defs: - _logger.warning( - "Attribute definitions (AttrDefs) not loaded. Source information will be incomplete." - ) + _logger.warning("Attribute definitions (AttrDefs) not loaded. Source information will be incomplete.") return [] class_attrs = attr_defs.class_attrs("Field", raiseError=False) if not class_attrs: - _logger.warning( - "ClassAttrs for 'Field' not found. Source information will be incomplete." - ) + _logger.warning("ClassAttrs for 'Field' not found. Source information will be incomplete.") return [] original_attrs: dict[str, str] = { - a.get("name"): a.text - for a in original_field_element.xpath("./A") - if a.get("name") + a.get("name"): a.text for a in original_field_element.xpath("./A") if a.get("name") } report_rows: list[AuditRow] = [] @@ -145,9 +138,7 @@ def _generate_field_audit_report( return report_rows -def audit_field( - field: Field, mf: ModelFile, audit_level: str | None = None -) -> AuditData | None: +def audit_field(field: Field, mf: ModelFile, audit_level: str | None = None) -> AuditData | None: """ Control field auditing based on configuration settings. @@ -165,14 +156,12 @@ def audit_field( if audit_flag == AuditFlag.NONE: return None - audit_data: AuditData = { "field": None, "proc_graph": None } + audit_data: AuditData = {"field": None, "proc_graph": None} if audit_flag & AuditFlag.FIELD: root = mf.root elem_list = root.xpath(f".//Field[@name='{field.name}']") if not elem_list: - _logger.error( - f"Audit failed for field '{field.name}': Original field element not found in XML." - ) + _logger.error(f"Audit failed for field '{field.name}': Original field element not found in XML.") return None field_elem: etree._Element = elem_list[0] audit_data["field"] = _generate_field_audit_report(field, field_elem) @@ -181,4 +170,3 @@ def audit_field( audit_data["proc_graph"] = create_process_diagram(field) return audit_data - diff --git a/opgee/bfs.py b/opgee/bfs.py index 061bff4a..3686c357 100644 --- a/opgee/bfs.py +++ b/opgee/bfs.py @@ -11,16 +11,16 @@ def is_cyclic_until(successor, visited, rec_stack, successors): """ - Helper function to determine if there is a cycle in the given successors starting from the current successor. + Helper function to determine if there is a cycle in the given successors starting from the current successor. - Args: - successor (Node): The current node being examined for cycles. - visited (dict): A dictionary to keep track of visited nodes. - rec_stack (dict): A dictionary to keep track of nodes in the recursion stack. - successors (list): A list of successors to check for cycles. + Args: + successor (Node): The current node being examined for cycles. + visited (dict): A dictionary to keep track of visited nodes. + rec_stack (dict): A dictionary to keep track of nodes in the recursion stack. + successors (list): A list of successors to check for cycles. - Returns: - bool: True if a cycle is found, False otherwise. + Returns: + bool: True if a cycle is found, False otherwise. """ visited[successor] = True @@ -41,13 +41,13 @@ def is_cyclic_until(successor, visited, rec_stack, successors): def is_cyclic(successors): """ - Determines if there is a cycle in the given successors list. + Determines if there is a cycle in the given successors list. - Args: - successors (list): A list of successors to check for cycles. + Args: + successors (list): A list of successors to check for cycles. - Returns: - bool: True if a cycle is found, False otherwise. + Returns: + bool: True if a cycle is found, False otherwise. """ visited = {} rec_stack = {} @@ -60,12 +60,12 @@ def is_cyclic(successors): def find_distance_bfs(start_node, successors_dist_dict, visited): """ - Helper function to perform a Breadth-First Search (BFS) traversal to calculate the maximum distance for each node. + Helper function to perform a Breadth-First Search (BFS) traversal to calculate the maximum distance for each node. - Args: - start_node (Node): The starting node for the BFS traversal. - successors_dist_dict (dict): A dictionary that stores the maximum distance for each node. - visited (dict): A dictionary to keep track of visited nodes during the traversal. + Args: + start_node (Node): The starting node for the BFS traversal. + successors_dist_dict (dict): A dictionary that stores the maximum distance for each node. + visited (dict): A dictionary to keep track of visited nodes during the traversal. """ queue = deque([start_node]) @@ -88,14 +88,14 @@ def find_distance_bfs(start_node, successors_dist_dict, visited): def get_successor_pairs(successors): """ - Calculate the maximum distance from the starting node to each node in the successors list. + Calculate the maximum distance from the starting node to each node in the successors list. - Args: - successors (list): A list of successors. + Args: + successors (list): A list of successors. - Returns: - list: A list of tuples, where each tuple contains a successor node and its maximum distance from the starting node. - """ + Returns: + list: A list of tuples, where each tuple contains a successor node and its maximum distance from the starting node. + """ successors_dist_dict = {} for successor in successors: successors_dist_dict[successor] = 0 @@ -108,16 +108,16 @@ def get_successor_pairs(successors): def bfs(start_node, unvisited, ordered_cycle): """ - Perform a Breadth-First Search (BFS) traversal on a graph structure starting - from ``start_node``. + Perform a Breadth-First Search (BFS) traversal on a graph structure starting + from ``start_node``. - Args: - start_node: The starting node for the BFS traversal. - unvisited: The notes yet to be visited (N.B. caller's list is modified) - ordered_cycle: the BFS ordered nodes (N.B. caller's list is modified) + Args: + start_node: The starting node for the BFS traversal. + unvisited: The notes yet to be visited (N.B. caller's list is modified) + ordered_cycle: the BFS ordered nodes (N.B. caller's list is modified) - Returns: - none + Returns: + none """ deck = deque([start_node]) diff --git a/opgee/bin/combine_csvs.py b/opgee/bin/combine_csvs.py index a7036cb2..79345d62 100755 --- a/opgee/bin/combine_csvs.py +++ b/opgee/bin/combine_csvs.py @@ -8,39 +8,52 @@ import argparse import pandas as pd -DEFAULT_OUTPUT = 'combined.csv' +DEFAULT_OUTPUT = "combined.csv" -def parseArgs(): - parser = argparse.ArgumentParser(description='''Combine CSV data into a single CSV file''') - choices = ('rows', 'columns') - parser.add_argument('-a', '--axis', choices=choices, default='rows', - help='''The axis on which to join the CSVs. Default is "rows", which requires +def parseArgs(): + parser = argparse.ArgumentParser(description="""Combine CSV data into a single CSV file""") + + choices = ("rows", "columns") + parser.add_argument( + "-a", + "--axis", + choices=choices, + default="rows", + help="""The axis on which to join the CSVs. Default is "rows", which requires that all CSVs share the same columns. If choosing "columns", all CSVs much share - the same index.''') + the same index.""", + ) - parser.add_argument('-o', '--output', default=DEFAULT_OUTPUT, required=True, - help='''The output file to create. Default is "%s".''' % DEFAULT_OUTPUT) + parser.add_argument( + "-o", + "--output", + default=DEFAULT_OUTPUT, + required=True, + help="""The output file to create. Default is "%s".""" % DEFAULT_OUTPUT, + ) - parser.add_argument('inputs', nargs='*', - help='''Input CSV files. Must have the same number and names of columns''') + parser.add_argument("inputs", nargs="*", help="""Input CSV files. Must have the same number and names of columns""") - parser.add_argument('-s', '--skip', type=int, default=0, - help='''A number of lines to skip before reading the column headers.''') + parser.add_argument( + "-s", "--skip", type=int, default=0, help="""A number of lines to skip before reading the column headers.""" + ) args = parser.parse_args() return args + import glob + def main(): args = parseArgs() print(f"In: {args.inputs}") print(f"Out: {args.output}") - index_col = False if args.axis == 'rows' else 0 - write_index = False if args.axis == 'rows' else True + index_col = False if args.axis == "rows" else 0 + write_index = False if args.axis == "rows" else True # Expand file paths using wildcard character expanded_file_paths = [] @@ -53,5 +66,5 @@ def main(): combined = pd.concat(dfs, axis=args.axis) combined.to_csv(args.output, index=write_index) -main() +main() diff --git a/opgee/bin/combine_wor_data.py b/opgee/bin/combine_wor_data.py index 096cecb0..70c59f2c 100755 --- a/opgee/bin/combine_wor_data.py +++ b/opgee/bin/combine_wor_data.py @@ -1,19 +1,19 @@ import pandas as pd -etc_dir = '/Users/rjp/repos/OPGEEv4/opgee/mcs/etc/' +etc_dir = "/Users/rjp/repos/OPGEEv4/opgee/mcs/etc/" -norway_csv = etc_dir + 'Norway_historical_WOR.csv' -uk_csv = etc_dir + 'UK_Results_MATLAB.csv' -orig_csv = etc_dir + 'WOR_observations_long.csv' +norway_csv = etc_dir + "Norway_historical_WOR.csv" +uk_csv = etc_dir + "UK_Results_MATLAB.csv" +orig_csv = etc_dir + "WOR_observations_long.csv" norway_df = pd.read_csv(norway_csv) -norway_df.drop('year', axis='columns', inplace=True) +norway_df.drop("year", axis="columns", inplace=True) uk_df = pd.read_csv(uk_csv) orig_df = pd.read_csv(orig_csv) -all = pd.concat([orig_df, norway_df, uk_df], axis="rows").dropna(axis='rows') -all = all.query("WOR > 0") # drop any remaining rows with zero or negative values for WOR +all = pd.concat([orig_df, norway_df, uk_df], axis="rows").dropna(axis="rows") +all = all.query("WOR > 0") # drop any remaining rows with zero or negative values for WOR -all_csv = etc_dir + 'all_wor.csv' +all_csv = etc_dir + "all_wor.csv" all.to_csv(all_csv, index=False) diff --git a/opgee/bin/gen-proc-doc.py b/opgee/bin/gen-proc-doc.py index 2ce1e0ea..e2aa6804 100644 --- a/opgee/bin/gen-proc-doc.py +++ b/opgee/bin/gen-proc-doc.py @@ -16,18 +16,20 @@ """ + def main(): - doc_dir = os.path.expanduser("~/repos/OPGEEv4/docs/source/processes") - proc_dir = os.path.expanduser("~/repos/OPGEEv4/opgee/processes") + doc_dir = os.path.expanduser("~/repos/OPGEEv4/docs/source/processes") + proc_dir = os.path.expanduser("~/repos/OPGEEv4/opgee/processes") + + with open(f"{proc_dir}/__init__.py") as f: + imports = [line.strip() for line in f.readlines() if line and not line.startswith("from .process_for_test")] - with open(f'{proc_dir}/__init__.py') as f: - imports = [line.strip() for line in f.readlines() if line and not line.startswith('from .process_for_test')] + matches = [(m.group(1), m.group(2)) for m in [re.match(r"from \.(.+) import (.+)\s*", s) for s in imports]] - matches = [(m.group(1), m.group(2)) for m in [re.match(r"from \.(.+) import (.+)\s*", s) for s in imports]] + for filename, classname in matches: + with open(f"{doc_dir}/{classname}.rst", "w") as f: + text = template.format(classname=classname, filename=filename) + f.write(text) - for filename, classname in matches: - with open(f"{doc_dir}/{classname}.rst", "w") as f: - text = template.format(classname=classname, filename=filename) - f.write(text) main() diff --git a/opgee/bin/read-distros.py b/opgee/bin/read-distros.py index 7dc1e6be..75c1ab6a 100644 --- a/opgee/bin/read-distros.py +++ b/opgee/bin/read-distros.py @@ -1,10 +1,10 @@ import pandas as pd -df = pd.read_csv('/mcs/parameter_distributions.csv').fillna('') +df = pd.read_csv("/mcs/parameter_distributions.csv").fillna("") # See https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.truncnorm.html -for row in df.itertuples(index=False, name='row'): +for row in df.itertuples(index=False, name="row"): shape = row.distribution_type.lower() name = row.variable_name low = row.low_bound @@ -14,31 +14,31 @@ default = row.default_value prob_of_yes = row.prob_of_yes - if low == '' and high == '' and mean == '' and prob_of_yes == '': + if low == "" and high == "" and mean == "" and prob_of_yes == "": print(f"{name} depends on other distributions / smart defaults") - elif shape == 'binary': + elif shape == "binary": if prob_of_yes == 0 or prob_of_yes == 1: print(f"* Ignoring distribution on {name}, Binary distribution has prob_of_yes = {prob_of_yes}") else: print(f"{name} = weighted_binary(prob_of_one={prob_of_yes})") - elif shape == 'uniform': + elif shape == "uniform": if low == high: print(f"* Ignoring distribution on {name}, Uniform high and low bounds are both {low}") else: print(f"{name} = uniform({low}, {high})") - elif shape == 'triangular': + elif shape == "triangular": print(f"{name} = triangular({low}, {default}, {high})") - elif shape == 'normal': + elif shape == "normal": if stdev == 0.0: print(f"* Ignoring distribution on {name}, Normal has stdev = 0") else: print(f"{name} = normal({mean}, {stdev}, minimum={low}, maximum={high})") - elif shape == 'lognormal': + elif shape == "lognormal": # Uses mean and stdef of underlying normal, not of the lognormal print(f"{name} = lognormal({mean}, {stdev})") diff --git a/opgee/bin/save_stream_defs.py b/opgee/bin/save_stream_defs.py index c6d793f8..b9d192b6 100644 --- a/opgee/bin/save_stream_defs.py +++ b/opgee/bin/save_stream_defs.py @@ -5,9 +5,9 @@ import pandas as pd from lxml import etree as ET -flow_sheet_csv = '/Volumes/Plevin1TB/Software/OPGEE/model/flow_sheet.csv' -stream_xml_path = '/Users/rjp/repos/OPGEEv4/opgee/etc/streams.xml' -streams_csv_path = '/Users/rjp/repos/OPGEEv4/opgee/etc/streams.csv' +flow_sheet_csv = "/Volumes/Plevin1TB/Software/OPGEE/model/flow_sheet.csv" +stream_xml_path = "/Users/rjp/repos/OPGEEv4/opgee/etc/streams.xml" +streams_csv_path = "/Users/rjp/repos/OPGEEv4/opgee/etc/streams.csv" df = pd.read_csv(flow_sheet_csv, index_col=None) @@ -16,18 +16,18 @@ if pd.isna(tup[1]): tup[1] = f"Reserved-{tup[0]}" -root = ET.Element('Streams') +root = ET.Element("Streams") for number, name in tups: - stream = ET.SubElement(root, 'Stream', attrib={'name': name, 'number': number}) - stream.text = ' ' - #t = ET.SubElement(stream, 'Temperature', attrib={'unit': 'degF'}) - #t.text = '0' - #p = ET.SubElement(stream, 'Pressure', attrib={'unit': 'psia'}) - #p.text = '0' + stream = ET.SubElement(root, "Stream", attrib={"name": name, "number": number}) + stream.text = " " + # t = ET.SubElement(stream, 'Temperature', attrib={'unit': 'degF'}) + # t.text = '0' + # p = ET.SubElement(stream, 'Pressure', attrib={'unit': 'psia'}) + # p.text = '0' tree = ET.ElementTree(root) -tree.write(stream_xml_path, xml_declaration=True, pretty_print=True, encoding='utf-8') +tree.write(stream_xml_path, xml_declaration=True, pretty_print=True, encoding="utf-8") tups2 = [(int(number), name) for number, name in tups] -df = pd.DataFrame(data=tups2, columns=['number', 'name']).set_index('number') +df = pd.DataFrame(data=tups2, columns=["number", "name"]).set_index("number") df.to_csv(streams_csv_path) diff --git a/opgee/bin/stream_contents.py b/opgee/bin/stream_contents.py index fe7c19ef..78a38fc2 100755 --- a/opgee/bin/stream_contents.py +++ b/opgee/bin/stream_contents.py @@ -7,10 +7,11 @@ from opgee.pkg_utils import resourceStream from opgee.XMLFile import XMLFile + def main(): - opgee_xml = getParam('OPGEE.ModelFile') - base_stream = resourceStream(opgee_xml, stream_type='bytes', decode=None) - xml_file = XMLFile(base_stream, schemaPath='etc/opgee.xsd') + opgee_xml = getParam("OPGEE.ModelFile") + base_stream = resourceStream(opgee_xml, stream_type="bytes", decode=None) + xml_file = XMLFile(base_stream, schemaPath="etc/opgee.xsd") root = xml_file.getRoot() # use a Set to store unique values @@ -18,5 +19,6 @@ def main(): for txt in sorted(contents): print(txt) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/opgee/bin/venting_fugitive_dummy_csv.py b/opgee/bin/venting_fugitive_dummy_csv.py index 55920b77..463cabf6 100755 --- a/opgee/bin/venting_fugitive_dummy_csv.py +++ b/opgee/bin/venting_fugitive_dummy_csv.py @@ -12,4 +12,4 @@ for name in columns: df[name] = np.random.uniform(0.001, 0.003, N) -df.to_csv('/tmp/venting_fugitives_by_process.csv', index=None) +df.to_csv("/tmp/venting_fugitives_by_process.csv", index=None) diff --git a/opgee/built_ins/collect_plugin.py b/opgee/built_ins/collect_plugin.py index 12ee64a2..779fdb90 100644 --- a/opgee/built_ins/collect_plugin.py +++ b/opgee/built_ins/collect_plugin.py @@ -9,34 +9,45 @@ _logger = getLogger(__name__) -class CollectCommand(SubcommandABC): +class CollectCommand(SubcommandABC): def __init__(self, subparsers): - kwargs = {'help' : 'Collect partial results from packets of trials in a Monte Carlo simulation.'} - super().__init__('collect', subparsers, kwargs) + kwargs = {"help": "Collect partial results from packets of trials in a Monte Carlo simulation."} + super().__init__("collect", subparsers, kwargs) def addArgs(self, parser): from ..utils import ParseCommaList - parser.add_argument('-d', '--delete', action='store_true', - help='''Delete the partial result files after combining them into a single file.''') - - parser.add_argument('-f', '--fields', action=ParseCommaList, - help='''The names of the field to operate on. If not provided, all partial - result files found will be combined.''') - - parser.add_argument('-o', '--output-dir', - help='''The directory containing partial result files. Use only for non-MCS results. - For MCS, use the -s/--sim-dir option.''') - - parser.add_argument('-s', '--sim-dir', - help='''The simulation directory. Use for Monte Carlo Simulations only. For - non-MCS results, use the -o/--output-dir option.''') - - - - return parser # for auto-doc generation - + parser.add_argument( + "-d", + "--delete", + action="store_true", + help="""Delete the partial result files after combining them into a single file.""", + ) + + parser.add_argument( + "-f", + "--fields", + action=ParseCommaList, + help="""The names of the field to operate on. If not provided, all partial + result files found will be combined.""", + ) + + parser.add_argument( + "-o", + "--output-dir", + help="""The directory containing partial result files. Use only for non-MCS results. + For MCS, use the -s/--sim-dir option.""", + ) + + parser.add_argument( + "-s", + "--sim-dir", + help="""The simulation directory. Use for Monte Carlo Simulations only. For + non-MCS results, use the -o/--output-dir option.""", + ) + + return parser # for auto-doc generation def run(self, args, tool): from ..manager import combine_mcs_results, combine_field_results @@ -45,4 +56,3 @@ def run(self, args, tool): combine_mcs_results(args.sim_dir, args.fields, args.delete) else: combine_field_results(args.output_dir, args.fields, args.delete) - diff --git a/opgee/built_ins/compare_excel.py b/opgee/built_ins/compare_excel.py index d57b5a33..1ce17a34 100644 --- a/opgee/built_ins/compare_excel.py +++ b/opgee/built_ins/compare_excel.py @@ -1,73 +1,75 @@ from matplotlib import pyplot as plt import pandas as pd import seaborn as sns -#from scipy import stats +# from scipy import stats # Could load this from opgee.built_ins.compare_plugin (RP) process_translator = { - 'Acid gas removal' : 'AcidGasRemoval', - 'CO2 gas reinjection compressor' : 'GasReinjectionCompressor', - 'CO2 membrane' : 'CO2Membrane', - 'Chiller' : 'PreMembraneChiller', - 'Crude oil dewatering' : 'CrudeOilDewatering', - 'Crude oil stabilization' : 'CrudeOilStabilization', - 'Crude oil storage' : 'CrudeOilStorage', - 'Crude oil transport' : 'CrudeOilTransport', - 'Demethanizer' : 'Demethanizer', - 'Downhole pump (Lifting)' : 'DownholePump', - 'Flaring' : 'Flaring', - 'Gas dehydration' : 'GasDehydration', - 'Gas distribution' : 'GasDistribution', - 'Gas flooding compressor' : 'GasReinjectionCompressor', - 'Gas gathering' : 'GasGathering', - 'Gas lifting compressor' : 'GasLiftingCompressor', - 'Gas storage wells' : 'StorageWell', - 'Gas transmission' : 'TransmissionCompressor', - 'HC gas reinjection compressor' : 'GasReinjectionCompressor', - 'Heavy oil dilution' : 'HeavyOilDilution', - 'Heavy oil upgrading' : 'HeavyOilUpgrading', - 'Liquefaction' : 'LNGLiquefaction', - 'Water treatment' : 'WaterTreatment', - 'Mining' : 'BitumenMining', - 'Petcoke handling and storage' : 'PetrocokeTransport', - 'Post-storage compressor' : 'PostStorageCompressor', - 'Pre-membrane compressor' : 'PreMembraneCompressor', - 'Regasification' : 'LNGRegasification', - 'Ryan-Holmes unit' : 'RyanHolmes', - 'Separation' : 'Separation', - 'Sour gas reinjection compressor' : 'SourGasCompressor', - 'Steam generation' : 'SteamGeneration', - 'Storage compressor' : 'StorageCompressor', - 'Storage separator' : 'StorageSeparator', - 'Transport' : 'LNGTransport', - 'VRU compressor' : 'VRUCompressor', - 'Venting' : 'Venting', - 'Water injection' : 'WaterInjection', - 'Exploration' : 'Exploration', - 'Drilling & Development' : 'Drilling', - 'Diluent transport' : 'HeavyOilDilution', - 'Makeup water treatment': 'WaterTreatment', - 'Produced water treatment': 'WaterTreatment', + "Acid gas removal": "AcidGasRemoval", + "CO2 gas reinjection compressor": "GasReinjectionCompressor", + "CO2 membrane": "CO2Membrane", + "Chiller": "PreMembraneChiller", + "Crude oil dewatering": "CrudeOilDewatering", + "Crude oil stabilization": "CrudeOilStabilization", + "Crude oil storage": "CrudeOilStorage", + "Crude oil transport": "CrudeOilTransport", + "Demethanizer": "Demethanizer", + "Downhole pump (Lifting)": "DownholePump", + "Flaring": "Flaring", + "Gas dehydration": "GasDehydration", + "Gas distribution": "GasDistribution", + "Gas flooding compressor": "GasReinjectionCompressor", + "Gas gathering": "GasGathering", + "Gas lifting compressor": "GasLiftingCompressor", + "Gas storage wells": "StorageWell", + "Gas transmission": "TransmissionCompressor", + "HC gas reinjection compressor": "GasReinjectionCompressor", + "Heavy oil dilution": "HeavyOilDilution", + "Heavy oil upgrading": "HeavyOilUpgrading", + "Liquefaction": "LNGLiquefaction", + "Water treatment": "WaterTreatment", + "Mining": "BitumenMining", + "Petcoke handling and storage": "PetrocokeTransport", + "Post-storage compressor": "PostStorageCompressor", + "Pre-membrane compressor": "PreMembraneCompressor", + "Regasification": "LNGRegasification", + "Ryan-Holmes unit": "RyanHolmes", + "Separation": "Separation", + "Sour gas reinjection compressor": "SourGasCompressor", + "Steam generation": "SteamGeneration", + "Storage compressor": "StorageCompressor", + "Storage separator": "StorageSeparator", + "Transport": "LNGTransport", + "VRU compressor": "VRUCompressor", + "Venting": "Venting", + "Water injection": "WaterInjection", + "Exploration": "Exploration", + "Drilling & Development": "Drilling", + "Diluent transport": "HeavyOilDilution", + "Makeup water treatment": "WaterTreatment", + "Produced water treatment": "WaterTreatment", } -dirpath = '/Users/rjp/Projects/OPGEE Python/comparison/' +dirpath = "/Users/rjp/Projects/OPGEE Python/comparison/" -status = pd.read_csv(dirpath + "status_excel_test_fields.csv", index_col='status') +status = pd.read_csv(dirpath + "status_excel_test_fields.csv", index_col="status") def read_results(filename): - df = pd.read_csv(dirpath + filename, index_col='process') - df = df.rename(index=process_translator).sort_index(axis='rows') + df = pd.read_csv(dirpath + filename, index_col="process") + df = df.rename(index=process_translator).sort_index(axis="rows") return df + def save_fig(fig, filename): - fig.savefig(dirpath + filename, bbox_inches='tight') + fig.savefig(dirpath + filename, bbox_inches="tight") + df1 = read_results("comparison.csv") df2 = read_results("test_fields_excel.csv") -good_fields = [c for c in status.columns if status[c]["Status"] == "OK" and c != 'Field 1'] +good_fields = [c for c in status.columns if status[c]["Status"] == "OK" and c != "Field 1"] python = df1.loc[df2.index] excel = df2[python.columns] @@ -75,7 +77,7 @@ def save_fig(fig, filename): excel = excel[good_fields] python = python[good_fields] -sns.set_context('talk') +sns.set_context("talk") sns.set_style("darkgrid") fig, ax = plt.subplots(figsize=(8, 8)) @@ -85,9 +87,9 @@ def save_fig(fig, filename): plt.ylabel("OPGEE v4") plt.xlabel("OPGEE v3") plt.title("V3 vs. V4 total energy consumption (mmbtu/day)", pad=20) -ax.set_aspect('equal') +ax.set_aspect("equal") -save_fig(fig, 'V3-vs-V4-total-energy-consumption.pdf') +save_fig(fig, "V3-vs-V4-total-energy-consumption.pdf") absolute_error = abs(excel - python).T @@ -103,12 +105,12 @@ def save_fig(fig, filename): with sns.plotting_context("notebook"): - fig, ax = plt.subplots(figsize=(30,5)) + fig, ax = plt.subplots(figsize=(30, 5)) absolute_error.boxplot(ax=ax) labels = ax.get_xticklabels() ax.set_xticklabels(labels, rotation=90) ax.set_ylim(0, 2000) - save_fig(fig, 'absolute-error-boxplot.pdf') + save_fig(fig, "absolute-error-boxplot.pdf") median_error = absolute_error.median().sort_values(ascending=False) print(median_error) @@ -123,9 +125,9 @@ def save_fig(fig, filename): plt.ylabel("OPGEE v4") plt.xlabel("OPGEE v3") - ax.ticklabel_format(style='sci', scilimits=(3, 3), axis='both') + ax.ticklabel_format(style="sci", scilimits=(3, 3), axis="both") - ax.set_aspect('equal') + ax.set_aspect("equal") plt.title(name + " Comparison (mmbtu/day)", pad=20) save_fig(fig, name + "_comparison.pdf") diff --git a/opgee/built_ins/compare_plugin.py b/opgee/built_ins/compare_plugin.py index b0f8d595..e2e29810 100644 --- a/opgee/built_ins/compare_plugin.py +++ b/opgee/built_ins/compare_plugin.py @@ -7,58 +7,59 @@ # Convert OPGEEv3 (Excel) process names to OPGEEv4 names process_translator = { - 'Acid gas removal' : 'AcidGasRemoval', - 'CO2 gas reinjection compressor' : 'GasReinjectionCompressor', - 'CO2 membrane' : 'CO2Membrane', - 'Chiller' : 'PreMembraneChiller', - 'Crude oil dewatering' : 'CrudeOilDewatering', - 'Crude oil stabilization' : 'CrudeOilStabilization', - 'Crude oil storage' : 'CrudeOilStorage', - 'Crude oil transport' : 'CrudeOilTransport', - 'Demethanizer' : 'Demethanizer', - 'Downhole pump (Lifting)' : 'DownholePump', - 'Drilling & Development' : 'Drilling', - 'Exploration' : 'Exploration', - 'Flaring' : 'Flaring', - 'Gas dehydration' : 'GasDehydration', - 'Gas distribution' : 'GasDistribution', - 'Gas flooding compressor' : 'GasReinjectionCompressor', - 'Gas gathering' : 'GasGathering', - 'Gas lifting compressor' : 'GasLiftingCompressor', - 'Gas storage wells' : 'StorageWell', - 'Gas transmission' : 'TransmissionCompressor', - 'HC gas reinjection compressor' : 'GasReinjectionCompressor', - 'Heavy oil dilution' : 'HeavyOilDilution', - 'Heavy oil upgrading' : 'HeavyOilUpgrading', - 'Liquefaction' : 'LNGLiquefaction', + "Acid gas removal": "AcidGasRemoval", + "CO2 gas reinjection compressor": "GasReinjectionCompressor", + "CO2 membrane": "CO2Membrane", + "Chiller": "PreMembraneChiller", + "Crude oil dewatering": "CrudeOilDewatering", + "Crude oil stabilization": "CrudeOilStabilization", + "Crude oil storage": "CrudeOilStorage", + "Crude oil transport": "CrudeOilTransport", + "Demethanizer": "Demethanizer", + "Downhole pump (Lifting)": "DownholePump", + "Drilling & Development": "Drilling", + "Exploration": "Exploration", + "Flaring": "Flaring", + "Gas dehydration": "GasDehydration", + "Gas distribution": "GasDistribution", + "Gas flooding compressor": "GasReinjectionCompressor", + "Gas gathering": "GasGathering", + "Gas lifting compressor": "GasLiftingCompressor", + "Gas storage wells": "StorageWell", + "Gas transmission": "TransmissionCompressor", + "HC gas reinjection compressor": "GasReinjectionCompressor", + "Heavy oil dilution": "HeavyOilDilution", + "Heavy oil upgrading": "HeavyOilUpgrading", + "Liquefaction": "LNGLiquefaction", # 'Makeup water treatment' : '', # 'Makeup watter treatment' : '', # SPELLING ERROR - 'Mining' : 'BitumenMining', - 'Petcoke handling and storage' : 'PetrocokeTransport', - 'Post-storage compressor' : 'PostStorageCompressor', - 'Pre-membrane compressor' : 'PreMembraneCompressor', - 'Regasification' : 'LNGRegasification', - 'Ryan-Holmes unit' : 'RyanHolmes', - 'Separation' : 'Separation', - 'Sour gas reinjection compressor' : 'SourGasCompressor', - 'Steam generation' : 'SteamGeneration', - 'Storage compressor' : 'StorageCompressor', - 'Storage separator' : 'StorageSeparator', - 'Transport' : 'LNGTransport', - 'VRU compressor' : 'VRUCompressor', - 'Venting' : 'Venting', - 'Water injection' : 'WaterInjection', - 'Water treatment' : 'WaterTreatment', + "Mining": "BitumenMining", + "Petcoke handling and storage": "PetrocokeTransport", + "Post-storage compressor": "PostStorageCompressor", + "Pre-membrane compressor": "PreMembraneCompressor", + "Regasification": "LNGRegasification", + "Ryan-Holmes unit": "RyanHolmes", + "Separation": "Separation", + "Sour gas reinjection compressor": "SourGasCompressor", + "Steam generation": "SteamGeneration", + "Storage compressor": "StorageCompressor", + "Storage separator": "StorageSeparator", + "Transport": "LNGTransport", + "VRU compressor": "VRUCompressor", + "Venting": "Venting", + "Water injection": "WaterInjection", + "Water treatment": "WaterTreatment", } DefaultCount = 0 DefaultFractionalDiff = 0.01 + class ComparisonStatus(Enum): - GOOD = 0, - PROCESS_MISMATCH = 1, - FIELD_MISMATCH = 2, + GOOD = (0,) + PROCESS_MISMATCH = (1,) + FIELD_MISMATCH = (2,) VALUE_MISMATCH = 3 @@ -67,8 +68,8 @@ def compare(file1, file2, count=DefaultCount, max_diff=DefaultFractionalDiff, ve import pandas as pd def read_results(filename): - df = pd.read_csv(filename, index_col='process') - df = df.rename(index=process_translator).sort_index(axis='rows') + df = pd.read_csv(filename, index_col="process") + df = df.rename(index=process_translator).sort_index(axis="rows") return df df1 = read_results(file1) @@ -124,7 +125,7 @@ def compare_sets(iter1, iter2, name): na1 = values1.isna() na2 = values2.isna() - comp = (na1 == na2) + comp = na1 == na2 if not comp.all(): status = ComparisonStatus.VALUE_MISMATCH print(f"Field '{field}' has NA values in different locations in the two files") @@ -149,40 +150,55 @@ def compare_sets(iter1, iter2, name): return status + class CompareCommand(SubcommandABC): def __init__(self, subparsers): - kwargs = {'help' : '''Compare result CSV files''', - 'description' : '''Compare CSV files with fields in columns and processes in row, + kwargs = { + "help": """Compare result CSV files""", + "description": """Compare CSV files with fields in columns and processes in row, with the value either blank for disabled processes, or the total energy use per day by process, after running the model. Mainly used for testing against OPGEEv3. Note that CSV files for comparison can be generated using - "opg run --comparisonCSV filename".'''} + "opg run --comparisonCSV filename".""", + } - super().__init__('compare', subparsers, kwargs) + super().__init__("compare", subparsers, kwargs) def addArgs(self, parser): - ''' + """ Process the command-line arguments for this sub-command - ''' - parser.add_argument('file1', - help='''A CSV file containing results for comparison against "file2"''') - - parser.add_argument('file2', - help='''A CSV file containing results for comparison against "file1"''') - - parser.add_argument('-n', '--count', type=int, default=DefaultCount, - help=f'''The number of fields to compare. Default is {DefaultCount}, which - means compare all fields.''') - - parser.add_argument('-m', '--max-diff', type=float, default=DefaultFractionalDiff, - help=f'''The maximum (fractional) difference in values for "file2" + """ + parser.add_argument("file1", help='''A CSV file containing results for comparison against "file2"''') + + parser.add_argument("file2", help='''A CSV file containing results for comparison against "file1"''') + + parser.add_argument( + "-n", + "--count", + type=int, + default=DefaultCount, + help=f"""The number of fields to compare. Default is {DefaultCount}, which + means compare all fields.""", + ) + + parser.add_argument( + "-m", + "--max-diff", + type=float, + default=DefaultFractionalDiff, + help=f"""The maximum (fractional) difference in values for "file2" relative to "file1" that are deemed "approximately the same". Default is {DefaultFractionalDiff}, - i.e., (file2_value - file1_value) / file1_value <= {DefaultFractionalDiff}''') - - parser.add_argument('-v', '--verbose', action='store_true', - help='''Report all value differences. By default, only field differences - are reported.''') + i.e., (file2_value - file1_value) / file1_value <= {DefaultFractionalDiff}""", + ) + + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="""Report all value differences. By default, only field differences + are reported.""", + ) return parser diff --git a/opgee/built_ins/config_plugin.py b/opgee/built_ins/config_plugin.py index 49830cc3..e105afe4 100644 --- a/opgee/built_ins/config_plugin.py +++ b/opgee/built_ins/config_plugin.py @@ -1,38 +1,63 @@ -''' +""" .. codeauthor:: Richard Plevin .. Copyright (c) 2016 Richard Plevin See the https://opensource.org/licenses/MIT for license details. -''' +""" + from ..error import OpgeeException, CommandlineError from ..subcommand import SubcommandABC, clean_help + class ConfigCommand(SubcommandABC): def __init__(self, subparsers): - kwargs = {'help' : '''List the values of configuration variables from - ~/opgee.cfg configuration file.'''} + kwargs = { + "help": """List the values of configuration variables from + ~/opgee.cfg configuration file.""" + } - super(ConfigCommand, self).__init__('config', subparsers, kwargs, group='utils') + super(ConfigCommand, self).__init__("config", subparsers, kwargs, group="utils") def addArgs(self, parser): - parser.add_argument('-d', '--useDefault', action='store_true', - help=clean_help('Indicates to operate on the DEFAULT section rather ' - 'than the project section.')) - - parser.add_argument('-e', '--edit', action='store_true', - help=clean_help('Edit the configuration file. The command given by the ' - 'value of config variable OPGEE.TextEditor is run with the ' - 'opgee.cfg file as an argument.')) - - parser.add_argument('name', nargs='?', default='', - help=clean_help('Show the names and values of all parameters whose ' - 'name contains the given value. The match is case-insensitive. ' - 'If not specified, all variable values are shown.')) - - parser.add_argument('-x', '--exact', action='store_true', - help=clean_help('Treat the text not as a substring to match, but ' - 'as the name of a specific variable. Match is case-sensitive. ' - 'Prints only the value.')) + parser.add_argument( + "-d", + "--useDefault", + action="store_true", + help=clean_help("Indicates to operate on the DEFAULT section rather than the project section."), + ) + + parser.add_argument( + "-e", + "--edit", + action="store_true", + help=clean_help( + "Edit the configuration file. The command given by the " + "value of config variable OPGEE.TextEditor is run with the " + "opgee.cfg file as an argument." + ), + ) + + parser.add_argument( + "name", + nargs="?", + default="", + help=clean_help( + "Show the names and values of all parameters whose " + "name contains the given value. The match is case-insensitive. " + "If not specified, all variable values are shown." + ), + ) + + parser.add_argument( + "-x", + "--exact", + action="store_true", + help=clean_help( + "Treat the text not as a substring to match, but " + "as the name of a specific variable. Match is case-sensitive. " + "Prints only the value." + ), + ) return parser def run(self, args, tool): @@ -41,8 +66,8 @@ def run(self, args, tool): from ..config import getParam, _ConfigParser, USR_CONFIG_FILE if args.edit: - editor = getParam('OPGEE.TextEditor') - home = getParam('Home') + editor = getParam("OPGEE.TextEditor") + home = getParam("Home") cmd = f"{editor} {home}/{USR_CONFIG_FILE}" print(cmd) exitStatus = subprocess.call(cmd, shell=True) @@ -50,12 +75,12 @@ def run(self, args, tool): raise OpgeeException(f"TextEditor command '{cmd}' exited with status {exitStatus}\n") return - section = 'DEFAULT' if args.useDefault else getParam('OPGEE.DefaultProject') + section = "DEFAULT" if args.useDefault else getParam("OPGEE.DefaultProject") if not section: raise CommandlineError("Project was not specified and OPGEE.DefaultProject is not set") - if section != 'DEFAULT' and not _ConfigParser.has_section(section): + if section != "DEFAULT" and not _ConfigParser.has_section(section): raise CommandlineError(f"Unknown configuration file section '{section}'") if args.name and args.exact: @@ -65,7 +90,7 @@ def run(self, args, tool): return # if no name is given, the pattern matches all variables - pattern = re.compile('.*' + args.name + '.*', re.IGNORECASE) + pattern = re.compile(".*" + args.name + ".*", re.IGNORECASE) print(f"[{section}]") for name, value in sorted(_ConfigParser.items(section)): diff --git a/opgee/built_ins/csv2xml_plugin.py b/opgee/built_ins/csv2xml_plugin.py index 9f0c6a61..91d5d2c2 100644 --- a/opgee/built_ins/csv2xml_plugin.py +++ b/opgee/built_ins/csv2xml_plugin.py @@ -4,13 +4,15 @@ .. Copyright (c) 2021 Richard Plevin See the https://opensource.org/licenses/MIT for license details. """ + from pathlib import Path from ..subcommand import SubcommandABC from ..log import getLogger _logger = getLogger(__name__) -DEFAULT_MODIFIES = 'template' +DEFAULT_MODIFIES = "template" + def read_fields(csv_path, from_package=False, skip_fields=None): """ @@ -34,24 +36,25 @@ def read_fields(csv_path, from_package=False, skip_fields=None): # If we decide to translate attribute names here, add mappings to this dict v3_to_v4 = { - 'old name': 'new name', - 'old name2': 'new name2', + "old name": "new name", + "old name2": "new name2", } df.rename(index=v3_to_v4, inplace=True) if skip_fields: - df.drop(skip_fields, axis='columns', inplace=True) + df.drop(skip_fields, axis="columns", inplace=True) # Pull dtypes out as a separate series - dtypes = df['Type'] - df.drop('Type', axis='columns', inplace=True) + dtypes = df["Type"] + df.drop("Type", axis="columns", inplace=True) return (df, dtypes) -def import_fields(csv_path, xml_path, analysis_name, count=0, skip_fields=None, - modifies=DEFAULT_MODIFIES, from_package=False): +def import_fields( + csv_path, xml_path, analysis_name, count=0, skip_fields=None, modifies=DEFAULT_MODIFIES, from_package=False +): """ Import Field information from a CSV file. @@ -66,63 +69,91 @@ def import_fields(csv_path, xml_path, analysis_name, count=0, skip_fields=None, """ from ..xml_utils import attr_to_xml - fields, dtypes = read_fields(csv_path, - from_package=from_package, - skip_fields=skip_fields) + fields, dtypes = read_fields(csv_path, from_package=from_package, skip_fields=skip_fields) if count: fields = fields[fields.columns[:count]] if not analysis_name: p = Path(csv_path) - analysis_name = p.name[:-(len(p.suffix))] + analysis_name = p.name[: -(len(p.suffix))] attr_to_xml(fields, dtypes, xml_path, analysis_name, modifies=modifies) class Csv2XmlCommand(SubcommandABC): - def __init__(self, subparsers): - kwargs = {'help' : '''Convert various CSV files to their corresponding XML representation.'''} - super().__init__('csv2xml', subparsers, kwargs, group='project') + kwargs = {"help": """Convert various CSV files to their corresponding XML representation."""} + super().__init__("csv2xml", subparsers, kwargs, group="project") def addArgs(self, parser): from ..utils import ParseCommaList - parser.add_argument('-a', '--analysis', - help='''The name to give the element. Default is the file basename - with the extension removed.''') - - default_format = 'fields' - format_choices = [default_format, 'attributes'] # extend as needed - parser.add_argument('-f', '--format', choices=format_choices, default=default_format, - help=f'''Which type of conversion to perform. Default is "{default_format}".''') - - parser.add_argument('-m', '--modifies', default=DEFAULT_MODIFIES, - help=f'''The name to use in the element. - Default is "{DEFAULT_MODIFIES}"''') - - parser.add_argument('-i', '--inputCSV', default=None, required=True, - help='''The pathname of the file to import''') - - parser.add_argument('-n', '--count', type=int, default=0, - help='''The number of rows to import from the CSV file. - Default is 0, which means import all rows.''') - - parser.add_argument('-o', '--outputXML', default=None, - help='''The pathname of the XML file to create. Default is the same + parser.add_argument( + "-a", + "--analysis", + help="""The name to give the element. Default is the file basename + with the extension removed.""", + ) + + default_format = "fields" + format_choices = [default_format, "attributes"] # extend as needed + parser.add_argument( + "-f", + "--format", + choices=format_choices, + default=default_format, + help=f'''Which type of conversion to perform. Default is "{default_format}".''', + ) + + parser.add_argument( + "-m", + "--modifies", + default=DEFAULT_MODIFIES, + help=f'''The name to use in the element. + Default is "{DEFAULT_MODIFIES}"''', + ) + + parser.add_argument( + "-i", "--inputCSV", default=None, required=True, help="""The pathname of the file to import""" + ) + + parser.add_argument( + "-n", + "--count", + type=int, + default=0, + help="""The number of rows to import from the CSV file. + Default is 0, which means import all rows.""", + ) + + parser.add_argument( + "-o", + "--outputXML", + default=None, + help="""The pathname of the XML file to create. Default is the same name as the input CSV file, but with the extension changed to "xml". Refuses - to overwrite an existing file unless --overwrite is specified.''') - - parser.add_argument('--overwrite', action='store_true', - help='''If set, allows existing XML file to be overwritten.''') - - parser.add_argument('-p', '--fromPackage', action='store_true', - help='''If specified, the inputCSV argument is treated as relative to - the opgee package and loaded as an internal resource.''') - - parser.add_argument('-s', '--skipFields', action=ParseCommaList, - help='''Comma-delimited list of field names to exclude from analysis''') + to overwrite an existing file unless --overwrite is specified.""", + ) + + parser.add_argument( + "--overwrite", action="store_true", help="""If set, allows existing XML file to be overwritten.""" + ) + + parser.add_argument( + "-p", + "--fromPackage", + action="store_true", + help="""If specified, the inputCSV argument is treated as relative to + the opgee package and loaded as an internal resource.""", + ) + + parser.add_argument( + "-s", + "--skipFields", + action=ParseCommaList, + help="""Comma-delimited list of field names to exclude from analysis""", + ) return parser @@ -131,19 +162,28 @@ def run(self, args, tool): input_csv = args.inputCSV if input_csv is None: - raise CommandlineError('Required input CSV is missing') + raise CommandlineError("Required input CSV is missing") input_path = Path(input_csv) from_package = args.fromPackage if not input_path.exists() and not from_package: - raise CommandlineError(f"Input file '{input_path}' does not exist. (Hint: do you need to specify --fromPackage?)") + raise CommandlineError( + f"Input file '{input_path}' does not exist. (Hint: do you need to specify --fromPackage?)" + ) output_xml = args.outputXML - output_path = Path(output_xml) if output_xml else input_path.with_stem('xml') + output_path = Path(output_xml) if output_xml else input_path.with_stem("xml") if output_path.exists() and not args.overwrite: raise CommandlineError(f"Refusing to overwrite '{output_path}'; use --overwrite to override this.") - import_fields(input_csv, output_xml, args.analysis, count=args.count, - modifies=args.modifies, from_package=from_package, skip_fields=args.skipFields) + import_fields( + input_csv, + output_xml, + args.analysis, + count=args.count, + modifies=args.modifies, + from_package=from_package, + skip_fields=args.skipFields, + ) diff --git a/opgee/built_ins/gensim_plugin.py b/opgee/built_ins/gensim_plugin.py index affa9359..adfb109d 100644 --- a/opgee/built_ins/gensim_plugin.py +++ b/opgee/built_ins/gensim_plugin.py @@ -13,49 +13,73 @@ _logger = getLogger(__name__) -class GensimCommand(SubcommandABC): +class GensimCommand(SubcommandABC): def __init__(self, subparsers): - kwargs = {'help' : 'Generate the simulation directory and trial data for a Monte Carlo simulation.'} - super().__init__('gensim', subparsers, kwargs) + kwargs = {"help": "Generate the simulation directory and trial data for a Monte Carlo simulation."} + super().__init__("gensim", subparsers, kwargs) def addArgs(self, parser): from ..utils import ParseCommaList - parser.add_argument('-a', '--analysis', - help='''The name of the analysis for which to generate a simulation''') - - parser.add_argument('-d', '--distributions', - help='''The path to a CSV file with distribution definitions. If omitted, the - built-in file etc/parameter_distributions.csv is used.''') - - parser.add_argument('-f', '--fields', action=ParseCommaList, default=None, - help='''Generate trial data for the specified field or fields only. Argument + parser.add_argument("-a", "--analysis", help="""The name of the analysis for which to generate a simulation""") + + parser.add_argument( + "-d", + "--distributions", + help="""The path to a CSV file with distribution definitions. If omitted, the + built-in file etc/parameter_distributions.csv is used.""", + ) + + parser.add_argument( + "-f", + "--fields", + action=ParseCommaList, + default=None, + help="""Generate trial data for the specified field or fields only. Argument may be a comma-delimited list of Field names. Otherwise trial data is generated - for all fields defined in the analysis.''') - - parser.add_argument('-m', '--model-file', action='append', - help='''XML model definition files to load. If --no-default-model is *not* specified, + for all fields defined in the analysis.""", + ) + + parser.add_argument( + "-m", + "--model-file", + action="append", + help="""XML model definition files to load. If --no-default-model is *not* specified, the built-in files etc/opgee.xml and etc/attributes.xml are loaded first, and the XML files specified here will be merged with these. If --no-default-model is specified, - only the given files are loaded; they are merged in the order stated.''') - - parser.add_argument('-n', '--no-default-model', action='store_true', - help='''Don't load the built-in opgee.xml model definition.''') - - parser.add_argument('--overwrite', action='store_true', - help='''DELETE and recreate the simulation directory.''') - - parser.add_argument('-s', '--simulation-dir', required=True, - help='''The top-level directory to create for this simulation "package". + only the given files are loaded; they are merged in the order stated.""", + ) + + parser.add_argument( + "-n", + "--no-default-model", + action="store_true", + help="""Don't load the built-in opgee.xml model definition.""", + ) + + parser.add_argument( + "--overwrite", action="store_true", help="""DELETE and recreate the simulation directory.""" + ) + + parser.add_argument( + "-s", + "--simulation-dir", + required=True, + help="""The top-level directory to create for this simulation "package". If the simulation directory already exists and you must specify –-overwrite, - or gensim will refuse to overwrite the directory.''') - - parser.add_argument('-t', '--trials', type=int, default=0, - help='''The number of trials to create for this simulation (REQUIRED).''') + or gensim will refuse to overwrite the directory.""", + ) - return parser # for auto-doc generation + parser.add_argument( + "-t", + "--trials", + type=int, + default=0, + help="""The number of trials to create for this simulation (REQUIRED).""", + ) + return parser # for auto-doc generation def run(self, args, tool): from ..error import McsUserError, CommandlineError @@ -68,7 +92,9 @@ def run(self, args, tool): raise McsUserError("Trials argument must be an integer > 0") if not (use_default_model or model_files): - raise CommandlineError("No model to run: the --model-file option was not used and --no-default-model was specified.") + raise CommandlineError( + "No model to run: the --model-file option was not used and --no-default-model was specified." + ) # Deprecated (soon) in favor of XML parameter list read_distributions(pathname=args.distributions) @@ -79,7 +105,12 @@ def run(self, args, tool): analysis_name = args.analysis sim_dir = args.simulation_dir - Simulation.new(sim_dir, model_files, analysis_name, args.trials, - field_names=args.fields, - overwrite=args.overwrite, - use_default_model=use_default_model) + Simulation.new( + sim_dir, + model_files, + analysis_name, + args.trials, + field_names=args.fields, + overwrite=args.overwrite, + use_default_model=use_default_model, + ) diff --git a/opgee/built_ins/graph_plugin.py b/opgee/built_ins/graph_plugin.py index ea3ec217..dff8a433 100644 --- a/opgee/built_ins/graph_plugin.py +++ b/opgee/built_ins/graph_plugin.py @@ -6,52 +6,80 @@ .. Copyright (c) 2021 Richard Plevin See the https://opensource.org/licenses/MIT for license details. """ + from ..subcommand import SubcommandABC, clean_help from ..log import getLogger _logger = getLogger(__name__) + class GraphCommand(SubcommandABC): def __init__(self, subparsers): - kwargs = {'help' : '''Create graphs of various aspects of OPGEE models.'''} - super().__init__('graph', subparsers, kwargs, group='project') + kwargs = {"help": """Create graphs of various aspects of OPGEE models."""} + super().__init__("graph", subparsers, kwargs, group="project") def addArgs(self, parser): - class_choices = ['all', 'core'] - parser.add_argument('-c', '--classes', choices=class_choices, - help=clean_help('''Graph the class structure, either "all", including all defined + class_choices = ["all", "core"] + parser.add_argument( + "-c", + "--classes", + choices=class_choices, + help=clean_help("""Graph the class structure, either "all", including all defined Process subclasses (of which there are dozens) or only the "core" classes excluding - Process subclasses.''')) + Process subclasses."""), + ) - parser.add_argument('-C', '--classes-output', - help=clean_help('''The pathname of the image file to create for classes. If none + parser.add_argument( + "-C", + "--classes-output", + help=clean_help("""The pathname of the image file to create for classes. If none is specified, and the code is running in a jupyter notebook, the image is - displayed inline. (Implies --classes.)''')) + displayed inline. (Implies --classes.)"""), + ) - parser.add_argument('-f', '--field', - help=clean_help('''Graph the process network for the named field.''')) + parser.add_argument("-f", "--field", help=clean_help("""Graph the process network for the named field.""")) - parser.add_argument('-F', '--field-output', - help=clean_help('''The pathname of the image file to create with process connections + parser.add_argument( + "-F", + "--field-output", + help=clean_help("""The pathname of the image file to create with process connections for the field specified in the --field argument. If no file is specified, and the code - is running in a jupyter notebook, the image is displayed inline.''')) - - parser.add_argument('-l', '--levels', type=int, default=0, - help=clean_help('''How many levels to descend when graphing the model hierarchy''')) - - parser.add_argument('-m', '--model-hierarchy', action='store_true', - help=clean_help('''Graph the model container hierarchy.''')) - - parser.add_argument('-M', '--hierarchy-output', - help=clean_help('''The pathname of the image file to create for classes. If none + is running in a jupyter notebook, the image is displayed inline."""), + ) + + parser.add_argument( + "-l", + "--levels", + type=int, + default=0, + help=clean_help("""How many levels to descend when graphing the model hierarchy"""), + ) + + parser.add_argument( + "-m", "--model-hierarchy", action="store_true", help=clean_help("""Graph the model container hierarchy.""") + ) + + parser.add_argument( + "-M", + "--hierarchy-output", + help=clean_help("""The pathname of the image file to create for classes. If none is specified, and the code is running in a jupyter notebook, the image is - displayed inline. (Implies --model_hierarchy.)''')) - - parser.add_argument('-n', '--no-default-model', action='store_true', - help=clean_help('''Don't load the built-in opgee.xml model definition.''')) - - parser.add_argument('-x', '--xml_file', default=None, - help="""The path to the model XML file to load. By default, the built-in opgee.xml is loaded.""") + displayed inline. (Implies --model_hierarchy.)"""), + ) + + parser.add_argument( + "-n", + "--no-default-model", + action="store_true", + help=clean_help("""Don't load the built-in opgee.xml model definition."""), + ) + + parser.add_argument( + "-x", + "--xml_file", + default=None, + help="""The path to the model XML file to load. By default, the built-in opgee.xml is loaded.""", + ) return parser def run(self, args, tool): @@ -68,7 +96,7 @@ def run(self, args, tool): write_model_diagram(model, args.hierarchy_output, levels=args.levels) if args.classes or args.classes_output: - show_process_subclasses = (args.classes == 'all') + show_process_subclasses = args.classes == "all" write_class_diagram(args.classes_output, show_process_subclasses=show_process_subclasses) if args.field: diff --git a/opgee/built_ins/gui_plugin.py b/opgee/built_ins/gui_plugin.py index d41cb264..f1f3ea7d 100644 --- a/opgee/built_ins/gui_plugin.py +++ b/opgee/built_ins/gui_plugin.py @@ -1,51 +1,82 @@ from ..subcommand import SubcommandABC -DFLT_FIELD = 'test' -DFLT_ANALYSIS = 'test' +DFLT_FIELD = "test" +DFLT_ANALYSIS = "test" class GUICommand(SubcommandABC): - def __init__(self, subparsers): - kwargs = {'help' : '''Run the OPGEE Graphical User Interface'''} + kwargs = {"help": """Run the OPGEE Graphical User Interface"""} - super(GUICommand, self).__init__('gui', subparsers, kwargs) + super(GUICommand, self).__init__("gui", subparsers, kwargs) def addArgs(self, parser): - parser.add_argument('-d', '--debug', action='store_true', - help='''Enable debug mode in the dash server''') - - parser.add_argument('-H', '--host', default='127.0.0.1', - help='''Set the host address to serve the application on. Default is localhost (127.0.0.1).''') - - parser.add_argument('-P', '--port', default=8050, type=int, - help='''Set the port to serve the application on. Default is 8050.''') - - parser.add_argument('-a', '--analysis', default=DFLT_ANALYSIS, - help=f'''The analysis to run. Default (for testing) is "{DFLT_ANALYSIS}"''') - - parser.add_argument('-f', '--field', default=DFLT_FIELD, - help=f'''The field to display. Default (for testing) is "{DFLT_FIELD}"''') - - parser.add_argument('-m', '--model-file', default=None, - help='''The OPGEE model XML file to read. By default it is merged with the built-in + parser.add_argument("-d", "--debug", action="store_true", help="""Enable debug mode in the dash server""") + + parser.add_argument( + "-H", + "--host", + default="127.0.0.1", + help="""Set the host address to serve the application on. Default is localhost (127.0.0.1).""", + ) + + parser.add_argument( + "-P", + "--port", + default=8050, + type=int, + help="""Set the port to serve the application on. Default is 8050.""", + ) + + parser.add_argument( + "-a", + "--analysis", + default=DFLT_ANALYSIS, + help=f'''The analysis to run. Default (for testing) is "{DFLT_ANALYSIS}"''', + ) + + parser.add_argument( + "-f", + "--field", + default=DFLT_FIELD, + help=f'''The field to display. Default (for testing) is "{DFLT_FIELD}"''', + ) + + parser.add_argument( + "-m", + "--model-file", + default=None, + help="""The OPGEE model XML file to read. By default it is merged with the built-in model file, "etc/opgee.xml". If no model file is specified, etc/opgee.xml is read. - Use --no-default-model to avoid reading the default model file.''') + Use --no-default-model to avoid reading the default model file.""", + ) - parser.add_argument('-n', '--no-default-model', action='store_true', - help='''Don't load the built-in opgee.xml model definition.''') + parser.add_argument( + "-n", + "--no-default-model", + action="store_true", + help="""Don't load the built-in opgee.xml model definition.""", + ) # TBD: apparently action=argparse.BooleanOptionalAction requires py 3.9 - parser.add_argument('--add-stream-components', action='store_true', - help='''Include additional stream components listed in config variable "OPGEE.StreamComponents"''') - - parser.add_argument('--use-class-path', action='store_true', - help='''Search for Process subclasses in Python files found in the path(s) listed in config variable "OPGEE.ClassPath"''') + parser.add_argument( + "--add-stream-components", + action="store_true", + help='''Include additional stream components listed in config variable "OPGEE.StreamComponents"''', + ) + + parser.add_argument( + "--use-class-path", + action="store_true", + help='''Search for Process subclasses in Python files found in the path(s) listed in config variable "OPGEE.ClassPath"''', + ) return parser def run(self, args, tool): from ..gui.app import main + main(args) + PluginClass = GUICommand diff --git a/opgee/built_ins/merge_plugin.py b/opgee/built_ins/merge_plugin.py index 941926be..f2bac6b8 100644 --- a/opgee/built_ins/merge_plugin.py +++ b/opgee/built_ins/merge_plugin.py @@ -6,6 +6,7 @@ .. Copyright (c) 2021,2022 Richard Plevin See the https://opensource.org/licenses/MIT for license details. """ + from pathlib import Path from ..subcommand import SubcommandABC from ..log import getLogger @@ -14,29 +15,40 @@ class MergeCommand(SubcommandABC): - def __init__(self, subparsers): - kwargs = {'help' : '''Merge two or more OPGEE XML files.'''} - super().__init__('merge', subparsers, kwargs, group='project') + kwargs = {"help": """Merge two or more OPGEE XML files."""} + super().__init__("merge", subparsers, kwargs, group="project") def addArgs(self, parser): - parser.add_argument('pathnames', nargs='*', - help="""Pathnames of the XML input files to be merged, in the order specified. By default, + parser.add_argument( + "pathnames", + nargs="*", + help="""Pathnames of the XML input files to be merged, in the order specified. By default, the built-in {opgee}/etc/opgee.xml is included as the base file to merge with. To - override this, use the -n/--no-default-model option.""") - - parser.add_argument('-o', '--outputXML', default=None, - help='''The pathname of the XML file to create. Default is the same + override this, use the -n/--no-default-model option.""", + ) + + parser.add_argument( + "-o", + "--outputXML", + default=None, + help="""The pathname of the XML file to create. Default is the same name as the input CSV file, but with the extension changed to "xml". Refuses to overwrite an existing file unless --overwrite is specified. If an output - XML file is not specified, the merged XML is written to stdout.''') + XML file is not specified, the merged XML is written to stdout.""", + ) - parser.add_argument('-n', '--no-default-model', action='store_true', - help='''Don't use the built-in {opgee}/etc/opgee.xml model file as the base - file to merge with.''') + parser.add_argument( + "-n", + "--no-default-model", + action="store_true", + help="""Don't use the built-in {opgee}/etc/opgee.xml model file as the base + file to merge with.""", + ) - parser.add_argument('--overwrite', action='store_true', - help='''If set, allows existing XML file to be overwritten.''') + parser.add_argument( + "--overwrite", action="store_true", help="""If set, allows existing XML file to be overwritten.""" + ) return parser @@ -46,7 +58,7 @@ def run(self, args, tool): pathnames = args.pathnames if not pathnames: - raise CommandlineError('Missing required input XML file(s)') + raise CommandlineError("Missing required input XML file(s)") if args.outputXML: output_path = Path(args.outputXML) @@ -60,9 +72,11 @@ def run(self, args, tool): output_path = None use_default_model = not args.no_default_model - mf = ModelFile(pathnames, use_default_model=use_default_model, - instantiate_model=False, save_to_path=output_path) + mf = ModelFile( + pathnames, use_default_model=use_default_model, instantiate_model=False, save_to_path=output_path + ) if not output_path: from lxml import etree + etree.dump(mf.root, pretty_print=True) diff --git a/opgee/built_ins/run_plugin.py b/opgee/built_ins/run_plugin.py index 479e59d6..560e80ed 100644 --- a/opgee/built_ins/run_plugin.py +++ b/opgee/built_ins/run_plugin.py @@ -4,6 +4,7 @@ .. Copyright (c) 2021 Richard Plevin and Stanford University See the https://opensource.org/licenses/MIT for license details. """ + from ..subcommand import SubcommandABC from opgee.constants import SIMPLE_RESULT from ..log import getLogger @@ -34,7 +35,7 @@ def addArgs(self, parser): partition = getParam("SLURM.Partition") min_per_task = getParam("SLURM.MinutesPerTask") - packet_size = getParamAsInt("OPGEE.MaxTrialsPerPacket") # 10 default + packet_size = getParamAsInt("OPGEE.MaxTrialsPerPacket") # 10 default # User can specify fields by name, or the number of fields to run MCS for, but not both. group = parser.add_mutually_exclusive_group() @@ -146,9 +147,7 @@ def addArgs(self, parser): (Mutually exclusive with -f/--fields.)""", ) - parser.add_argument( - "-o", "--output-dir", help="""Write output to the specified directory.""" - ) + parser.add_argument("-o", "--output-dir", help="""Write output to the specified directory.""") parser.add_argument( "-p", @@ -159,15 +158,17 @@ def addArgs(self, parser): Ignored if --cluster-type=slurm is not specified.""", ) - parser.add_argument( - "-P", - "--packet-size", - type=positive_int, - default=packet_size, - help=f"""Divide runs for a single field into groups of this size + ( + parser.add_argument( + "-P", + "--packet-size", + type=positive_int, + default=packet_size, + help=f"""Divide runs for a single field into groups of this size to run serially on a single worker. Default is the value of configuration file parameter "OPGEE.MaxTrialsPerPacket", currently {packet_size}.""", - ), + ), + ) parser.add_argument( "-r", @@ -261,9 +262,7 @@ def run(self, args, tool): if sim_dir: metadata = Simulation.read_metadata(sim_dir) field_names = field_names or metadata["field_names"] - trial_nums = ( - range(metadata["trials"]) if trials == "all" else parseTrialString(trials) - ) + trial_nums = range(metadata["trials"]) if trials == "all" else parseTrialString(trials) model_xml_file = model_xml_file or model_file_path(sim_dir) output_dir = f"{sim_dir}/results" @@ -277,13 +276,11 @@ def run(self, args, tool): if not output_dir: raise CommandlineError("Non-MCS runs must specify -o/--output-dir") - setParam("OPGEE.output_dir", output_dir ) + setParam("OPGEE.output_dir", output_dir) mkdirs(output_dir) if not (field_names or analysis_names): - raise CommandlineError( - "Must indicate one or more fields or analyses to run" - ) + raise CommandlineError("Must indicate one or more fields or analyses to run") if not (use_default_model or model_files): raise CommandlineError( @@ -291,25 +288,17 @@ def run(self, args, tool): ) # TBD: unclear if this is necessary - setParam( - "OPGEE.XmlSavePathname", "" - ) # avoid writing /tmp/final.xml since no need + setParam("OPGEE.XmlSavePathname", "") # avoid writing /tmp/final.xml since no need # TBD: decide if we need to support multiple analysis names (only 1st is used currently) - analysis_name = ( - analysis_names[0] - if analysis_names - else model_analysis_names(model_xml_file)[0] - ) + analysis_name = analysis_names[0] if analysis_names else model_analysis_names(model_xml_file)[0] all_fields = fields_for_analysis(model_xml_file, analysis_name) field_names = [name.strip() for name in field_names] if field_names else None if field_names: unknown = set(field_names) - set(all_fields) if unknown: - raise CommandlineError( - f"Fields not found in {model_xml_file}: {unknown}" - ) + raise CommandlineError(f"Fields not found in {model_xml_file}: {unknown}") else: field_names = all_fields @@ -322,9 +311,7 @@ def run(self, args, tool): field_names = field_names[:num_fields] if skip_fields: - field_names = [ - name.strip() for name in field_names if name not in skip_fields - ] + field_names = [name.strip() for name in field_names if name not in skip_fields] mgr = Manager(cluster_type=args.cluster_type) @@ -332,13 +319,9 @@ def run(self, args, tool): if num_tasks is None: num_tasks = len(field_names) - packets = TrialPacket.packetize( - sim_dir, trial_nums, field_names, packet_size - ) + packets = TrialPacket.packetize(sim_dir, trial_nums, field_names, packet_size) else: - packets = FieldPacket.packetize( - model_xml_file, analysis_name, field_names, packet_size - ) + packets = FieldPacket.packetize(model_xml_file, analysis_name, field_names, packet_size) results_list = [] save_batches = batch_size is not None @@ -350,7 +333,6 @@ def run(self, args, tool): num_engines=num_tasks, minutes_per_task=minutes_per_task, ): - # Save to disk, optionally in batches. results_list.append(results) if save_batches: @@ -360,9 +342,7 @@ def run(self, args, tool): batch_num += 1 if results_list: - save_results( - results_list, output_dir, batch_num=batch_num if batch_size else None - ) + save_results(results_list, output_dir, batch_num=batch_num if batch_size else None) if collect and save_batches: # Combine partial result files into one diff --git a/opgee/built_ins/update_plugin.py b/opgee/built_ins/update_plugin.py index 4fd64cd5..76da5b17 100644 --- a/opgee/built_ins/update_plugin.py +++ b/opgee/built_ins/update_plugin.py @@ -9,30 +9,36 @@ _logger = getLogger(__name__) -class UpdateCommand(SubcommandABC): +class UpdateCommand(SubcommandABC): def __init__(self, subparsers): - kwargs = {'help' : 'Update model XML files with Fields inside Analyses to use FieldRef instead.'} - super().__init__('update', subparsers, kwargs) + kwargs = {"help": "Update model XML files with Fields inside Analyses to use FieldRef instead."} + super().__init__("update", subparsers, kwargs) def addArgs(self, parser): - parser.add_argument('model_file', - help='''The model XML file to operate on.''') + parser.add_argument("model_file", help="""The model XML file to operate on.""") - parser.add_argument('-o', '--output', - help='''The path of the updated model XML file. If not specified, + parser.add_argument( + "-o", + "--output", + help="""The path of the updated model XML file. If not specified, the output name will be the input name with "-updated" added before - the ".xml" extension.''') - - parser.add_argument('--overwrite', action='store_true', - help='Force overwrite of an existing model XML. Cannot be the same' - 'file as the input.') + the ".xml" extension.""", + ) - parser.add_argument('--ignore-duplicates', action='store_true', - help='Ignore duplicate field names in the input file, saving only the final one.') + parser.add_argument( + "--overwrite", + action="store_true", + help="Force overwrite of an existing model XML. Cannot be the samefile as the input.", + ) - return parser # used for auto-doc generation + parser.add_argument( + "--ignore-duplicates", + action="store_true", + help="Ignore duplicate field names in the input file, saving only the final one.", + ) + return parser # used for auto-doc generation def run(self, args, tool): from collections import OrderedDict @@ -65,19 +71,21 @@ def run(self, args, tool): field_dict = OrderedDict() # analyses = root.findall('Analysis') - for analysis in root.iter('Analysis'): - for field in analysis.iter('Field'): - name = field.attrib['name'] + for analysis in root.iter("Analysis"): + for field in analysis.iter("Field"): + name = field.attrib["name"] if field_dict.get(name) and not args.ignore_duplicates: - raise XmlFormatError(f"Field '{name}' appears multiple times in file '{input}'. " - "Use --ignore-duplicates to save only the final element.") + raise XmlFormatError( + f"Field '{name}' appears multiple times in file '{input}'. " + "Use --ignore-duplicates to save only the final element." + ) field_dict[name] = field # replace element with analysis.remove(field) - ET.SubElement(analysis, 'FieldRef', attrib={'name': name}) + ET.SubElement(analysis, "FieldRef", attrib={"name": name}) # Move all elements to the root for field in field_dict.values(): @@ -85,4 +93,4 @@ def run(self, args, tool): print(f"Writing '{output}'") tree = xmlfile.getTree() - tree.write(output, xml_declaration=True, pretty_print=True, encoding='utf-8') + tree.write(output, xml_declaration=True, pretty_print=True, encoding="utf-8") diff --git a/opgee/combine_streams.py b/opgee/combine_streams.py index c3f3cbff..9cf710a8 100644 --- a/opgee/combine_streams.py +++ b/opgee/combine_streams.py @@ -17,6 +17,7 @@ _logger = getLogger(__name__) + # TODO: improve this to use temp and press def combine_streams(streams): """ @@ -36,25 +37,27 @@ def combine_streams(streams): non_empty_streams_pressure = [stream.tp.P for stream in non_empty_streams] - non_empty_API_streams = \ - [stream for stream in non_empty_streams if stream.API is not None and stream.liquid_flow_rate("oil").m > 0] + non_empty_API_streams = [ + stream for stream in non_empty_streams if stream.API is not None and stream.liquid_flow_rate("oil").m > 0 + ] def calculated_combined_API_using_weighted_average(streams): """ - Calculate the combined API of crude oil streams using the weighted average method. + Calculate the combined API of crude oil streams using the weighted average method. - Args: - streams (List): A list of crude oil stream objects. + Args: + streams (List): A list of crude oil stream objects. - Returns: - float: The combined API of the crude oil streams. + Returns: + float: The combined API of the crude oil streams. """ if len(streams) == 1: return streams[0].API total_mass_rate = sum(stream.liquid_flow_rate("oil") for stream in streams) sum_of_mass_multiply_specific_gravity = sum( - stream.liquid_flow_rate("oil") * Oil.specific_gravity(stream.API) for stream in streams) + stream.liquid_flow_rate("oil") * Oil.specific_gravity(stream.API) for stream in streams + ) combined_sg = sum_of_mass_multiply_specific_gravity / total_mass_rate return Oil.API_from_SG(combined_sg) @@ -64,23 +67,20 @@ def calculated_combined_API_using_weighted_average(streams): comp_matrix = sum([stream.components for stream in streams]) - stream_temperature = pd.Series([stream.tp.T.to("kelvin").m for stream in non_empty_streams], - dtype="pint[kelvin]") + stream_temperature = pd.Series([stream.tp.T.to("kelvin").m for stream in non_empty_streams], dtype="pint[kelvin]") - stream_specific_heat = pd.Series([mixture_specific_heat_capacity(stream).m for - stream in non_empty_streams], - dtype="pint[btu/degF/day]") + stream_specific_heat = pd.Series( + [mixture_specific_heat_capacity(stream).m for stream in non_empty_streams], dtype="pint[btu/degF/day]" + ) stream_sp_heat_sum = stream_specific_heat.sum() if stream_sp_heat_sum.m != 0.0: temperature = (stream_temperature * stream_specific_heat).sum() / stream_sp_heat_sum temperature = temperature.to("degF") min_pressure = min(non_empty_streams_pressure) - stream = Stream('combined', - TemperaturePressure(temperature, max(STP.P, min_pressure)), - comp_matrix=comp_matrix) + stream = Stream("combined", TemperaturePressure(temperature, max(STP.P, min_pressure)), comp_matrix=comp_matrix) else: - stream = Stream('empty_stream', tp=STP) + stream = Stream("empty_stream", tp=STP) if len(non_empty_API_streams) > 0: stream.API = calculated_combined_API_using_weighted_average(non_empty_API_streams) diff --git a/opgee/config.py b/opgee/config.py index 94efc163..1243e491 100644 --- a/opgee/config.py +++ b/opgee/config.py @@ -1,10 +1,11 @@ -''' +""" .. Created 2016 as part of pygcam. Imported into opgee on 3/29/21 .. Copyright (c) 2015-2022 Richard Plevin See the https://opensource.org/licenses/MIT for license details. -''' +""" + import configparser import os import platform @@ -12,15 +13,15 @@ from .error import ConfigFileError, OpgeeException from .pkg_utils import getResource -DEFAULT_SECTION = 'DEFAULT' -USR_CONFIG_FILE = 'opgee.cfg' -USR_DEFAULTS_FILE = '.opgee.defaults' +DEFAULT_SECTION = "DEFAULT" +USR_CONFIG_FILE = "opgee.cfg" +USR_DEFAULTS_FILE = ".opgee.defaults" PlatformName = platform.system() -IsWindows = platform.system() == 'Windows' +IsWindows = platform.system() == "Windows" -_ConfigParser = None # type: configparser.ConfigParser +_ConfigParser = None # type: configparser.ConfigParser _ProjectSection = DEFAULT_SECTION @@ -42,6 +43,7 @@ # OPGEE.StreamComponents = foo, bar , baz """ + # The unixPath and pathjoin funcs are here rather than in utils.py # since this functionality is needed here and this avoids import loops. def unixPath(path, abspath=False): @@ -55,13 +57,14 @@ def unixPath(path, abspath=False): """ # Use str values, not Paths - path = str(path).replace('\\', '/') + path = str(path).replace("\\", "/") if abspath: path = os.path.abspath(path) return path + def pathjoin(*elements, expanduser=False, abspath=False, realpath=False): path = os.path.join(*elements) @@ -76,6 +79,7 @@ def pathjoin(*elements, expanduser=False, abspath=False, realpath=False): return unixPath(path) + # Deprecated (docker) # def savePathMap(mapString): # """ @@ -132,9 +136,11 @@ def pathjoin(*elements, expanduser=False, abspath=False, realpath=False): # # return semver.parse_version_info(vers) + def getSection(): return _ProjectSection + def setSection(section): """ Set the name of the default config file section to read from. @@ -145,22 +151,27 @@ def setSection(section): global _ProjectSection _ProjectSection = section + def configLoaded(): return bool(_ConfigParser) + def ensure_default_config(): - ''' + """ Check that config file exists or create default one. - ''' + """ configPath = userConfigPath() if not os.path.lexists(configPath) or os.stat(configPath).st_size == 0: try: - with open(configPath, 'w') as f: + with open(configPath, "w") as f: f.write(_DEFAULT_CONFIG) except Exception as e: - raise OpgeeException(f'\n***\n*** Failed to write default opgee configuration file {configPath}: {e}.\n***\n') + raise OpgeeException( + f"\n***\n*** Failed to write default opgee configuration file {configPath}: {e}.\n***\n" + ) + def getConfig(reload=False, allowMissing=False, createDefault=False, systemConfigOnly=False): """ @@ -188,13 +199,12 @@ def getConfig(reload=False, allowMissing=False, createDefault=False, systemConfi global _ConfigParser _ConfigParser = None - return _ConfigParser or readConfigFiles(allowMissing=allowMissing, - systemConfigOnly=systemConfigOnly) + return _ConfigParser or readConfigFiles(allowMissing=allowMissing, systemConfigOnly=systemConfigOnly) -def _readConfigResourceFile(filename, package='opgee', raiseError=True): +def _readConfigResourceFile(filename, package="opgee", raiseError=True): try: - data = getResource(filename, decode='utf-8') + data = getResource(filename, decode="utf-8") except IOError: if raiseError: raise @@ -204,27 +214,30 @@ def _readConfigResourceFile(filename, package='opgee', raiseError=True): _ConfigParser.read_string(data, source=filename) return data + def getHomeDir(): env = os.environ - if PlatformName == 'Windows': + if PlatformName == "Windows": # HOME exists on all Unix-like systems; for Windows it's HOMEPATH or HOMESHARE. # If set, we use OPGEE_HOME to identify the folder with the config file; # otherwise, we use HOMESHARE if set, or HOMEPATH, in that order. - homedir = env.get('OPGEE_HOME') or env.get('HOMESHARE') or env.get('HOMEPATH') + homedir = env.get("OPGEE_HOME") or env.get("HOMESHARE") or env.get("HOMEPATH") drive, path = os.path.splitdrive(homedir) - drive = drive or env.get('HOMEDRIVE') or 'C:' + drive = drive or env.get("HOMEDRIVE") or "C:" home = os.path.realpath(drive + path) - home = home.replace('\\', '/') # avoids '\' quoting issues + home = home.replace("\\", "/") # avoids '\' quoting issues else: - home = env.get('OPGEE_HOME') or os.getenv('HOME') + home = env.get("OPGEE_HOME") or os.getenv("HOME") return home + def userConfigPath(): path = pathjoin(getHomeDir(), USR_CONFIG_FILE) return path + def readConfigFile(path_or_stream): # N.B. doesn't handle Path-like objs but we aren't using them here if isinstance(path_or_stream, (str, bytes)): @@ -234,6 +247,7 @@ def readConfigFile(path_or_stream): # test code passes a StringIO instance to set up test environment _ConfigParser.read_file(path_or_stream) + def readConfigFiles(allowMissing=False, systemConfigOnly=False): """ Read the OPGEE configuration files, starting with ``opgee/etc/system.cfg``, @@ -247,32 +261,30 @@ def readConfigFiles(allowMissing=False, systemConfigOnly=False): global _ConfigParser # Strict mode prevents duplicate sections, which we do not restrict - _ConfigParser = configparser.ConfigParser(comment_prefixes=('#'), - strict=False, - empty_lines_in_values=False) + _ConfigParser = configparser.ConfigParser(comment_prefixes=("#"), strict=False, empty_lines_in_values=False) # don't force keys to lower-case: variable names are case sensitive _ConfigParser.optionxform = lambda option: option home = getHomeDir() - _ConfigParser.set(DEFAULT_SECTION, 'Home', home) + _ConfigParser.set(DEFAULT_SECTION, "Home", home) if not systemConfigOnly: - _ConfigParser.set(DEFAULT_SECTION, 'User', os.getenv('USER', 'unknown')) + _ConfigParser.set(DEFAULT_SECTION, "User", os.getenv("USER", "unknown")) # Create vars from environment variables as '$' + variable name, as in the shell for name, value in os.environ.items(): - value = value.replace(r'%', r'%%') - _ConfigParser.set(DEFAULT_SECTION, '$' + name, value) + value = value.replace(r"%", r"%%") + _ConfigParser.set(DEFAULT_SECTION, "$" + name, value) # Initialize config parser with default values - _readConfigResourceFile('etc/system.cfg') + _readConfigResourceFile("etc/system.cfg") if not systemConfigOnly: # Read platform-specific defaults, if defined. No error if file is missing. - _readConfigResourceFile(f'etc/{PlatformName}.cfg', raiseError=False) + _readConfigResourceFile(f"etc/{PlatformName}.cfg", raiseError=False) - siteConfig = os.getenv('OPGEE_SITE_CONFIG') + siteConfig = os.getenv("OPGEE_SITE_CONFIG") if siteConfig: try: readConfigFile(siteConfig) @@ -296,21 +308,25 @@ def readConfigFiles(allowMissing=False, systemConfigOnly=False): # Dynamically set (if not defined) OPGEE.ProjectName in each section, holding the # section (i.e., project) name. If user has set this, the value is unchanged. - projectNameVar = 'OPGEE.ProjectName' + projectNameVar = "OPGEE.ProjectName" for section in getSections(): - if not (_ConfigParser.has_option(section, projectNameVar) and # var must exist - _ConfigParser.get(section, projectNameVar)): # and not be blank + if not ( + _ConfigParser.has_option(section, projectNameVar) # var must exist + and _ConfigParser.get(section, projectNameVar) + ): # and not be blank _ConfigParser.set(section, projectNameVar, section) - projectName = getParam('OPGEE.DefaultProject', section=DEFAULT_SECTION) + projectName = getParam("OPGEE.DefaultProject", section=DEFAULT_SECTION) if projectName: setSection(projectName) return _ConfigParser + def getSections(): return _ConfigParser.sections() + def getConfigDict(section=DEFAULT_SECTION, raw=False): """ Return all variables defined in `section` as a dictionary. @@ -327,9 +343,10 @@ def getConfigDict(section=DEFAULT_SECTION, raw=False): func = lambda x: x # no-op - d = {key : func(value) for key, value in _ConfigParser.items(section, raw=raw)} + d = {key: func(value) for key, value in _ConfigParser.items(section, raw=raw)} return d + def setParam(name, value, section=None): """ Set a configuration parameter in memory. @@ -345,6 +362,7 @@ def setParam(name, value, section=None): _ConfigParser.set(section, name, value) return value + def getParam(name, section=None, raw=False, raiseError=True): """ Get the value of the configuration parameter `name`. Calls @@ -393,8 +411,10 @@ def getParam(name, section=None, raw=False, raiseError=True): return value -_True = ['t', 'y', 'true', 'yes', 'on', '1'] -_False = ['f', 'n', 'false', 'no', 'off', '0'] + +_True = ["t", "y", "true", "yes", "on", "1"] +_False = ["f", "n", "false", "no", "off", "0"] + def stringTrue(value, raiseError=True): value = str(value).lower() @@ -411,6 +431,7 @@ def stringTrue(value, raiseError=True): else: return None + def getParamAsBoolean(name, section=None): """ Get the value of the configuration parameter `name`, coerced @@ -436,6 +457,7 @@ def getParamAsBoolean(name, section=None): return result + def getParamAsInt(name, section=None): """ Get the value of the configuration parameter `name`, coerced @@ -450,6 +472,7 @@ def getParamAsInt(name, section=None): value = getParam(name, section=section) return int(value) + def getParamAsFloat(name, section=None): """ Get the value of the configuration parameter `name` as a @@ -464,7 +487,8 @@ def getParamAsFloat(name, section=None): value = getParam(name, section=section) return float(value) + def getParamAsList(name): value = getParam(name) - values = [s.strip() for s in value.split(',')] + values = [s.strip() for s in value.split(",")] return values diff --git a/opgee/constants.py b/opgee/constants.py index 917c60fb..eb2eacd4 100644 --- a/opgee/constants.py +++ b/opgee/constants.py @@ -1,14 +1,14 @@ # # String constants # -CLUSTER_NONE = 'serial' # no cluster; just run fields serially -CLUSTER_LOCAL = 'local' -CLUSTER_SLURM = 'slurm' +CLUSTER_NONE = "serial" # no cluster; just run fields serially +CLUSTER_LOCAL = "local" +CLUSTER_SLURM = "slurm" CLUSTER_TYPES = (CLUSTER_NONE, CLUSTER_SLURM, CLUSTER_LOCAL) -SIMPLE_RESULT = 'simple' -DETAILED_RESULT = 'detailed' -ERROR_RESULT = 'error' +SIMPLE_RESULT = "simple" +DETAILED_RESULT = "detailed" +ERROR_RESULT = "error" DEFAULT_RESULT_TYPE = SIMPLE_RESULT -USER_RESULT_TYPES = (SIMPLE_RESULT, DETAILED_RESULT) # used by argument parser -ALL_RESULT_TYPES = (SIMPLE_RESULT, DETAILED_RESULT, ERROR_RESULT) +USER_RESULT_TYPES = (SIMPLE_RESULT, DETAILED_RESULT) # used by argument parser +ALL_RESULT_TYPES = (SIMPLE_RESULT, DETAILED_RESULT, ERROR_RESULT) diff --git a/opgee/container.py b/opgee/container.py index e16629e5..836afad7 100644 --- a/opgee/container.py +++ b/opgee/container.py @@ -17,11 +17,13 @@ _logger = getLogger(__name__) + class Container(AttributeMixin, XmlInstantiable): """ Generic hierarchical node element, has a name and contains other Containers and/or Processes (and subclasses thereof). """ + def __init__(self, name, attr_dict=None, parent=None): AttributeMixin.__init__(self, attr_dict=attr_dict) XmlInstantiable.__init__(self, name, parent=parent) @@ -38,7 +40,7 @@ def __init__(self, name, attr_dict=None, parent=None): self.aggs = None def add_children(self, aggs=None, procs=None, **kwargs): - self.aggs = self.adopt(aggs) + self.aggs = self.adopt(aggs) self.procs = self.adopt(procs) def _children(self): @@ -77,7 +79,7 @@ def _add_children(container, include_disabled=False): for obj in container.children(): if isinstance(obj, Container): _add_children(obj, include_disabled=include_disabled) - elif (include_disabled or obj.is_enabled()): + elif include_disabled or obj.is_enabled(): procs.append(obj) _add_children(self) @@ -115,8 +117,8 @@ def get_energy_rates(self): data = self.energy.data for child in self.children(): - child_data = child.get_energy_rates() - data += child_data + child_data = child.get_energy_rates() + data += child_data return data @@ -128,12 +130,12 @@ def get_emission_rates(self, analysis, procs_to_exclude=None): :return: (pandas.Series) the emissions Series. """ data = self.emissions.data - data[data.columns] = ureg.Quantity(0.0, 't/d') + data[data.columns] = ureg.Quantity(0.0, "t/d") for child in self.children(): if not procs_to_exclude or child not in procs_to_exclude: - child_data = child.get_emission_rates(analysis, procs_to_exclude=procs_to_exclude) - data += child_data + child_data = child.get_emission_rates(analysis, procs_to_exclude=procs_to_exclude) + data += child_data # compute CO2eq using chosen GWP values data = self.emissions.rates(analysis.gwp) @@ -153,5 +155,3 @@ def get_net_imported_product(self): data += child_data return data - - diff --git a/opgee/core.py b/opgee/core.py index b6edae5f..ba2a5dd6 100644 --- a/opgee/core.py +++ b/opgee/core.py @@ -20,10 +20,10 @@ def name_of(obj): def elt_name(elt): - return elt.attrib.get('name') + return elt.attrib.get("name") -def instantiate_subelts(elt, cls, parent=None, as_dict=False, include_names=None, - **cls_args): + +def instantiate_subelts(elt, cls, parent=None, as_dict=False, include_names=None, **cls_args): """ Return a list of instances of ``cls`` (or of its indicated subclass of ``Process``). @@ -43,8 +43,11 @@ def instantiate_subelts(elt, cls, parent=None, as_dict=False, include_names=None tag = cls.__name__ # class name matches element name include = None if include_names is None else set(include_names) - objs = [cls.from_xml(e, parent=parent, **cls_args) - for e in elt.findall(tag) if include is None or e.attrib.get('name') in include] + objs = [ + cls.from_xml(e, parent=parent, **cls_args) + for e in elt.findall(tag) + if include is None or e.attrib.get("name") in include + ] if as_dict: d = {obj.name: obj for obj in objs} @@ -52,6 +55,7 @@ def instantiate_subelts(elt, cls, parent=None, as_dict=False, include_names=None else: return objs + def dict_from_list(objs): """ Create a dictionary of ``XMLInstantiable`` objects by their name attribute, but @@ -73,7 +77,8 @@ def dict_from_list(objs): return d -CLASS_DELIMITER = '.' +CLASS_DELIMITER = "." + def split_attr_name(attr_name): splits = attr_name.split(CLASS_DELIMITER) @@ -95,7 +100,7 @@ def split_attr_name(attr_name): # Top of hierarchy, because it's useful to know which classes are "ours" -class OpgeeObject(): +class OpgeeObject: @classmethod def clear(cls): # Clear state stored in class variables @@ -113,6 +118,7 @@ class XmlInstantiable(OpgeeObject): 4. Subclasses of Container and Process implement ``run(self)`` to perform any required operations. """ + def __init__(self, name, parent=None): super().__init__() self.name = name @@ -124,12 +130,12 @@ def set_parent(self, parent): @classmethod def from_xml(cls, elt, parent=None, **cls_args): - raise AbstractMethodError(cls, 'XmlInstantiable.from_xml') + raise AbstractMethodError(cls, "XmlInstantiable.from_xml") def __str__(self): type_str = type(self).__name__ - name_str = f' name="{self.name}"' if self.name else '' - return f'<{type_str}{name_str} enabled={self.enabled}>' + name_str = f' name="{self.name}"' if self.name else "" + return f"<{type_str}{name_str} enabled={self.enabled}>" def print_in_context(self): """ @@ -139,7 +145,7 @@ def print_in_context(self): """ obj = self seq = [obj] - while ((obj := obj.parent) is not None): + while (obj := obj.parent) is not None: seq.insert(0, obj) indent = 0 @@ -169,7 +175,7 @@ def adopt(self, objs, asDict=False): dct = {} for obj in objs: - if (existing := dct.get(obj.name)): + if existing := dct.get(obj.name): obj.print_in_context() existing.print_in_context() raise ModelValidationError(f"Tried to adopt {obj} which is a duplicate of {existing}.") @@ -268,7 +274,8 @@ class TemperaturePressure(OpgeeObject): """ Stores temperature and pressure together for convenience. """ - __slots__ = ('T', 'P') # keeps instances small and fast + + __slots__ = ("T", "P") # keeps instances small and fast def __init__(self, T, P): self.T = None @@ -280,7 +287,7 @@ def __str__(self): def set(self, T=None, P=None): if T is None and P is None: - #_logger.warning("Tried to set TemperaturePressure with both values None") + # _logger.warning("Tried to set TemperaturePressure with both values None") return if T is not None: @@ -295,16 +302,18 @@ def get(self): def copy_from(self, tp): self.set(T=tp.T, P=tp.P) + # Standard temperature and pressure std_temperature = ureg.Quantity(60.0, "degF") -std_pressure = ureg.Quantity(14.676, "psia") +std_pressure = ureg.Quantity(14.676, "psia") STP = TemperaturePressure(std_temperature, std_pressure) + class Timer: def __init__(self, feature_name, start=True): self.feature_name = feature_name self.start_time = None - self.stop_time = None + self.stop_time = None if start: self.start() @@ -331,4 +340,3 @@ def __str__(self): status = f"completed in {d}" return f"" - diff --git a/opgee/emissions.py b/opgee/emissions.py index 843ef62b..6931299e 100644 --- a/opgee/emissions.py +++ b/opgee/emissions.py @@ -14,21 +14,22 @@ from .error import OpgeeException from .stream import Stream -EM_COMBUSTION = 'Combustion' -EM_LAND_USE = 'Land-use' -EM_VENTING = 'Venting' -EM_FLARING = 'Flaring' -EM_FUGITIVES = 'Fugitives' -EM_OTHER = 'Other' +EM_COMBUSTION = "Combustion" +EM_LAND_USE = "Land-use" +EM_VENTING = "Venting" +EM_FLARING = "Flaring" +EM_FUGITIVES = "Fugitives" +EM_OTHER = "Other" EM_VOC = "VOC" -EM_CO = "CO" +EM_CO = "CO" EM_CH4 = "CH4" -EM_C1 = "C1" # refers to CH4 in some contexts +EM_C1 = "C1" # refers to CH4 in some contexts EM_N2O = "N2O" EM_CO2 = "CO2" EM_GHG = "GHG" + class EmissionsError(OpgeeException): def __init__(self, func_name, category, gas): self.func_name = func_name @@ -111,7 +112,7 @@ def compute_GHG(self, gwp): :return: none """ product = self.data.T[self.emissions] * gwp - self.data.loc[EM_GHG] = product.sum(axis='columns') + self.data.loc[EM_GHG] = product.sum(axis="columns") def reset_GHG(self): """ @@ -135,7 +136,7 @@ def set_rate(self, category, gas, rate): :return: none """ rate = rate.to("tonne/day") if isinstance(rate, pint.Quantity) else rate - self._check_loc('set_rate', gas, category) + self._check_loc("set_rate", gas, category) self.data.loc[gas, category] = magnitude(rate, units="tonne/day") def set_rates(self, category, **kwargs): @@ -159,7 +160,7 @@ def add_rate(self, category, gas, rate): :param rate: (float) the increment in rate in the Process' flow units (e.g., mmbtu (LHV) of fuel burned) :return: none """ - self._check_loc('add_rate', gas, category) + self._check_loc("add_rate", gas, category) sum = self.data.loc[gas, category] + rate self.data.loc[gas, category] = magnitude(sum, units="tonne/day") @@ -192,7 +193,7 @@ def add_from_stream(self, category, stream): # All gas-phase hydrocarbons heavier than methane are considered VOCs voc_rate = stream.voc_flow_rates().sum() - self.add_rate(category, 'VOC', voc_rate) + self.add_rate(category, "VOC", voc_rate) def set_from_stream(self, category, stream): """ diff --git a/opgee/energy.py b/opgee/energy.py index daf379ad..5de02e39 100644 --- a/opgee/energy.py +++ b/opgee/energy.py @@ -16,14 +16,14 @@ _logger = getLogger(__name__) # TBD: Decide if these strings are the ones we want to use throughout. Some seem a bit random. -EN_NATURAL_GAS = 'Natural gas' -EN_UPG_PROC_GAS = 'Upgrader proc. gas' -EN_NGL = 'NGL' -EN_CRUDE_OIL = 'Crude oil' -EN_DIESEL = 'Diesel' -EN_RESID = 'Residual fuel' -EN_PETCOKE = 'Pet. coke' -EN_ELECTRICITY = 'Electricity' +EN_NATURAL_GAS = "Natural gas" +EN_UPG_PROC_GAS = "Upgrader proc. gas" +EN_NGL = "NGL" +EN_CRUDE_OIL = "Crude oil" +EN_DIESEL = "Diesel" +EN_RESID = "Residual fuel" +EN_PETCOKE = "Pet. coke" +EN_ELECTRICITY = "Electricity" class Energy(OpgeeObject): @@ -36,8 +36,7 @@ class Energy(OpgeeObject): #: Note that when used in the code, the defined variables (EN_NATURAL_GAS, #: EN_UPG_PROC_GAS, EN_NGL, EN_CRUDE_OIL, EN_DIESEL, EN_RESID, EN_PETCOKE, #: EN_ELECTRICITY) should be used to avoid dependencies on the specific strings. - carriers = [EN_NATURAL_GAS, EN_UPG_PROC_GAS, EN_NGL, EN_CRUDE_OIL, - EN_DIESEL, EN_RESID, EN_PETCOKE, EN_ELECTRICITY] + carriers = [EN_NATURAL_GAS, EN_UPG_PROC_GAS, EN_NGL, EN_CRUDE_OIL, EN_DIESEL, EN_RESID, EN_PETCOKE, EN_ELECTRICITY] _carrier_set = set(carriers) @@ -46,11 +45,11 @@ class Energy(OpgeeObject): @classmethod def create_energy_series(cls): """ - Create a pandas Series to hold energy consumption rates. + Create a pandas Series to hold energy consumption rates. - :return: (pandas.Series) Zero-filled energy carrier Series - """ - return pd.Series(data=0.0, index=cls.carriers, name='energy', dtype=f"pint[{cls._units}]") + :return: (pandas.Series) Zero-filled energy carrier Series + """ + return pd.Series(data=0.0, index=cls.carriers, name="energy", dtype=f"pint[{cls._units}]") def __init__(self): self.data = self.create_energy_series() diff --git a/opgee/error.py b/opgee/error.py index 10c7b5ef..3f907ca8 100644 --- a/opgee/error.py +++ b/opgee/error.py @@ -7,6 +7,7 @@ # See LICENSE.txt for license details. # + class OpgeeException(Exception): pass @@ -16,16 +17,21 @@ class OpgeeStopIteration(OpgeeException): def __init__(self, reason): self.reason = reason + class OpgeeMaxIterationsReached(OpgeeStopIteration): """Thrown when iterations have reached Model's ``max_iterations``""" + pass + class OpgeeIterationConverged(OpgeeStopIteration): """Thrown when a Process's change variables have all changed less than Model's - ``iteration_epsilon`` between runs. + ``iteration_epsilon`` between runs. """ + pass + class AbstractMethodError(OpgeeException): def __init__(self, cls, method): self.cls = cls @@ -38,6 +44,7 @@ def __str__(self): class AttributeError(OpgeeException): pass + # class AttributeError(OpgeeException): # def __init__(self, dict_name, key): # self.dict_name = dict_name @@ -51,6 +58,7 @@ class FileFormatError(OpgeeException): """ Indicate a syntax error in a user-managed file. """ + pass @@ -65,10 +73,12 @@ def __init__(self, msg): def __str__(self): return f'<{self.__class__.__name__} "{self.msg}">' + class ConfigFileError(FileFormatError): """ Raised for errors in user's configuration file. """ + pass @@ -76,6 +86,7 @@ class CommandlineError(OpgeeException): """ Command-line arguments were missing or incorrectly specified. """ + pass @@ -83,43 +94,51 @@ class BalanceError(OpgeeException): """ Mass or Energy balances are fail """ + def __init__(self, proc_name, mass_or_energy, message=None): self.proc_name = proc_name self.mass_or_energy = mass_or_energy self.message = message def __str__(self): - return f"{self.mass_or_energy} is not balanced in {self.proc_name}" + \ - (f": {self.message}" if self.message else "") + return f"{self.mass_or_energy} is not balanced in {self.proc_name}" + ( + f": {self.message}" if self.message else "" + ) class ZeroEnergyFlowError(OpgeeException): """ Zero energy flow at system boundary, so cannot compute CI """ + def __init__(self, stream, message=None): self.stream = stream self.message = message def __str__(self): - return (f"Zero energy flow rate for {self.stream.boundary} boundary stream {self.stream}" + - (f": {self.message}" if self.message else "")) + return f"Zero energy flow rate for {self.stream.boundary} boundary stream {self.stream}" + ( + f": {self.message}" if self.message else "" + ) class McsUserError(OpgeeException): pass + class McsSystemError(OpgeeException): pass + class DistributionSpecError(OpgeeException): pass + class RemoteError(OpgeeException): """ Returned when we catch any exception so it can be handled in the Manager. """ + def __init__(self, msg, field_name, trial=None): self.msg = msg self.field_name = field_name diff --git a/opgee/etc/pluginTemplate.py b/opgee/etc/pluginTemplate.py index 704e7778..b585a46a 100644 --- a/opgee/etc/pluginTemplate.py +++ b/opgee/etc/pluginTemplate.py @@ -5,34 +5,43 @@ # from opgee.subcommand import SubcommandABC + class MyNewCommand(SubcommandABC): def __init__(self, subparsers): - kwargs = {'help' : '''Short help text for main driver''', - 'description' : '''Longer description for sub-command'''} + kwargs = { + "help": """Short help text for main driver""", + "description": """Longer description for sub-command""", + } # The first argument is the name of the new sub-command - super().__init__('subCmdName', subparsers, kwargs) + super().__init__("subCmdName", subparsers, kwargs) def addArgs(self, parser): - ''' + """ Process the command-line arguments for this sub-command - ''' - parser.add_argument('-n', '--number', type=int, default=0, - help='''A number to demonstrate a command line arg. - Replace as needed with your own plugin's args.''') + """ + parser.add_argument( + "-n", + "--number", + type=int, + default=0, + help="""A number to demonstrate a command line arg. + Replace as needed with your own plugin's args.""", + ) return parser def run(self, args, tool): - ''' + """ Implement the sub-command here. "args" is an `argparse.Namespace` instance holding the parsed command-line arguments, and "tool" is a reference to the running OpgeeTool instance. - ''' + """ # from opgee.log import getLogger # _logger = getLogger(__name__) pass + # An alternative to naming the class 'Plugin' is to assign the class to PluginClass PluginClass = MyNewCommand diff --git a/opgee/field.py b/opgee/field.py index 4210cdcf..b5888055 100644 --- a/opgee/field.py +++ b/opgee/field.py @@ -43,27 +43,27 @@ class FieldResult: def __init__( - self, - analysis_name, - field_name, - result_type, - energy_data=None, - ghg_data=None, # CO2e - gas_data=None, # individual gases - streams_data=None, - ci_results=None, - energy_output=None, - trial_num=None, - audit_data=None, - error=None, + self, + analysis_name, + field_name, + result_type, + energy_data=None, + ghg_data=None, # CO2e + gas_data=None, # individual gases + streams_data=None, + ci_results=None, + energy_output=None, + trial_num=None, + audit_data=None, + error=None, ): self.analysis_name = analysis_name self.field_name = field_name self.result_type = result_type self.ci_results = ci_results # list of tuples of (node_name, CI) self.energy_output = energy_output - self.energy = energy_data # energy consumption data - self.emissions = ghg_data # TBD: change self.emissions to self.ghgs + self.energy = energy_data # energy consumption data + self.emissions = ghg_data # TBD: change self.emissions to self.ghgs self.gases = gas_data self.streams = streams_data self.trial_num = trial_num @@ -319,9 +319,7 @@ def cache_attributes(self): # Used by validate() to descend model hierarchy def _children(self): - return ( - super()._children() - ) # + self.streams() # Adding this caused several errors... + return super()._children() # + self.streams() # Adding this caused several errors... def add_children(self, aggs=None, procs=None, streams=None, process_choice_dict=None): # Note that `procs` include only Processes defined at the top-level of the field. @@ -369,8 +367,7 @@ def add_children(self, aggs=None, procs=None, streams=None, process_choice_dict= self.check_attr_constraints(self.attr_dict) - self.component_fugitive_table, self.loss_mat_gas_ave_df = \ - self.get_component_fugitive() + self.component_fugitive_table, self.loss_mat_gas_ave_df = self.get_component_fugitive() self.finalize_process_graph() @@ -413,11 +410,7 @@ def _run_after_ok(proc): return False return True - bad = [ - proc - for proc in self.processes() - if proc.run_after and not _run_after_ok(proc) - ] + bad = [proc for proc in self.processes() if proc.run_after and not _run_after_ok(proc)] if bad: # DOCUMENT after=True attribute raise OpgeeException( @@ -437,9 +430,7 @@ def _impute_upstream(proc): proc.visit() proc.impute() - upstream_procs = { - stream.src_proc for stream in proc.inputs if stream.impute - } + upstream_procs = {stream.src_proc for stream in proc.inputs if stream.impute} for upstream_proc in upstream_procs: _impute_upstream(upstream_proc) @@ -447,15 +438,11 @@ def _impute_upstream(proc): for stream in start_streams: if not stream.impute: - raise OpgeeException( - f"A start stream {stream} cannot have its 'impute' flag set to '0'." - ) + raise OpgeeException(f"A start stream {stream} cannot have its 'impute' flag set to '0'.") # Find procs with start == True or find start_procs upstream from streams with exogenous data.from # We require that all start streams emerge from one Process. - start_procs = {p for p in self.processes() if p.impute_start} or { - stream.src_proc for stream in start_streams - } + start_procs = {p for p in self.processes() if p.impute_start} or {stream.src_proc for stream in start_streams} start_count = len(start_procs) # No impute @@ -476,9 +463,7 @@ def _impute_upstream(proc): _impute_upstream(start_proc) except OpgeeStopIteration: # Shouldn't be possible - raise OpgeeException( - "Impute failed due to a process loop. Use Stream attribute impute='0' to break cycle." - ) + raise OpgeeException("Impute failed due to a process loop. Use Stream attribute impute='0' to break cycle.") def run(self, analysis, compute_ci=True, trial_num=None): """ @@ -516,12 +501,8 @@ def run(self, analysis, compute_ci=True, trial_num=None): # Perform aggregations self.get_energy_rates() - self.get_emission_rates( - analysis, procs_to_exclude=self.procs_beyond_boundary - ) - self.carbon_intensity = ( - self.compute_carbon_intensity(analysis) if compute_ci else None - ) + self.get_emission_rates(analysis, procs_to_exclude=self.procs_beyond_boundary) + self.carbon_intensity = self.compute_carbon_intensity(analysis) if compute_ci else None _logger.info(timer.stop()) def reset(self): @@ -575,9 +556,7 @@ def boundary_process(self, analysis) -> Process: try: return self.boundary_dict[analysis.boundary] except KeyError: - raise OpgeeException( - f"{self} does not declare boundary process '{analysis.boundary}'." - ) + raise OpgeeException(f"{self} does not declare boundary process '{analysis.boundary}'.") def defined_boundaries(self): """ @@ -598,17 +577,13 @@ def boundary_energy_flow_rate(self, analysis, raiseError=True): combined_stream = combine_streams(boundary_proc.inputs) # TODO: Add method to calculate petcoke energy flow rate - energy = self.oil.energy_flow_rate(combined_stream) + self.gas.energy_flow_rate( - combined_stream - ) + energy = self.oil.energy_flow_rate(combined_stream) + self.gas.energy_flow_rate(combined_stream) if energy.m == 0: if raiseError: raise ZeroEnergyFlowError(boundary_proc) else: - _logger.warning( - f"Zero energy flow rate for {boundary_proc.boundary} boundary process {boundary_proc}" - ) + _logger.warning(f"Zero energy flow rate for {boundary_proc.boundary} boundary process {boundary_proc}") return energy @@ -640,9 +615,7 @@ def compute_carbon_intensity(self, analysis): boundary_energy_flow_rate = self.boundary_energy_flow_rate(analysis) self.carbon_intensity = ci = ureg.Quantity(0, "grams/MJ") if boundary_energy_flow_rate.m != 0: - self.carbon_intensity = ci = ( - total_emissions / boundary_energy_flow_rate - ).to("grams/MJ") + self.carbon_intensity = ci = (total_emissions / boundary_energy_flow_rate).to("grams/MJ") # Also save the numerator and denominator separately for reporting self.energy_output = boundary_energy_flow_rate @@ -667,9 +640,7 @@ def partial_ci_values(self, analysis, nodes): energy = self.boundary_energy_flow_rate(analysis) except ZeroEnergyFlowError: - _logger.error( - f"Can't save results: zero energy flow at system boundary for {self}" - ) + _logger.error(f"Can't save results: zero energy flow at system boundary for {self}") return None def partial_ci(obj): @@ -681,11 +652,7 @@ def partial_ci(obj): # convert to g/MJ, but we don't need units in CSV file return ci.to("grams/MJ") - results = [ - (obj.name, partial_ci(obj)) - for obj in nodes - if not isinstance(obj, Boundary) - ] + results = [(obj.name, partial_ci(obj)) for obj in nodes if not isinstance(obj, Boundary)] return results def energy_and_emissions(self, analysis): @@ -698,9 +665,9 @@ def process_data(proc_dict, column_name): # Add a 'units' columns using the units from the first element # in the dict. N.B. We assume all elements have the same units. unit = next(iter(proc_dict.values())).u - df['unit'] = unit + df["unit"] = unit - df.index.rename('process', inplace=True) + df.index.rename("process", inplace=True) return df gwp = analysis.gwp @@ -721,9 +688,9 @@ def process_data(proc_dict, column_name): # process, then concatenating them into a dataframe. def gas_df_with_name(proc): df = proc.emissions.data.reset_index().rename(columns={"index": "gas"}) - cols = ['field', 'process'] + list(df.columns) - df['field'] = self.name - df['process'] = proc.name + cols = ["field", "process"] + list(df.columns) + df["field"] = self.name + df["process"] = proc.name df = df[cols].pint.dequantify() # move units to 2nd row of column headings... return df @@ -742,16 +709,14 @@ def get_result(self, analysis, result_type, trial_num=None) -> FieldResult: :param trial_num: (int) trial number, if running in MCS mode :return: (FieldResult) results """ - energy_data, ghg_data, gas_data = self.energy_and_emissions(analysis) \ - if result_type == DETAILED_RESULT else (None, None, None) + energy_data, ghg_data, gas_data = ( + self.energy_and_emissions(analysis) if result_type == DETAILED_RESULT else (None, None, None) + ) nodes = [p for p in self.process_dict.values()] + [agg for agg in self.agg_dict.values()] ci_tuples = self.partial_ci_values(analysis, nodes) - ci_results = ( - None if ci_tuples is None - else [("TOTAL", self.carbon_intensity)] + ci_tuples - ) + ci_results = None if ci_tuples is None else [("TOTAL", self.carbon_intensity)] + ci_tuples dfs = [s.to_dataframe() for s in self.streams()] streams_data = pd.concat(dfs) @@ -795,9 +760,7 @@ def get_imported_emissions(self, net_import): continue energy_rate = ( - energy_rate - if isinstance(energy_rate, pint.Quantity) - else ureg.Quantity(energy_rate, "mmbtu/day") + energy_rate if isinstance(energy_rate, pint.Quantity) else ureg.Quantity(energy_rate, "mmbtu/day") ) if energy_rate.m > 0: @@ -820,9 +783,7 @@ def get_carbon_credit(self, byproduct_names, analysis): for name in byproduct_names: process_name = self.product_boundaries.loc[name, analysis.boundary] if process_name and process_name in process_names: - carbon_credit += ( - export.loc[process_name, name] * self.upstream_CI.loc[name, "EF"] - ) + carbon_credit += export.loc[process_name, name] * self.upstream_CI.loc[name, "EF"] return carbon_credit @@ -837,7 +798,7 @@ def comp_fugitive_productivity(prod_mat_gas, mean): """ result = prod_mat_gas[ (prod_mat_gas["Bin low"] < mean) & (prod_mat_gas["Bin high"] >= mean) - ].index.values.astype(int)[0] + ].index.values.astype(int)[0] return result @@ -867,9 +828,7 @@ def get_component_fugitive(self): frac_wells_with_non_plunger = self.attr("frac_wells_with_non_plunger").m if self.attr("gas_flooding") and self.attr("flood_gas_type") == "CO2": - productivity += ( - oil_rate * self.attr("GFIR") * self.attr("frac_CO2_breakthrough") - ) + productivity += oil_rate * self.attr("GFIR") * self.attr("frac_CO2_breakthrough") num_prod_wells = self.attr("num_prod_wells") separation_loss_rate = ureg.Quantity(0.0, "frac") @@ -898,22 +857,16 @@ def get_component_fugitive(self): ) field_productivity["Mean gas rate (Mscf/well/day)"] = ( - prod_mat_gas["Normalized rate"] - if GOR > GOR_cutoff - else prod_mat_oil["Normalized rate"] + prod_mat_gas["Normalized rate"] if GOR > GOR_cutoff else prod_mat_oil["Normalized rate"] ) field_productivity["Mean gas rate (Mscf/well/day)"] *= productivity field_productivity["Frac total gas"] = ( - prod_mat_gas["Frac total gas"] - if GOR > GOR_cutoff - else prod_mat_oil["Frac total gas"] + prod_mat_gas["Frac total gas"] if GOR > GOR_cutoff else prod_mat_oil["Frac total gas"] ) field_productivity["Assignment"] = field_productivity.apply( - lambda row: self.comp_fugitive_productivity( - prod_mat_gas, row["Mean gas rate (Mscf/well/day)"] - ), + lambda row: self.comp_fugitive_productivity(prod_mat_gas, row["Mean gas rate (Mscf/well/day)"]), axis=1, ) @@ -938,9 +891,7 @@ def get_component_fugitive(self): flash_factor = 0.51 # kg CH4/bbl (total flashing gas). Divide by 0.51 to correct for fraction of wells controlled in Rutherford et al. 2021 loss_mat_gas_ave = loss_mat_gas.mean(axis=0).values loss_mat_gas_ave = loss_mat_gas_ave.reshape(len(tranch), len(cols_gas)) - loss_mat_gas_ave_df = pd.DataFrame( - data=loss_mat_gas_ave, index=prod_mat_gas["Bin low"], columns=cols_gas - ) + loss_mat_gas_ave_df = pd.DataFrame(data=loss_mat_gas_ave, index=prod_mat_gas["Bin low"], columns=cols_gas) cols = cols_gas if GOR > GOR_cutoff else cols_oil loss_mat = loss_mat_gas if GOR > GOR_cutoff else loss_mat_oil @@ -948,28 +899,22 @@ def get_component_fugitive(self): loss_mat_ave = loss_mat_ave.reshape(len(tranch), len(cols)) df = pd.DataFrame(loss_mat_ave, columns=cols, index=range(len(tranch))) - df = field_productivity.apply( - lambda row: self.comp_fugitive_loss(df, row["Assignment"]), axis=1 - ) + df = field_productivity.apply(lambda row: self.comp_fugitive_loss(df, row["Assignment"]), axis=1) comp_fugitive = df.T.dot(field_productivity["Frac total gas"]) comp_fugitive["Flash factor"] /= flash_factor separation_loss_rate = comp_fugitive["Separator"] tank_loss_rate = comp_fugitive["Flash factor"] pump_loss_rate = comp_fugitive - pump_loss_rate.drop( - "Separator", inplace=True - ) # TBD: drop both at same time + pump_loss_rate.drop("Separator", inplace=True) # TBD: drop both at same time pump_loss_rate.drop("Flash factor", inplace=True) if GOR > GOR_cutoff: pump_loss_rate["LU-plunger-norm"] = ( - pump_loss_rate["LU-plunger"] * frac_wells_with_plunger - + pump_loss_rate["LU-no plunger"] * frac_wells_with_non_plunger + pump_loss_rate["LU-plunger"] * frac_wells_with_plunger + + pump_loss_rate["LU-no plunger"] * frac_wells_with_non_plunger ) - pump_loss_rate.drop( - "LU-plunger", inplace=True - ) # TBD: drop both at same time + pump_loss_rate.drop("LU-plunger", inplace=True) # TBD: drop both at same time pump_loss_rate.drop("LU-no plunger", inplace=True) pump_loss_rate = pump_loss_rate.sum() @@ -998,11 +943,7 @@ def get_completion_and_workover_C1_rate(self): float: The total C1 rate for completion and workover events in the well system. """ oil_sands_mine = self.oil_sands_mine - completion_event = ( - self.num_prod_wells - if oil_sands_mine == "None" - else ureg.Quantity(0, "frac") - ) + completion_event = self.num_prod_wells if oil_sands_mine == "None" else ureg.Quantity(0, "frac") workover_event = completion_event * self.attr("workovers_per_well") is_flaring = self.attr("is_flaring") @@ -1016,21 +957,15 @@ def find_value(df, is_hydraulic_fracture, well_type, is_flaring, is_REC): & (df["type"] == well_type) & (df["is_flaring"] == is_flaring) & (df["is_REC"] == is_REC) - ] + ] - return ( - result["value"].values[0] - if not result.empty - else ureg.Quantity(0, "tonne") - ) + return result["value"].values[0] if not result.empty else ureg.Quantity(0, "tonne") def calculate_C1_rate(event, well_type): fracture_rate = find_value(df, "Yes", well_type, is_flaring, is_REC) no_fracture_rate = find_value(df, "No", well_type, is_flaring, "No") - C1_rate = fracture_rate * frac_well_fractured + no_fracture_rate * ( - 1 - frac_well_fractured - ) + C1_rate = fracture_rate * frac_well_fractured + no_fracture_rate * (1 - frac_well_fractured) return C1_rate * event completion_C1_rate = calculate_C1_rate(completion_event, "Completion") @@ -1079,9 +1014,7 @@ def validate(self): for cycle in self.cycles: if proc in cycle: - msgs.append( - f"{proc.boundary} boundary {proc} is in one or more cycles." - ) + msgs.append(f"{proc.boundary} boundary {proc} is in one or more cycles.") break # There will generally be far fewer Processes outside the system boundary than within, @@ -1096,9 +1029,7 @@ def validate(self): is_inside = procs[0] not in beyond is_beyond = not is_inside # improves readability for proc in procs: - if (is_inside and proc in beyond) or ( - is_beyond and proc not in beyond - ): + if (is_inside and proc in beyond) or (is_beyond and proc not in beyond): msgs.append(f"{agg} spans the {proc.boundary} boundary.") if self.attr("steam_flooding") and not self.attr("SOR"): @@ -1119,9 +1050,7 @@ def report(self, include_streams=False): if include_streams: _logger.debug(f"\n*** Streams for field '{name}'") for stream in self.streams(): - _logger.debug( - f"{stream} (tonne/day)\n{dequantify_dataframe(stream.components)}\n" - ) + _logger.debug(f"{stream} (tonne/day)\n{dequantify_dataframe(stream.components)}\n") _logger.debug(f"{self}\nEnergy consumption:\n{self.energy.data}") _logger.debug( @@ -1194,9 +1123,7 @@ def _compute_graph_sections(self): run_afters = {process for process in processes if process.run_after} - cycle_independent = ( - set(processes) - procs_in_cycles - cycle_dependent - run_afters - ) + cycle_independent = set(processes) - procs_in_cycles - cycle_dependent - run_afters return cycle_independent, procs_in_cycles, cycle_dependent, run_afters def check_enabled_processes(self): @@ -1274,9 +1201,7 @@ def run_procs_in_order(processes): while True: iter_count += 1 if iter_count > max_iter: - raise OpgeeMaxIterationsReached( - f"Maximum iterations ({max_iter}) reached without convergence" - ) + raise OpgeeMaxIterationsReached(f"Maximum iterations ({max_iter}) reached without convergence") try: for proc in ordered_cycle: @@ -1367,9 +1292,7 @@ def find_stream(self, name, raiseError=True): stream = self.stream_dict.get(name) if stream is None and raiseError: - raise OpgeeException( - f"Stream named '{name}' was not found in field '{self.name}'" - ) + raise OpgeeException(f"Stream named '{name}' was not found in field '{self.name}'") return stream @@ -1386,9 +1309,7 @@ def find_process(self, name, raiseError=True): process = self.process_dict.get(name) if process is None and raiseError: - raise OpgeeException( - f"Process '{name}' was not found in field '{self.name}'" - ) + raise OpgeeException(f"Process '{name}' was not found in field '{self.name}'") return process @@ -1421,9 +1342,7 @@ def from_xml(cls, elt, parent=None): field.set_enabled(attrib.get("enabled", "1")) field.set_extend(attrib.get("extend", "0")) - field.set_modifies( - attrib.get("modified") - ) # "modified" attr is changed to "modified" after merging + field.set_modifies(attrib.get("modified")) # "modified" attr is changed to "modified" after merging aggs = instantiate_subelts(elt, Aggregator, parent=field) procs = instantiate_subelts(elt, Process, parent=field) @@ -1439,7 +1358,7 @@ def from_xml(cls, elt, parent=None): streams=streams, process_choice_dict=process_choice_dict, ) - + # need to recache process attributes to pick up smart defaults for proc in field.processes(): proc.cache_attributes() @@ -1527,14 +1446,12 @@ def resolve_process_choices(self, process_choice_dict=None): procs, streams = group.processes_and_streams(self) # remember the ones to enable - if (group_name == selected_group_name): + if group_name == selected_group_name: to_enable.extend(procs) to_enable.extend(streams) # Handle nested process groups in the enabled group - self.resolve_process_choices( - process_choice_dict=group.process_choice_dict - ) + self.resolve_process_choices(process_choice_dict=group.process_choice_dict) # disable all objects in all groups for obj in procs + streams: @@ -1545,7 +1462,6 @@ def resolve_process_choices(self, process_choice_dict=None): obj.set_enabled(True) def sum_process_energy(self, processes_to_exclude=None) -> Energy: - total = Energy() processes_to_exclude = processes_to_exclude or [] for proc in self.processes(): @@ -1666,9 +1582,7 @@ def WIR_default(self, wor): # =J86+1 [J86 is WOR default, 6] return wor + 1 - @SmartDefault.register( - "stabilizer_column", ["GOR", "gas_lifting", "oil_sands_mine"] - ) + @SmartDefault.register("stabilizer_column", ["GOR", "gas_lifting", "oil_sands_mine"]) def stabilizer_default(self, GOR, gas_lifting, oil_sands_mine): # =IF(OR(J55+J56=1,AND(J85<=500,J52=0)),0,1) # J52 = gas_lifting (binary) @@ -1678,9 +1592,7 @@ def stabilizer_default(self, GOR, gas_lifting, oil_sands_mine): # # Note: in OPGEEv4, there's one attribute 'oil_sands_mine' that can have values # 'None', 'Integrated with upgrader', or 'Non-integrated with upgrader'. - return ( - 0 if (oil_sands_mine != "None") or (not gas_lifting and GOR <= 500) else 1 - ) + return 0 if (oil_sands_mine != "None") or (not gas_lifting and GOR <= 500) else 1 # gas flooding injection ratio @SmartDefault.register("GFIR", ["flood_gas_type", "GOR"]) @@ -1709,11 +1621,7 @@ def depth_default(self, GOR): def res_press_default(self, country, depth, steam_flooding): # =IF(AND('Active Field'!J59="California",'Active Field'!J54=1),100,0.5*(J62*0.43)) # J59 = country, J62 = depth, J54 = steam_flooding - return ( - 100.0 - if (country == "California" and steam_flooding) - else 0.5 * depth.to("ft").m * 0.43 - ) + return 100.0 if (country == "California" and steam_flooding) else 0.5 * depth.to("ft").m * 0.43 @SmartDefault.register("res_temp", ["depth"]) def res_temp_default(self, depth): @@ -1735,9 +1643,7 @@ def num_producing_wells_default(self, oil_sands_mine, oil_prod): # shouldn't exist for oils sands mines. return 1 if oil_sands_mine != "None" else max(1.0, round(oil_prod.to("bbl_oil/d").m / 87.5, 0)) - @SmartDefault.register( - "num_water_inj_wells", ["oil_sands_mine", "oil_prod", "num_prod_wells"] - ) + @SmartDefault.register("num_water_inj_wells", ["oil_sands_mine", "oil_prod", "num_prod_wells"]) def oil_prod_default(self, oil_sands_mine, oil_prod, num_prod_wells): # =IF(OR(Oil_sands_mine_int_01=1,Oil_sands_mine_nonint_01=1), # 0, @@ -1764,24 +1670,16 @@ def oil_prod_default(self, oil_sands_mine, oil_prod, num_prod_wells): return roundup(num_prod_wells * fraction, 0) - @SmartDefault.register( - "HeavyOilDilution.fraction_diluent", ["oil_sands_mine", "upgrader_type"] - ) + @SmartDefault.register("HeavyOilDilution.fraction_diluent", ["oil_sands_mine", "upgrader_type"]) def fraction_diluent_default(self, oil_sands_mine, upgrader_type): # =IF(AND(J56=1,J111=0),0.3,0) [J56 = 'oil sands mine nonint'; ; J111 = upgrader_type - return ( - 0.3 - if (oil_sands_mine == "Integrated with diluent" and upgrader_type == "None") - else 0.0 - ) + return 0.3 if (oil_sands_mine == "Integrated with diluent" and upgrader_type == "None") else 0.0 @SmartDefault.register("fraction_elec_onsite", ["offshore"]) def fraction_elec_onsite_default(self, offshore): return 1.0 if offshore else 0.0 - @SmartDefault.register( - "fraction_remaining_gas_inj", ["natural_gas_reinjection", "gas_flooding"] - ) + @SmartDefault.register("fraction_remaining_gas_inj", ["natural_gas_reinjection", "gas_flooding"]) def fraction_remaining_gas_inj_default(self, natural_gas_reinjection, gas_flooding): # =IF(J53=1,1,IF(J50=1,0.5,0)) [J53 = gas_flooding, J50 = natural_gas_reinjection] return 1.0 if gas_flooding else (0.5 if natural_gas_reinjection else 0.0) @@ -1808,17 +1706,15 @@ def field_development_intensity_default(self, offshore): def common_gas_process_choice_default(self, oil_sands_mine): # Disable the ancillary group of gas-related processes when there is oil sand mine. # Otherwise enable all of those processes. - return 'None' if oil_sands_mine != 'None' else 'All' + return "None" if oil_sands_mine != "None" else "All" - @SmartDefault.register('prod_water_inlet_temp', ['country']) + @SmartDefault.register("prod_water_inlet_temp", ["country"]) def prod_water_inlet_temp_default(self, country): + temperature = 340 if country == "Canada" else 140 + return ureg.Quantity(temperature, "degF") - temperature = 340 if country == 'Canada' else 140 - return ureg.Quantity(temperature, 'degF') - - @SmartDefault.register('num_gas_inj_wells', ['num_prod_wells']) + @SmartDefault.register("num_gas_inj_wells", ["num_prod_wells"]) def num_gas_inj_wells_default(self, num_prod_wells): return num_prod_wells * 0.25 # TODO: decide how to handle "associated gas defaults", which is just global vs CA-LCFS values currently - diff --git a/opgee/graph.py b/opgee/graph.py index d0953342..1cef3a2e 100644 --- a/opgee/graph.py +++ b/opgee/graph.py @@ -16,27 +16,31 @@ _logger = getLogger(__name__) + def add_subclasses(graph, cls, show_process_subclasses=False): name = cls.__name__ - graph.add_node(pydot.Node(name, shape='box')) + graph.add_node(pydot.Node(name, shape="box")) if (not show_process_subclasses) and (cls == Process): return subs = cls.__subclasses__() for sub in subs: - graph.add_edge(pydot.Edge(name, sub.__name__, color='black')) + graph.add_edge(pydot.Edge(name, sub.__name__, color="black")) add_subclasses(graph, sub, show_process_subclasses=show_process_subclasses) + def display_in_notebook(graph): from .utils import ipython_info # if in a notebook, also display it directly - if ipython_info() == 'notebook': + if ipython_info() == "notebook": from IPython.display import Image, display + png = graph.create_png() display(Image(png)) + def write_class_diagram(pathname, show_process_subclasses=False): """ Create and save a graph of the class hierarchy starting from OpgeeObject. @@ -47,13 +51,14 @@ def write_class_diagram(pathname, show_process_subclasses=False): :param limit: (str) either 'all' or 'core') :return: None """ - graph = pydot.Dot('classes', graph_type='graph', bgcolor='white') + graph = pydot.Dot("classes", graph_type="graph", bgcolor="white") add_subclasses(graph, OpgeeObject, show_process_subclasses=show_process_subclasses) _logger.info(f"Writing {pathname}") graph.write_png(pathname) display_in_notebook(graph) + def write_model_diagram(model, pathname, levels=0): """ Create a network graph from an OPGEE Model. @@ -64,6 +69,7 @@ def write_model_diagram(model, pathname, levels=0): A value of zero implies no limit. :return: None """ + def name_of(obj): class_name = obj.__class__.__name__ obj_name = obj.name @@ -72,16 +78,16 @@ def name_of(obj): def add_tree(graph, obj, level): name = name_of(obj) _logger.debug(f"Adding node '{name}'") - graph.add_node(pydot.Node(name, shape='box')) + graph.add_node(pydot.Node(name, shape="box")) if levels == 0 or level < levels: level += 1 for child in obj.children(): add_tree(graph, child, level) _logger.debug(f"Adding edge('{name}', '{name_of(child)}')") - graph.add_edge(pydot.Edge(name, name_of(child), color='black')) + graph.add_edge(pydot.Edge(name, name_of(child), color="black")) - graph = pydot.Dot('model', graph_type='graph', bgcolor='white') + graph = pydot.Dot("model", graph_type="graph", bgcolor="white") for obj in model.fields(): add_tree(graph, obj, 1) @@ -92,12 +98,14 @@ def add_tree(graph, obj, level): graph.write_png(pathname) display_in_notebook(graph) + def write_process_diagram(field, pathname): graph = create_process_diagram(field) _logger.info(f"Writing {pathname}") graph.write_png(pathname) display_in_notebook(graph) + def create_process_diagram(field: Field): graph = pydot.Dot("model", graph_type="digraph", bgcolor="white") diff --git a/opgee/gui/app.py b/opgee/gui/app.py index 849eef2f..54e8f443 100644 --- a/opgee/gui/app.py +++ b/opgee/gui/app.py @@ -19,88 +19,91 @@ # } # } + def app_layout(app, model, analysis): analysis_names = [analysis.name for analysis in model.analyses()] # noinspection PyCallingNonCallable - layout = html.Div([ - dcc.Store(id='analysis-and-field', storage_type='session'), - - # TBD: Experiment to see if client-side function fixes graph resizing problem, per - # https://stackoverflow.com/questions/55462861/dash-dynamic-layout-does-not-propagate-resized-graph-dimensions-until-window-i - html.Div(id="output-clientside"), - - html.Div([ - html.H1(app.title), - - html.Div([ - html.Center([ - html.Span("Model: ", style=label_style), - html.Span(f"{model.pathnames}"), - horiz_space, - - html.Span("Analysis: ", style=label_style), - dcc.Dropdown( - id='analysis-selector', - placeholder='Select analysis...', - options=[{'value': name, 'label': name} for name in analysis_names], - value=analysis.name, - style=pulldown_style, - ), - horiz_space, - - html.Span('Field: ', style=label_style), - dcc.Dropdown( - id='field-selector', - placeholder='Select field...', - options=[{'value': 'none', 'label': 'none'}], - value='none', - style=pulldown_style, - ) - ]), - html.Br(), - html.Button('Run model', id='run-button', n_clicks=0), - dcc.Markdown(id='run-model-status'), - ], - # style = {'height': '130px'} - ), - ], - style={'textAlign': 'center'} - ), - - html.Div([ - dcc.Tabs( - id="tabs", - value='processes', - parent_className='custom-tabs', - className='custom-tabs-container', - children=[ - dcc.Tab( - children=[], # see processes_layout() - label='Processes', - value='processes', - className='custom-tab', - selected_className='custom-tab--selected' - ), - dcc.Tab( - children=[], # see settings_layout() - label='Settings', - value='settings', - className='custom-tab', - selected_className='custom-tab--selected' + layout = html.Div( + [ + dcc.Store(id="analysis-and-field", storage_type="session"), + # TBD: Experiment to see if client-side function fixes graph resizing problem, per + # https://stackoverflow.com/questions/55462861/dash-dynamic-layout-does-not-propagate-resized-graph-dimensions-until-window-i + html.Div(id="output-clientside"), + html.Div( + [ + html.H1(app.title), + html.Div( + [ + html.Center( + [ + html.Span("Model: ", style=label_style), + html.Span(f"{model.pathnames}"), + horiz_space, + html.Span("Analysis: ", style=label_style), + dcc.Dropdown( + id="analysis-selector", + placeholder="Select analysis...", + options=[{"value": name, "label": name} for name in analysis_names], + value=analysis.name, + style=pulldown_style, + ), + horiz_space, + html.Span("Field: ", style=label_style), + dcc.Dropdown( + id="field-selector", + placeholder="Select field...", + options=[{"value": "none", "label": "none"}], + value="none", + style=pulldown_style, + ), + ] + ), + html.Br(), + html.Button("Run model", id="run-button", n_clicks=0), + dcc.Markdown(id="run-model-status"), + ], + # style = {'height': '130px'} ), - dcc.Tab( - children=[], # see results_layout() - label='Results', - value='results', - className='custom-tab', - selected_className='custom-tab--selected' + ], + style={"textAlign": "center"}, + ), + html.Div( + [ + dcc.Tabs( + id="tabs", + value="processes", + parent_className="custom-tabs", + className="custom-tabs-container", + children=[ + dcc.Tab( + children=[], # see processes_layout() + label="Processes", + value="processes", + className="custom-tab", + selected_className="custom-tab--selected", + ), + dcc.Tab( + children=[], # see settings_layout() + label="Settings", + value="settings", + className="custom-tab", + selected_className="custom-tab--selected", + ), + dcc.Tab( + children=[], # see results_layout() + label="Results", + value="results", + className="custom-tab", + selected_className="custom-tab--selected", + ), + ], ), + html.Div(id="tab-content"), ] ), - html.Div(id='tab-content') - ]) - ]) + ] + ) return layout @@ -112,17 +115,21 @@ def main(args): use_default_model = not args.no_default_model - mf = ModelFile(args.model_file, add_stream_components=args.add_stream_components, - use_class_path=args.use_class_path, use_default_model=use_default_model) + mf = ModelFile( + args.model_file, + add_stream_components=args.add_stream_components, + use_class_path=args.use_class_path, + use_default_model=use_default_model, + ) model = mf.model analysis_name = args.analysis initial_analysis = model.get_analysis(analysis_name) # import the css template, and pass the css template into dash - external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] + external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"] app = dash.Dash(__name__, external_stylesheets=external_stylesheets) - app.config['suppress_callback_exceptions'] = True + app.config["suppress_callback_exceptions"] = True app.title = "OPGEEv" + VERSION # OPGEEv4.0a0 # TBD: @@ -132,9 +139,9 @@ def main(args): # # N.B. use "app.config['suppress_callback_exceptions'] = True" to not need to call tab-layout fns in this layout def - process_pane = ProcessPane(app, model) + process_pane = ProcessPane(app, model) settings_pane = SettingsPane(app, model) - results_pane = ResultsPane(app, model) + results_pane = ResultsPane(app, model) app.layout = app_layout(app, model, initial_analysis) @@ -145,28 +152,25 @@ def main(args): [Input("network-layout", "figure")], ) - @app.callback( - Output('field-selector', 'options'), - Input('analysis-selector', 'value') - ) + @app.callback(Output("field-selector", "options"), Input("analysis-selector", "value")) def field_pulldown(analysis_name): analysis = model.get_analysis(analysis_name) field_names = [field.name for field in analysis.fields()] - options = [{'label': name, 'value': name} for name in field_names] + options = [{"label": name, "value": name} for name in field_names] return options @app.callback( - Output('field-selector', 'value'), - Input('field-selector', 'options'), + Output("field-selector", "value"), + Input("field-selector", "options"), ) def field_pulldown(options): - value = options[0]['value'] + value = options[0]["value"] return value @app.callback( - Output('analysis-and-field', 'data'), - Input('field-selector', 'value'), - Input('analysis-selector', 'value'), + Output("analysis-and-field", "data"), + Input("field-selector", "value"), + Input("analysis-selector", "value"), ) def switch_fields(field_name, analysis_name): analysis = model.get_analysis(analysis_name) @@ -175,9 +179,8 @@ def switch_fields(field_name, analysis_name): return dict(analysis=analysis_name, field=field_name) @app.callback( - Output('run-model-status', 'children'), - Input('run-button', 'n_clicks'), - State('analysis-and-field', 'data')) + Output("run-model-status", "children"), Input("run-button", "n_clicks"), State("analysis-and-field", "data") + ) def update_output(n_clicks, analysis_and_field): if n_clicks: analysis, field = get_analysis_and_field(model, analysis_and_field) @@ -189,20 +192,20 @@ def update_output(n_clicks, analysis_and_field): return "Model has not been run" @app.callback( - Output('tab-content', 'children'), - Input('tabs', 'value'), - Input('analysis-and-field', 'data'), + Output("tab-content", "children"), + Input("tabs", "value"), + Input("analysis-and-field", "data"), ) def render_content(tab, analysis_and_field): analysis, field = get_analysis_and_field(model, analysis_and_field) - if tab == 'processes': + if tab == "processes": return process_pane.get_layout(field) - elif tab == 'settings': + elif tab == "settings": return settings_pane.get_layout(field) - elif tab == 'results': + elif tab == "results": return results_pane.get_layout(field) # @app.callback( diff --git a/opgee/gui/process_pane.py b/opgee/gui/process_pane.py index 4e03454d..6c69fcf4 100644 --- a/opgee/gui/process_pane.py +++ b/opgee/gui/process_pane.py @@ -11,97 +11,100 @@ class ProcessPane(OpgeePane): - - def get_layout(self, field, show_stream_contents=False, show_disabled_procs=False, - layout_name='breadthfirst'): + def get_layout(self, field, show_stream_contents=False, show_disabled_procs=False, layout_name="breadthfirst"): # the main row - layout = html.Div([ - - html.Center( + layout = html.Div( + [ + html.Center( + html.Div( + className="row", + children=[ + gui_switches(), + html.Br(), + ], + ), + ), + # graph component html.Div( + id="field-network-graph-div", className="row", children=[ - gui_switches(), - html.Br(), - ] + field_network_graph( + field, + show_stream_contents=show_stream_contents, + show_disabled_procs=show_disabled_procs, + layout_name=layout_name, + ) + ], + style={ + "resize": "vertical", + # 'overflow': 'auto', + # 'height': '35%', + "autosize": "true", + "display": "flex", + }, ), - ), - - # graph component - html.Div( - id='field-network-graph-div', - className="row", - children=[ - field_network_graph(field, - show_stream_contents=show_stream_contents, - show_disabled_procs=show_disabled_procs, - layout_name=layout_name) - ], - style={ - 'resize': 'vertical', - # 'overflow': 'auto', - # 'height': '35%', - 'autosize': 'true', - 'display': 'flex', - } - ), - - html.Div( - children=[], - className="row", - id='emissions-table', - style={ - 'background-color': 'aliceblue', - 'border-radius': '4px', - 'border': '1px solid', - } - ), - - html.Br(), - - # output components - html.Div( - className="twelve columns", - children=[ - html.Div( - className='six columns', - children=[ - dcc.Markdown(d(""" + html.Div( + children=[], + className="row", + id="emissions-table", + style={ + "background-color": "aliceblue", + "border-radius": "4px", + "border": "1px solid", + }, + ), + html.Br(), + # output components + html.Div( + className="twelve columns", + children=[ + html.Div( + className="six columns", + children=[ + dcc.Markdown( + d(""" **Emissions and energy use** - """), style={'margin-left': '4px'}), - html.Pre(id='emissions-and-energy', - style={'margin-left': '8px'}) - ], - style={ - # 'height': '400px', - 'display': 'inline-block', - 'background-color': 'aliceblue', - 'border-radius': '4px', - 'border': '1px solid', - }), - - html.Div( - className='six columns', - children=[ - dcc.Markdown(d(""" + """), + style={"margin-left": "4px"}, + ), + html.Pre(id="emissions-and-energy", style={"margin-left": "8px"}), + ], + style={ + # 'height': '400px', + "display": "inline-block", + "background-color": "aliceblue", + "border-radius": "4px", + "border": "1px solid", + }, + ), + html.Div( + className="six columns", + children=[ + dcc.Markdown( + d(""" **Stream Data** (tonne/day) - """), style={'margin-left': '8px'}), - html.Div( - children=[], - id='stream-data', - style={'margin-left': '8px'}, - ) - ], - style={ - # 'height': '400px', - 'display': 'inline-block', - 'background-color': 'aliceblue', - 'border-radius': '4px', - 'border': '1px solid', - }) - ], - style={'height': '400px', 'display': 'inline-block'}) - ], + """), + style={"margin-left": "8px"}, + ), + html.Div( + children=[], + id="stream-data", + style={"margin-left": "8px"}, + ), + ], + style={ + # 'height': '400px', + "display": "inline-block", + "background-color": "aliceblue", + "border-radius": "4px", + "border": "1px solid", + }, + ), + ], + style={"height": "400px", "display": "inline-block"}, + ), + ], className="row", ) return layout @@ -111,14 +114,15 @@ def add_callbacks(self): model = self.model @app.callback( - Output('emissions-and-energy', 'children'), - Input('network-layout', 'tapNodeData'), - State('analysis-and-field', 'data')) + Output("emissions-and-energy", "children"), + Input("network-layout", "tapNodeData"), + State("analysis-and-field", "data"), + ) def display_emissions_and_energy(node_data, analysis_and_field): if node_data: analysis, field = get_analysis_and_field(model, analysis_and_field) - proc_name = node_data['id'] + proc_name = node_data["id"] proc = field.find_process(proc_name) digits = 2 @@ -131,59 +135,61 @@ def display_emissions_and_energy(node_data, analysis_and_field): # emissions_str = f"\nEmissions: (tonne/day)\n{values}" rates = proc.get_energy_rates() - values = '\n'.join([f"{name:19s} {round(value.m, digits)}" for name, value in rates.items()]) + values = "\n".join([f"{name:19s} {round(value.m, digits)}" for name, value in rates.items()]) energy_str = f"\n\nEnergy use: (mmbtu/day)\n{values}" # display intermediate results - intermediate_str = '' + intermediate_str = "" intermediate = proc.get_intermediate_results() if intermediate is not None: - intermediate_str += '\n\n** Intermediate results **' + intermediate_str += "\n\n** Intermediate results **" for key, (energy, emissions) in intermediate.items(): rates = emissions.rates(gwp=analysis.gwp) em_str = f"\nEmissions: (tonne/day)\n{rates.astype(float)}" rates = energy.rates() - values = '\n'.join([f"{name:19s} {round(value.m, digits)}" for name, value in rates.items()]) + values = "\n".join([f"{name:19s} {round(value.m, digits)}" for name, value in rates.items()]) en_str = f"\nEnergy use: (mmbtu/day)\n{values}" intermediate_str += f"\n\n{key}:\n{em_str}\n{en_str}" return header + emissions_str + energy_str + intermediate_str else: - return '' + return "" - @app.callback(Output('stream-data', 'children'), - Input('network-layout', 'tapEdgeData'), - State('analysis-and-field', 'data')) + @app.callback( + Output("stream-data", "children"), + Input("network-layout", "tapEdgeData"), + State("analysis-and-field", "data"), + ) def display_edge_data(data, analysis_and_field): import pandas as pd if data: analysis, field = get_analysis_and_field(model, analysis_and_field) - name = data['id'] + name = data["id"] stream = field.find_stream(name) - with pd.option_context('display.max_rows', None, - 'display.precision', 3): + with pd.option_context("display.max_rows", None, "display.precision", 3): nonzero = stream.non_zero_flow_rates() - components = (str(nonzero.astype(float)) - if nonzero is not None and len(nonzero) - else '') + components = ( + str(nonzero.astype(float)) if nonzero is not None and len(nonzero) else "" + ) - contents = '\n '.join(stream.contents) + contents = "\n ".join(stream.contents) text = f"Name: {name}\nT: {stream.tp.T}, P: {stream.tp.P}\nContains: {contents}\n{components}" return html.Pre(text) @app.callback( - Output('emissions-table', 'children'), - Input('tabs', 'value'), - Input('run-model-status', 'children'), - Input('analysis-and-field', 'data')) + Output("emissions-table", "children"), + Input("tabs", "value"), + Input("run-model-status", "children"), + Input("analysis-and-field", "data"), + ) def update_result_table(tab, status, analysis_and_field): analysis, field = get_analysis_and_field(model, analysis_and_field) - style = {'margin-left': '16px'} + style = {"margin-left": "16px"} # recursively create expanding aggregator structure with emissions (table, eventually) def add_children(container, elt): @@ -196,8 +202,7 @@ def add_children(container, elt): if container.procs: enabled_procs = [proc for proc in container.procs if proc.is_enabled()] sorted_procs = sorted(enabled_procs, key=name_of) - div = html.Div(style=style, - children=[emissions_table(analysis, sorted_procs)]) + div = html.Div(style=style, children=[emissions_table(analysis, sorted_procs)]) elt.children.append(div) item = html.Details(open=True, children=[html.Summary("Process Emissions")]) @@ -205,122 +210,119 @@ def add_children(container, elt): return item @app.callback( - Output('field-network-graph-div', 'children'), - Input('show-stream-contents', 'value'), - Input('show-disabled-procs', 'value'), - Input('graph-layout-selector', 'value'), - State('analysis-and-field', 'data'), + Output("field-network-graph-div", "children"), + Input("show-stream-contents", "value"), + Input("show-disabled-procs", "value"), + Input("graph-layout-selector", "value"), + State("analysis-and-field", "data"), ) - def redraw_network_graph(show_stream_contents, show_disabled_procs, - layout_name, analysis_and_field): + def redraw_network_graph(show_stream_contents, show_disabled_procs, layout_name, analysis_and_field): analysis, field = get_analysis_and_field(model, analysis_and_field) - return field_network_graph(field, - show_stream_contents=show_stream_contents, - show_disabled_procs=show_disabled_procs, - layout_name=layout_name) + return field_network_graph( + field, + show_stream_contents=show_stream_contents, + show_disabled_procs=show_disabled_procs, + layout_name=layout_name, + ) # Load extra layouts # cyto.load_extra_layouts() # required for cose-bilkent -def field_network_graph(field, show_stream_contents=False, show_disabled_procs=False, - layout_name='breadthfirst'): +def field_network_graph(field, show_stream_contents=False, show_disabled_procs=False, layout_name="breadthfirst"): def edge_class(stream): if stream.enabled and stream.dst_proc.enabled and stream.src_proc.enabled: - return 'enabled-edge' + return "enabled-edge" else: - return 'disabled-edge' + return "disabled-edge" def node_class(proc): - return 'disabled-node' if not proc.enabled else ( - 'boundary-node' if proc.boundary else 'enabled-node' - ) - - nodes = [{'data': {'id': name, 'label': name}, - 'classes': node_class(proc)} - for name, proc in field.process_dict.items() - if show_disabled_procs or proc.enabled - ] - - edges = [{'data': {'id': name, 'source': s.src_name, 'target': s.dst_name, - 'contents': ', '.join(s.contents)}, 'classes': edge_class(s)} - for name, s in field.stream_dict.items() - if (s.dst_proc and s.src_proc and - (show_disabled_procs or (s.dst_proc.enabled and s.src_proc.enabled))) - ] - - enabled_edge_color = 'maroon' - boundary_node_color = 'brown' - disabled_edge_color = 'gray' - node_color = 'sandybrown' + return "disabled-node" if not proc.enabled else ("boundary-node" if proc.boundary else "enabled-node") + + nodes = [ + {"data": {"id": name, "label": name}, "classes": node_class(proc)} + for name, proc in field.process_dict.items() + if show_disabled_procs or proc.enabled + ] + + edges = [ + { + "data": {"id": name, "source": s.src_name, "target": s.dst_name, "contents": ", ".join(s.contents)}, + "classes": edge_class(s), + } + for name, s in field.stream_dict.items() + if (s.dst_proc and s.src_proc and (show_disabled_procs or (s.dst_proc.enabled and s.src_proc.enabled))) + ] + + enabled_edge_color = "maroon" + boundary_node_color = "brown" + disabled_edge_color = "gray" + node_color = "sandybrown" layout = cyto.Cytoscape( - id='network-layout', + id="network-layout", responsive=True, elements=nodes + edges, autounselectify=False, autoungrabify=True, userPanningEnabled=True, # False, # may need to reconsider this when model is bigger userZoomingEnabled=True, # False, # automatic zoom when user changes browser size still works - style={'width': '100%', - # 'height': '100%', - 'height': '600px', - # not sure any of this really works - 'autosize': 'true', - 'resize': 'inherit', - 'overflow': 'auto', - 'display': 'flex', - }, - # style={'width': '100%', 'height': '500px', 'resize': 'inherit'}, - layout={ - 'name': layout_name, - 'roots': '[id = "Reservoir"]' + style={ + "width": "100%", + # 'height': '100%', + "height": "600px", + # not sure any of this really works + "autosize": "true", + "resize": "inherit", + "overflow": "auto", + "display": "flex", }, + # style={'width': '100%', 'height': '500px', 'resize': 'inherit'}, + layout={"name": layout_name, "roots": '[id = "Reservoir"]'}, stylesheet=[ { - 'selector': 'node', - 'style': { - 'label': 'data(id)', - 'background-color': node_color, - 'width': '30', - 'height': '30', - } + "selector": "node", + "style": { + "label": "data(id)", + "background-color": node_color, + "width": "30", + "height": "30", + }, }, { - 'selector': '.boundary-node', - 'style': { - 'background-color': boundary_node_color, - } + "selector": ".boundary-node", + "style": { + "background-color": boundary_node_color, + }, }, { - 'selector': '.disabled-node', - 'style': { - 'background-color': 'lightgray', - } + "selector": ".disabled-node", + "style": { + "background-color": "lightgray", + }, }, { - 'selector': 'edge', - 'style': { - 'curve-style': 'bezier', + "selector": "edge", + "style": { + "curve-style": "bezier", # 'arrow-scale': 1.5, # 'mid-target-arrow-color': edge_color, # 'mid-target-arrow-shape': 'triangle', - 'target-arrow-shape': 'triangle', - 'target-arrow-color': enabled_edge_color, - 'line-color': enabled_edge_color, - 'line-opacity': 0.60, - 'width': 1, - 'target-distance-from-node': 1, # stop just short of the node - 'source-distance-from-node': 1, - + "target-arrow-shape": "triangle", + "target-arrow-color": enabled_edge_color, + "line-color": enabled_edge_color, + "line-opacity": 0.60, + "width": 1, + "target-distance-from-node": 1, # stop just short of the node + "source-distance-from-node": 1, # "overlay-padding": "30px", - 'label': 'data(contents)', - 'text-opacity': 0.6 if show_stream_contents else 0.0, - 'text-rotation': 'autorotate', - 'text-margin-y': -10, - 'text-margin-x': 7, - 'text-background-color': 'blue', + "label": "data(contents)", + "text-opacity": 0.6 if show_stream_contents else 0.0, + "text-rotation": "autorotate", + "text-margin-y": -10, + "text-margin-x": 7, + "text-background-color": "blue", "font-size": "14px", }, }, @@ -333,13 +335,13 @@ def node_class(proc): # } # }, { - 'selector': '.disabled-edge', - 'style': { - 'target-arrow-color': disabled_edge_color, - 'line-color': disabled_edge_color, - } + "selector": ".disabled-edge", + "style": { + "target-arrow-color": disabled_edge_color, + "line-color": disabled_edge_color, + }, }, - ] + ], ) return layout @@ -349,7 +351,7 @@ def emissions_table(analysis, procs): import pandas as pd from ..emissions import Emissions - columns = [{'name': 'Name', 'id': 'Name'}] + [{'name': col, 'id': col} for col in Emissions.categories] + columns = [{"name": "Name", "id": "Name"}] + [{"name": col, "id": col} for col in Emissions.categories] def series_for_df(proc): rates = proc.get_emission_rates(analysis).astype(float) @@ -358,46 +360,42 @@ def series_for_df(proc): return s df = pd.DataFrame(data=[series_for_df(proc) for proc in procs]) - totals = df.sum(axis='rows') - totals.name = 'Total' + totals = df.sum(axis="rows") + totals.name = "Total" df.append(totals) df.reset_index(inplace=True) - df.rename({'index': 'Name'}, axis='columns', inplace=True) + df.rename({"index": "Name"}, axis="columns", inplace=True) # convert to scientific notation for col_name, col in df.iteritems(): if col.dtype == float: - df[col_name] = col.apply(lambda x: '{:.2E}'.format(x)) + df[col_name] = col.apply(lambda x: "{:.2E}".format(x)) - data = df.to_dict('records') + data = df.to_dict("records") - text_cols = ['Name'] + text_cols = ["Name"] tbl = dash_table.DataTable( columns=columns, data=data, style_as_list_view=True, - style_cell={'padding': '5px'}, - style_header={ - 'backgroundColor': 'white', - 'fontWeight': 'bold' - }, + style_cell={"padding": "5px"}, + style_header={"backgroundColor": "white", "fontWeight": "bold"}, style_cell_conditional=[ { - 'if': { - 'column_id': c - }, - 'textAlign': 'left', - 'font-family': 'sans-serif', - } for c in text_cols + "if": {"column_id": c}, + "textAlign": "left", + "font-family": "sans-serif", + } + for c in text_cols ], style_data_conditional=[ { - 'if': { - 'filter_query': '{Name} = "Total"', + "if": { + "filter_query": '{Name} = "Total"', }, - 'fontWeight': 'bold' + "fontWeight": "bold", }, - ] + ], ) return tbl diff --git a/opgee/gui/results_pane.py b/opgee/gui/results_pane.py index 46ceca6d..c0d7c9d9 100644 --- a/opgee/gui/results_pane.py +++ b/opgee/gui/results_pane.py @@ -7,64 +7,54 @@ _logger = getLogger(__name__) -barchart_style = {"width": "440px", 'display': 'inline-block'} +barchart_style = {"width": "440px", "display": "inline-block"} _zero_flow_at_boundary_msg = { "layout": { - "xaxis": { - "visible": False - }, - "yaxis": { - "visible": False - }, + "xaxis": {"visible": False}, + "yaxis": {"visible": False}, "annotations": [ - { - "text": "No figure", - "xref": "paper", - "yref": "paper", - "showarrow": False, - "font": { - "size": 20 - } - } - ] + {"text": "No figure", "xref": "paper", "yref": "paper", "showarrow": False, "font": {"size": 20}} + ], } } class ResultsPane(OpgeePane): - def get_layout(self, field, **kwargs): - layout = html.Div([ - html.H3('Results'), - html.Div('', id='ci-text', - className="row", - # style={'textAlign': "center"}, - ), - html.Center([ - dcc.Graph(id="ci-barchart", style=barchart_style), - horiz_space, - dcc.Graph(id="energy-barchart", style=barchart_style) - ] - ), - ], className="row", + layout = html.Div( + [ + html.H3("Results"), + html.Div( + "", + id="ci-text", + className="row", + # style={'textAlign': "center"}, + ), + html.Center( + [ + dcc.Graph(id="ci-barchart", style=barchart_style), + horiz_space, + dcc.Graph(id="energy-barchart", style=barchart_style), + ] + ), + ], + className="row", style={ # 'display': 'flex', - 'align-items': 'center', - 'textAlign': "center", - 'justify-content': 'center' - } + "align-items": "center", + "textAlign": "center", + "justify-content": "center", + }, ) return layout - def add_callbacks(self): app = self.app model = self.model @app.callback( - Output('ci-text', 'children'), - Input('run-button', 'n_clicks'), - Input('analysis-and-field', 'data')) + Output("ci-text", "children"), Input("run-button", "n_clicks"), Input("analysis-and-field", "data") + ) def ci_text(n_clicks, analysis_and_field): analysis, field = get_analysis_and_field(model, analysis_and_field) @@ -76,9 +66,10 @@ def ci_text(n_clicks, analysis_and_field): return f"CI: {ci.m:0.2f} g CO2e/MJ LHV of {analysis.fn_unit}" @app.callback( - Output('ci-barchart', 'figure'), - Input('ci-text', 'children'), # ensures that we run after ci_text - State('analysis-and-field', 'data')) + Output("ci-barchart", "figure"), + Input("ci-text", "children"), # ensures that we run after ci_text + State("analysis-and-field", "data"), + ) def ci_barchart(ci_text, analysis_and_field): import pandas as pd import plotly.graph_objs as go @@ -93,25 +84,34 @@ def ci_barchart(ci_text, analysis_and_field): fn_unit = analysis.fn_unit.title() - df = pd.DataFrame({"category": [pair[0] for pair in top_level], - "value": [pair[1] for pair in top_level], - "unit": [fn_unit] * len(top_level)}) + df = pd.DataFrame( + { + "category": [pair[0] for pair in top_level], + "value": [pair[1] for pair in top_level], + "unit": [fn_unit] * len(top_level), + } + ) - fig = go.Figure(data=[go.Bar(name=row.category, - x=[row.unit], - y=[row.value], width=[0.7]) for idx, row in df.iterrows()], - layout=go.Layout(barmode='stack')) + fig = go.Figure( + data=[ + go.Bar(name=row.category, x=[row.unit], y=[row.value], width=[0.7]) for idx, row in df.iterrows() + ], + layout=go.Layout(barmode="stack"), + ) - fig.update_layout(yaxis_title=f"g CO2 per MJ of {fn_unit}", # doesn't render in latex: r'g CO$_2$ MJ$^{-1}$', - xaxis_title='Carbon Intensity') + fig.update_layout( + yaxis_title=f"g CO2 per MJ of {fn_unit}", # doesn't render in latex: r'g CO$_2$ MJ$^{-1}$', + xaxis_title="Carbon Intensity", + ) return fig @app.callback( - Output('energy-barchart', 'figure'), - Input('run-button', 'n_clicks'), - Input('ci-barchart', 'figure'), # run this after CI barchart - Input('analysis-and-field', 'data')) + Output("energy-barchart", "figure"), + Input("run-button", "n_clicks"), + Input("ci-barchart", "figure"), # run this after CI barchart + Input("analysis-and-field", "data"), + ) def energy_barchart(n_clicks, ci_figure, analysis_and_field): import pandas as pd import plotly.graph_objs as go @@ -120,7 +120,7 @@ def energy_barchart(n_clicks, ci_figure, analysis_and_field): # Identify procs / aggs outside the boundary of interest and subtract their energy use from total. try: - energy = field.boundary_energy_flow_rate(analysis) + energy = field.boundary_energy_flow_rate(analysis) except ZeroEnergyFlowError: return _zero_flow_at_boundary_msg @@ -129,16 +129,23 @@ def energy_barchart(n_clicks, ci_figure, analysis_and_field): beyond = boundary_proc.beyond_boundary() # Show results for top-level aggregators and procs for the selected field that are within the boundary - top_level = [(obj.name, obj.energy.data.sum()/energy) for obj in field.children() if obj not in beyond] + top_level = [(obj.name, obj.energy.data.sum() / energy) for obj in field.children() if obj not in beyond] - df = pd.DataFrame({"category": [pair[0] for pair in top_level], - "value": [pair[1] for pair in top_level], - "unit": [fn_unit] * len(top_level)}) + df = pd.DataFrame( + { + "category": [pair[0] for pair in top_level], + "value": [pair[1] for pair in top_level], + "unit": [fn_unit] * len(top_level), + } + ) - fig = go.Figure(data=[go.Bar(name=row.category, x=[row.unit], y=[row.value.m], width=[0.7]) for idx, row in df.iterrows()], - layout=go.Layout(barmode='stack')) + fig = go.Figure( + data=[ + go.Bar(name=row.category, x=[row.unit], y=[row.value.m], width=[0.7]) for idx, row in df.iterrows() + ], + layout=go.Layout(barmode="stack"), + ) - fig.update_layout(yaxis_title=f"MJ per MJ of {fn_unit}", - xaxis_title='Energy consumption') + fig.update_layout(yaxis_title=f"MJ per MJ of {fn_unit}", xaxis_title="Energy consumption") return fig diff --git a/opgee/gui/settings_pane.py b/opgee/gui/settings_pane.py index b5a2ed6a..3bf5a533 100644 --- a/opgee/gui/settings_pane.py +++ b/opgee/gui/settings_pane.py @@ -15,42 +15,50 @@ class SettingsPane(OpgeePane): - def get_layout(self, field, **kwargs): proc_names = sorted([proc.name for proc in field.processes()]) proc_sections = [attr_inputs(proc_name) for proc_name in proc_names] sections = [ - attr_inputs('Model'), - attr_inputs('Analysis'), - attr_inputs('Field'), - ] + proc_sections + attr_inputs("Model"), + attr_inputs("Analysis"), + attr_inputs("Field"), + ] + proc_sections - attributes_xml = getParam('OPGEE.UserAttributesFile') or '' + attributes_xml = getParam("OPGEE.UserAttributesFile") or "" # noinspection PyCallingNonCallable - layout = html.Div([ - html.H3('Settings'), - html.Div([ - dcc.Input(id='settings-filename', type='text', debounce=True, pattern=r'^.*\.xml$', - value=attributes_xml, style={'width': '400px'}), - html.Button('Save', id='save-settings-button', n_clicks=0), - dcc.Markdown(id='save-button-status') + layout = html.Div( + [ + html.H3("Settings"), + html.Div( + [ + dcc.Input( + id="settings-filename", + type="text", + debounce=True, + pattern=r"^.*\.xml$", + value=attributes_xml, + style={"width": "400px"}, + ), + html.Button("Save", id="save-settings-button", n_clicks=0), + dcc.Markdown(id="save-button-status"), + ], + style={"height": "100px"}, + ), + html.Div( + sections, + className="row", + ), ], - style={'height': '100px'}), - html.Div(sections, - className="row", - ), - ], style={'textAlign': "center"}, - className="row" + style={"textAlign": "center"}, + className="row", ) return layout - def add_callbacks(self): pass - # # TBD: Really only works on a current field. # @@ -66,7 +74,7 @@ def generate_settings_callback(self, analysis, field): """ app = self.app - class_names = ['Model', 'Analysis', 'Field'] + [proc.name for proc in field.processes()] + class_names = ["Model", "Analysis", "Field"] + [proc.name for proc in field.processes()] attr_defs = AttrDefs.get_instance() class_dict = attr_defs.classes @@ -84,19 +92,21 @@ def generate_settings_callback(self, analysis, field): # First element is the filename field, we pop() this before processing all the generated inputs # state_list = [State('settings-filename', 'value')] + [State(id, 'value') for id in ids] - state_list = [State(id, 'value') for id in ids] + state_list = [State(id, "value") for id in ids] def func(n_clicks, xml_path, *values): if n_clicks == 0 or not values or not xml_path: - return 'Save attributes to an xml file' + return "Save attributes to an xml file" else: self.save_attributes(xml_path, ids, values, analysis, field) return f"Attributes saved to '{xml_path}'" - app.callback(Output('save-button-status', 'children'), - Input('save-settings-button', 'n_clicks'), - Input('settings-filename', 'value'), - *state_list)(func) + app.callback( + Output("save-button-status", "children"), + Input("save-settings-button", "n_clicks"), + Input("settings-filename", "value"), + *state_list, + )(func) @staticmethod def save_attributes(xml_path, ids, values, analysis, field): @@ -121,7 +131,7 @@ def save_attributes(xml_path, ids, values, analysis, field): class_value_dict = defaultdict(list) for id, value in zip(ids, values): - class_name, attr_name = id.split(':') + class_name, attr_name = id.split(":") # Don't write out values that are equal to defaults class_attrs = class_dict.get(class_name) @@ -132,24 +142,24 @@ def save_attributes(xml_path, ids, values, analysis, field): class_value_dict[class_name].append((attr_name, value)) - root = ET.Element('Model') + root = ET.Element("Model") for class_name, value_pairs in class_value_dict.items(): - if class_name == 'Model': + if class_name == "Model": class_elt = root # Model attributes are top level - elif class_name == 'Process': + elif class_name == "Process": class_elt = ET.SubElement(root, class_name) - elif class_name in ('Field', 'Analysis'): - name = field.name if class_name == 'Field' else analysis.name - class_elt = ET.SubElement(root, class_name, attrib={'name': name}) + elif class_name in ("Field", "Analysis"): + name = field.name if class_name == "Field" else analysis.name + class_elt = ET.SubElement(root, class_name, attrib={"name": name}) else: # Process subclass - class_elt = ET.SubElement(root, 'Process', attrib={'name': class_name}) + class_elt = ET.SubElement(root, "Process", attrib={"name": class_name}) for attr_name, value in value_pairs: - elt = ET.SubElement(class_elt, 'A', attrib={'name': attr_name}) + elt = ET.SubElement(class_elt, "A", attrib={"name": attr_name}) elt.text = str(value) # ET.dump(root) @@ -158,7 +168,7 @@ def save_attributes(xml_path, ids, values, analysis, field): path = Path(xml_path) mkdirs(path.parent) - _logger.info('Writing %s', xml_path) + _logger.info("Writing %s", xml_path) tree = ET.ElementTree(root) - tree.write(xml_path, xml_declaration=True, pretty_print=True, encoding='utf-8') + tree.write(xml_path, xml_declaration=True, pretty_print=True, encoding="utf-8") diff --git a/opgee/gui/testapp.py b/opgee/gui/testapp.py index 28e5c961..f3cc46ce 100644 --- a/opgee/gui/testapp.py +++ b/opgee/gui/testapp.py @@ -3,38 +3,39 @@ from dash import dcc, html from dash.dependencies import Input, Output, State, ClientsideFunction -def app_layout(app): - label_style = { - 'font-weight': 'bold' - } +def app_layout(app): + label_style = {"font-weight": "bold"} # noinspection PyCallingNonCallable - layout = html.Div([ - dcc.Store(id='analysis-and-field', storage_type='session'), - - # TBD: Experiment to see if client-side function fixes graph resizing problem, per - # https://stackoverflow.com/questions/55462861/dash-dynamic-layout-does-not-propagate-resized-graph-dimensions-until-window-i - # html.Div(id="output-clientside"), - - html.Div([ - html.H1(app.title), - - html.Div([ - html.Center([ - html.Span("Model: ", style=label_style), - html.Span("Not a real model"), - ]), - html.Br(), - - html.Button('Run model', id='run-button', n_clicks=0), - dcc.Markdown(id='run-model-status'), - ], - # style = {'height': '130px'} + layout = html.Div( + [ + dcc.Store(id="analysis-and-field", storage_type="session"), + # TBD: Experiment to see if client-side function fixes graph resizing problem, per + # https://stackoverflow.com/questions/55462861/dash-dynamic-layout-does-not-propagate-resized-graph-dimensions-until-window-i + # html.Div(id="output-clientside"), + html.Div( + [ + html.H1(app.title), + html.Div( + [ + html.Center( + [ + html.Span("Model: ", style=label_style), + html.Span("Not a real model"), + ] + ), + html.Br(), + html.Button("Run model", id="run-button", n_clicks=0), + dcc.Markdown(id="run-model-status"), + ], + # style = {'height': '130px'} + ), + ], + style={"textAlign": "center"}, ), - ], style={'textAlign': 'center'} - ), - ]) + ] + ) return layout @@ -44,7 +45,7 @@ def main(): # 'https://codepen.io/chriddyp/pen/bWLwgP.css' ] app = dash.Dash(__name__, external_stylesheets=external_stylesheets) - app.config['suppress_callback_exceptions'] = True + app.config["suppress_callback_exceptions"] = True app.title = "Test App" # TBD: use "app.config['suppress_callback_exceptions'] = True" to not need to call tab-layout fns in this layout def @@ -59,9 +60,8 @@ def main(): ) @app.callback( - Output('run-model-status', 'children'), - Input('run-button', 'n_clicks'), - State('analysis-and-field', 'data')) + Output("run-model-status", "children"), Input("run-button", "n_clicks"), State("analysis-and-field", "data") + ) def update_output(n_clicks, analysis_and_field): if n_clicks: return "Model has been run" @@ -70,4 +70,5 @@ def update_output(n_clicks, analysis_and_field): app.run_server(debug=True) + main() diff --git a/opgee/gui/widgets.py b/opgee/gui/widgets.py index 04d440b5..4ec88ff7 100644 --- a/opgee/gui/widgets.py +++ b/opgee/gui/widgets.py @@ -6,18 +6,12 @@ _logger = getLogger(__name__) -horiz_space = html.Span("", style={'width': '50px', 'display': 'inline-block'}) +horiz_space = html.Span("", style={"width": "50px", "display": "inline-block"}) -pulldown_style = { - 'width': '200px', - 'textAlign': 'center', - 'vertical-align': 'middle', - 'display': 'inline-block' -} +pulldown_style = {"width": "200px", "textAlign": "center", "vertical-align": "middle", "display": "inline-block"} + +label_style = {"font-weight": "bold"} -label_style = { - 'font-weight': 'bold' -} class OpgeePane(object): def __init__(self, app, model): @@ -33,36 +27,34 @@ def add_callbacks(self): def get_analysis_and_field(current_model, data): - analysis = current_model.get_analysis(data['analysis']) - field = analysis.get_field(data['field'], raiseError=False) or analysis.first_field() + analysis = current_model.get_analysis(data["analysis"]) + field = analysis.get_field(data["field"], raiseError=False) or analysis.first_field() return analysis, field -int_pattern = r'^\s*\d+\s*$' -float_pattern = r'^\s*((\d+).?(\d*)|(\d*).?(\d+))\s*$' -number_pattern = {'int': int_pattern, 'float': float_pattern} +int_pattern = r"^\s*\d+\s*$" +float_pattern = r"^\s*((\d+).?(\d*)|(\d*).?(\d+))\s*$" + +number_pattern = {"int": int_pattern, "float": float_pattern} + +binary_options = [dict(label="Yes", value=1), dict(label="No", value=0)] -binary_options = [dict(label='Yes', value=1), dict(label='No', value=0)] -def attr_inputs(class_name, direction='h'): +def attr_inputs(class_name, direction="h"): from ..attributes import AttrDefs attr_defs = AttrDefs.get_instance() class_attrs = attr_defs.classes.get(class_name) if not class_attrs: - return '' + return "" attr_dict = class_attrs.attr_dict - details = html.Details( - children=[ - html.Summary(class_name, style={'font-weight': 'bold', 'font-size': '14px'}) - ] - ) + details = html.Details(children=[html.Summary(class_name, style={"font-weight": "bold", "font-size": "14px"})]) det_children = details.children - radio_label_style = {'display': 'inline', 'margin-right': '8px'} if direction == 'h' else None # , 'margin': '4px' + radio_label_style = {"display": "inline", "margin-right": "8px"} if direction == "h" else None # , 'margin': '4px' for attr_name in sorted(attr_dict.keys(), key=str.casefold): id = f"{class_name}:{attr_name}" @@ -72,72 +64,85 @@ def attr_inputs(class_name, direction='h'): title = attr_name pytype = attr_def.pytype - if pytype == 'binary': - input = dcc.RadioItems(id=id, options=binary_options, value=attr_def.default, - labelStyle=radio_label_style, - style={'display': 'inline-block', 'width': "45%"}, - persistence=True) + if pytype == "binary": + input = dcc.RadioItems( + id=id, + options=binary_options, + value=attr_def.default, + labelStyle=radio_label_style, + style={"display": "inline-block", "width": "45%"}, + persistence=True, + ) elif attr_def.option_set: option_dict = class_attrs.option_dict if len(option_dict) == 0: - raise OpgeeException(f'Options for option set {attr_def.option_set} are undefined') + raise OpgeeException(f"Options for option set {attr_def.option_set} are undefined") opt = option_dict[attr_def.option_set] options = [dict(label=label, value=value) for value, label, desc in opt.options] - input = dcc.RadioItems(id=id, options=options, value=opt.default, - labelStyle=radio_label_style, - style={'display': 'inline-block', 'width': "45%"}, - persistence=True) + input = dcc.RadioItems( + id=id, + options=options, + value=opt.default, + labelStyle=radio_label_style, + style={"display": "inline-block", "width": "45%"}, + persistence=True, + ) else: - input_type = 'text' if (pytype is None or pytype == 'str') else 'number' - input = dcc.Input(id=id, type=input_type, debounce=True, - value=magnitude(attr_def.default), - pattern=number_pattern.get(pytype)) + input_type = "text" if (pytype is None or pytype == "str") else "number" + input = dcc.Input( + id=id, + type=input_type, + debounce=True, + value=magnitude(attr_def.default), + pattern=number_pattern.get(pytype), + ) - unit = f"({attr_def.unit}) " if attr_def.unit else '' + unit = f"({attr_def.unit}) " if attr_def.unit else "" title = f"{attr_name} {unit}" div = html.Div( children=[ - html.Div(title, style={ - 'font-weight': 'bold', - 'width': '45%', - 'display': 'inline-block', - 'text-align': 'right', - 'margin-right': '4px', - }), - input + html.Div( + title, + style={ + "font-weight": "bold", + "width": "45%", + "display": "inline-block", + "text-align": "right", + "margin-right": "4px", + }, + ), + input, ], - style={'margin-left': '4px', 'padding': '2px'} + style={"margin-left": "4px", "padding": "2px"}, ) det_children.append(div) layout = html.Div( children=[details], - className='row', + className="row", style={ - 'text-align': 'left', - 'background-color': 'aliceblue', - 'border-radius': '4px', - 'border': '1px solid', - 'padding': '5px', - 'margin': '2px', - } + "text-align": "left", + "background-color": "aliceblue", + "border-radius": "4px", + "border": "1px solid", + "padding": "5px", + "margin": "2px", + }, ) return layout + def gui_switches(): - label_style = {'display': 'inline', 'margin-right': '2px'} - radio_options = [dict(label='Show', value=1), dict(label='Hide', value=0)] + label_style = {"display": "inline", "margin-right": "2px"} + radio_options = [dict(label="Show", value=1), dict(label="Hide", value=0)] - options = [ - ('Stream contents', 'show-stream-contents', 0), - ('Disabled procs', 'show-disabled-procs', 0) - ] + options = [("Stream contents", "show-stream-contents", 0), ("Disabled procs", "show-disabled-procs", 0)] outer_div = html.Div(children=[]) children = outer_div.children @@ -145,17 +150,23 @@ def gui_switches(): for title, id, value in options: span = html.Span( children=[ - html.Div(title + ':', style={ - 'font-weight': 'bold', - 'display': 'inline', - 'text-align': 'right', - 'margin-left': '15px', - }), - - dcc.RadioItems(id=id, options=radio_options, value=value, - labelStyle=label_style, - style={'display': 'inline'}, - persistence=True) + html.Div( + title + ":", + style={ + "font-weight": "bold", + "display": "inline", + "text-align": "right", + "margin-left": "15px", + }, + ), + dcc.RadioItems( + id=id, + options=radio_options, + value=value, + labelStyle=label_style, + style={"display": "inline"}, + persistence=True, + ), ], # style={'padding': '1px'} ) @@ -163,34 +174,37 @@ def gui_switches(): children.append(span) # dropdown of graph layout alternatives - default_layout = 'breadthfirst' - layout_names = (default_layout, 'circle', 'concentric', 'cose', 'grid', - # extra layouts don't seem to work as documented... - # 'cose-bilkent', 'cola', 'euler', 'spread', 'dagre', 'klay' - ) + default_layout = "breadthfirst" + layout_names = ( + default_layout, + "circle", + "concentric", + "cose", + "grid", + # extra layouts don't seem to work as documented... + # 'cose-bilkent', 'cola', 'euler', 'spread', 'dagre', 'klay' + ) span = html.Span( children=[ - html.Div("Layout: ", style={ - 'font-weight': 'bold', - 'display': 'inline', - 'text-align': 'right', - 'margin-left': '15px', - }), - + html.Div( + "Layout: ", + style={ + "font-weight": "bold", + "display": "inline", + "text-align": "right", + "margin-left": "15px", + }, + ), dcc.Dropdown( - id='graph-layout-selector', + id="graph-layout-selector", # placeholder='Select layout...', - options=[{'value': name, 'label': name} for name in layout_names], + options=[{"value": name, "label": name} for name in layout_names], value=default_layout, - style={ - 'width': '130px', - 'textAlign': 'center', - 'vertical-align': 'middle', - 'display': 'inline-block' - } - ) - ]) + style={"width": "130px", "textAlign": "center", "vertical-align": "middle", "display": "inline-block"}, + ), + ] + ) children.append(span) return outer_div diff --git a/opgee/import_export.py b/opgee/import_export.py index 75658036..8a986821 100644 --- a/opgee/import_export.py +++ b/opgee/import_export.py @@ -11,8 +11,16 @@ from .core import OpgeeObject from .error import OpgeeException -from .energy import (EN_CRUDE_OIL, EN_DIESEL, EN_ELECTRICITY, EN_NATURAL_GAS, - EN_NGL, EN_PETCOKE, EN_RESID, EN_UPG_PROC_GAS) +from .energy import ( + EN_CRUDE_OIL, + EN_DIESEL, + EN_ELECTRICITY, + EN_NATURAL_GAS, + EN_NGL, + EN_PETCOKE, + EN_RESID, + EN_UPG_PROC_GAS, +) from .log import getLogger _logger = getLogger(__name__) @@ -34,37 +42,38 @@ class ImportExport(OpgeeObject): - IMPORT = 'import' - EXPORT = 'export' - NET_IMPORTS = 'net imports' - - unit_dict = {NATURAL_GAS: "mmbtu/day", - UPG_PROC_GAS: "mmbtu/day", - NGL_LPG: "mmbtu/day", - DILUENT: "mmbtu/day", - CRUDE_OIL: "mmbtu/day", - DIESEL: "mmbtu/day", - RESID: "mmbtu/day", - PETCOKE: "mmbtu/day", - ELECTRICITY: "kWh/day", - WATER: "tonne/day", - N2: "tonne/day", - H2: "tonne/day", - CO2_Flooding: "tonne/day"} + IMPORT = "import" + EXPORT = "export" + NET_IMPORTS = "net imports" + + unit_dict = { + NATURAL_GAS: "mmbtu/day", + UPG_PROC_GAS: "mmbtu/day", + NGL_LPG: "mmbtu/day", + DILUENT: "mmbtu/day", + CRUDE_OIL: "mmbtu/day", + DIESEL: "mmbtu/day", + RESID: "mmbtu/day", + PETCOKE: "mmbtu/day", + ELECTRICITY: "kWh/day", + WATER: "tonne/day", + N2: "tonne/day", + H2: "tonne/day", + CO2_Flooding: "tonne/day", + } imports_set = set(unit_dict.keys()) @classmethod def _create_dataframe(cls): """ - Create a DataFrame to hold import or export rates. - Used only by the __init__ method. + Create a DataFrame to hold import or export rates. + Used only by the __init__ method. - :return: (pandas.DataFrame) An empty imports or exports DataFrame with - the columns and types set - """ - df = pd.DataFrame({name: pd.Series([], dtype=f"pint[{units}]") - for name, units in cls.unit_dict.items()}) + :return: (pandas.DataFrame) An empty imports or exports DataFrame with + the columns and types set + """ + df = pd.DataFrame({name: pd.Series([], dtype=f"pint[{units}]") for name, units in cls.unit_dict.items()}) return df @@ -160,6 +169,7 @@ def imports_exports(self): def _sum(series, name): from .units import ureg + # Sum of an empty series is returned as int(0); need to initialize units return series.sum() if len(series) > 0 else ureg.Quantity(0.0, self.unit_dict[name]) @@ -170,9 +180,7 @@ def _totals(df): imports = _totals(self.import_df) exports = _totals(self.export_df) - d = {self.IMPORT: imports, - self.EXPORT: exports, - self.NET_IMPORTS: imports - exports} + d = {self.IMPORT: imports, self.EXPORT: exports, self.NET_IMPORTS: imports - exports} return pd.DataFrame(d) diff --git a/opgee/log.py b/opgee/log.py index c84c3f0a..485bc85f 100644 --- a/opgee/log.py +++ b/opgee/log.py @@ -14,39 +14,43 @@ .. Copyright (c) 2016-2022 Richard Plevin See the https://opensource.org/licenses/MIT for license details. """ + import logging import os from .config import getParam, getParamAsBoolean, configLoaded -PKGNAME = __name__.split('.')[0] +PKGNAME = __name__.split(".")[0] + +_Loggers = {} # loggers created herein, keyed by module or package name +_LogLevels = None # log levels keyed by module or package name +_verbose = False # whether _debug() msgs should print -_Loggers = {} # loggers created herein, keyed by module or package name -_LogLevels = None # log levels keyed by module or package name -_verbose = False # whether _debug() msgs should print # Can't use this module to debug itself def _debug(msg): if _verbose: print(msg) + # Note: 'traitlets' library uses root logger, which we don't want to enable here def _createPkgLogger(dotspec): - pkgName = dotspec.split('.')[0] + pkgName = dotspec.split(".")[0] if pkgName and pkgName not in _Loggers: _debug('_createPkgLogger("%s") from %s' % (pkgName, dotspec)) logger = getLogger(pkgName) logger.propagate = False + def getLogger(name): - ''' + """ Register a logger, which will be set up after the configuration file is read. :param name: the name of the logger, conventionally passed as __name__. :return: a logging logger instance - ''' + """ _debug('getLogger("%s")' % name) try: @@ -54,7 +58,7 @@ def getLogger(name): except KeyError: logger = logging.getLogger(name) - logger.propagate = True # set to False for explicitly named modules + logger.propagate = True # set to False for explicitly named modules _Loggers[name] = logger _configureLogger(name) @@ -62,6 +66,7 @@ def getLogger(name): return logger + def parseLevels(levelStr=None): """ Get log levels for pygcam as a whole or for indicated modules individually. @@ -74,21 +79,22 @@ def parseLevels(levelStr=None): If levelStr is None, the value of the variable 'OPGEE.LogLevel' is used. :return: (dict) of log levels, keyed by module names """ + def splitAndStrip(s, delim): items = [item.strip() for item in s.split(delim)] return items result = {} - levelStr = levelStr or getParam('OPGEE.LogLevel') + levelStr = levelStr or getParam("OPGEE.LogLevel") - levels = splitAndStrip(levelStr, ',') + levels = splitAndStrip(levelStr, ",") for level in levels: - if ':' in level: - module, lvl = splitAndStrip(level, ':') + if ":" in level: + module, lvl = splitAndStrip(level, ":") # if '.' not in module or module[0] == '.': # module = PKGNAME + '.' + (module[1:] if module[0] == '.' else module) - if module[0] == '.': + if module[0] == ".": module = PKGNAME + module else: module = PKGNAME @@ -98,6 +104,7 @@ def splitAndStrip(s, delim): return result + # # Copied here from utils.py to avoid an import loop # @@ -113,14 +120,16 @@ def _mkdirs(newdir, mode=0o770): if e.errno != EEXIST: raise + def _addHandler(logger, formatStr, logFile=None): if logFile: _mkdirs(os.path.dirname(logFile)) - handler = logging.FileHandler(logFile, mode='a') if logFile else logging.StreamHandler() + handler = logging.FileHandler(logFile, mode="a") if logFile else logging.StreamHandler() handler.setFormatter(logging.Formatter(formatStr)) logger.addHandler(handler) - _debug("Added %s log handler for '%s'" % ('file' if logFile else 'console', logger.name)) + _debug("Added %s log handler for '%s'" % ("file" if logFile else "console", logger.name)) + def _configureLogger(name, force=False): try: @@ -136,7 +145,7 @@ def _configureLogger(name, force=False): global _LogLevels if not _LogLevels: - setLogLevels(getParam('OPGEE.LogLevel') or 'WARN') + setLogLevels(getParam("OPGEE.LogLevel") or "WARN") if name in _LogLevels: level = _LogLevels[name] @@ -155,44 +164,46 @@ def _configureLogger(name, force=False): # flush and remove all handlers _debug("Flushing and removing all handlers for %s" % logger) - handlers = logger.handlers.copy() # avoid iterating over the list we're removing items from + handlers = logger.handlers.copy() # avoid iterating over the list we're removing items from for handler in handlers: if not isinstance(handler, logging.NullHandler): handler.flush() logger.removeHandler(handler) - logConsole = getParamAsBoolean('OPGEE.LogConsole') + logConsole = getParamAsBoolean("OPGEE.LogConsole") if logConsole: - consoleFormat = getParam('OPGEE.LogConsoleFormat') + consoleFormat = getParam("OPGEE.LogConsoleFormat") _addHandler(logger, consoleFormat) - logFile = getParam('OPGEE.LogFile') + logFile = getParam("OPGEE.LogFile") if logFile: - fileFormat = getParam('OPGEE.LogFileFormat') + fileFormat = getParam("OPGEE.LogFileFormat") _addHandler(logger, fileFormat, logFile=logFile) if not logger.handlers: logger.addHandler(logging.NullHandler()) _debug("Added NullHandler to root logger") + def setLogFile(pathname, remove_old_file=False): from .config import setParam + if remove_old_file and os.path.isfile(pathname): os.remove(pathname) - setParam('OPGEE.LogFile', pathname) + setParam("OPGEE.LogFile", pathname) configureLogs(force=True) def configureLogs(force=False): - ''' + """ Do basicConfig setup and configure package loggers based on the information in the config instance given. Unless force == True, loggers with handlers will not be reconfigured. :param force: (bool) if True, reconfigure the logs even if already configured. :return: none - ''' + """ if not configLoaded() or not _Loggers: return @@ -207,8 +218,9 @@ def configureLogs(force=False): if name not in explicit: _configureLogger(name, force=force) + def setLogLevels(levelStr): - ''' + """ Set the logging level string, which can define levels for packages and/or modules. Must call configureLogs(force=True) afterwards. Level string can be a single level, which is used as the default for all modules, or module-specific settings, e.g., @@ -216,6 +228,6 @@ def setLogLevels(levelStr): :param levelStr: (str) comma-delimited module:LEVEL pairs, or just a single LEVEL :return: none - ''' + """ global _LogLevels _LogLevels = parseLevels(levelStr) diff --git a/opgee/main.py b/opgee/main.py index 751b39ba..cdb92728 100644 --- a/opgee/main.py +++ b/opgee/main.py @@ -7,6 +7,6 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from opgee.tool import main # pragma: no cover +from opgee.tool import main # pragma: no cover -main() # pragma: no cover +main() # pragma: no cover diff --git a/opgee/manager.py b/opgee/manager.py index 2a698793..c8b0bea1 100644 --- a/opgee/manager.py +++ b/opgee/manager.py @@ -36,6 +36,7 @@ _logger = getLogger(__name__) + def _walltime(minutes: int) -> str: """ Convert minutes to a walltime string suitable for SLURM @@ -43,7 +44,8 @@ def _walltime(minutes: int) -> str: :param minutes: (int) a number of minutes :return: (str) a string of the form "HH:MM:00" """ - return f"{minutes // 60 :02d}:{minutes % 60 :02d}:00" + return f"{minutes // 60:02d}:{minutes % 60:02d}:00" + # From recipes at https://docs.python.org/3/library/itertools.html def _batched(iterable, length): @@ -54,7 +56,7 @@ def _batched(iterable, length): from itertools import islice if length < 1: - raise ValueError('_batched: length must be > 0') + raise ValueError("_batched: length must be > 0") it = iter(iterable) while batch := tuple(islice(it, length)): @@ -65,6 +67,7 @@ class AbsPacket(OpgeeObject): """ Abstract superclass for FieldPacket and TrialPacket """ + _next_packet_num: int = 1 def __init__(self, items): @@ -92,18 +95,20 @@ def run(self, result_type): SIMPLE_RESULT or DETAILED_RESULT :return: (list of FieldResult) """ - raise AbstractMethodError(self.__class__, 'run') + raise AbstractMethodError(self.__class__, "run") def packetize(self, *args, **kwargs): "Must be implemented by subclass" - raise AbstractMethodError(self.__class__, 'packetize') + raise AbstractMethodError(self.__class__, "packetize") class FieldPacket(AbsPacket): - def __init__(self, - model_xml_file: str, - analysis_name: str, # TBD: might not be needed here - field_names: Sequence[str]): # by default this is a list of 10 field names + def __init__( + self, + model_xml_file: str, + analysis_name: str, # TBD: might not be needed here + field_names: Sequence[str], + ): # by default this is a list of 10 field names """ Create a ``FieldPacket`` of OPGEE runs to perform on a worker process. FieldPackets are defined by a list of field names. The worker process will @@ -117,24 +122,21 @@ def __init__(self, self.analysis_name = analysis_name @classmethod - def packetize(cls, - model_xml_file: str, - analysis_name: str, - field_names: Sequence[str], - packet_size: int): + def packetize(cls, model_xml_file: str, analysis_name: str, field_names: Sequence[str], packet_size: int): """ Packetizes over ``field_names``. Each packet contains a set of field names to iterate over. """ - packets = [FieldPacket(model_xml_file, analysis_name, field_names) - for field_names in _batched(field_names, packet_size)] + packets = [ + FieldPacket(model_xml_file, analysis_name, field_names) + for field_names in _batched(field_names, packet_size) + ] return packets def run(self, result_type): timer = Timer(f"FieldPacket.run({self})") field_names = self.items - results = run_serial(self.model_xml_file, self.analysis_name, field_names, - result_type=result_type) + results = run_serial(self.model_xml_file, self.analysis_name, field_names, result_type=result_type) timer.stop() _logger.debug(f"FieldPacket.run({self}) returning {len(results)} results") @@ -148,18 +150,16 @@ def __init__(self, sim_dir: str, field_name: str, trial_nums: Sequence[int]): self.field_name = field_name @classmethod - def packetize(cls, - sim_dir: str, - trial_nums: Sequence[int], - field_names: Sequence[str], - packet_size: int): + def packetize(cls, sim_dir: str, trial_nums: Sequence[int], field_names: Sequence[str], packet_size: int): """ Packetizes over ``trial_nums`` for each name in ``field_names``. Each resulting packet identifies a set of trials for one field. """ - packets = [TrialPacket(sim_dir, field_name, trial_batch) - for field_name in field_names - for trial_batch in _batched(trial_nums, packet_size)] + packets = [ + TrialPacket(sim_dir, field_name, trial_batch) + for field_name in field_names + for trial_batch in _batched(trial_nums, packet_size) + ] return packets def run(self, result_type): @@ -190,15 +190,14 @@ def run(self, result_type): return results - class Manager(OpgeeObject): def __init__(self, cluster_type=None): from .constants import CLUSTER_TYPES - cluster_type = (cluster_type or getParam('OPGEE.ClusterType')).lower() + + cluster_type = (cluster_type or getParam("OPGEE.ClusterType")).lower() if cluster_type not in CLUSTER_TYPES: - raise McsSystemError(f"Unknown cluster type '{cluster_type}'. " - f"Valid options are {CLUSTER_TYPES}.") + raise McsSystemError(f"Unknown cluster type '{cluster_type}'. Valid options are {CLUSTER_TYPES}.") self.cluster_type = cluster_type self.cluster = None @@ -213,38 +212,38 @@ def start_cluster(self, num_workers=None, minutes_per_task=None): _logger.info(f"Creating {cluster_type} cluster") - cores = getParamAsInt('SLURM.CoresPerNode') # "Total number of cores per job" + cores = getParamAsInt("SLURM.CoresPerNode") # "Total number of cores per job" - local_dir = getParam('SLURM.TempDir') + local_dir = getParam("SLURM.TempDir") mkdirs(local_dir) - if cluster_type == 'slurm': + if cluster_type == "slurm": # "Cut the job up into this many processes. Good for GIL workloads or for nodes with # many cores. By default, process ~= sqrt(cores) so that the number of processes and # the number of threads per process is roughly the same." - processes_per_core = getParamAsInt('SLURM.ProcessesPerCore') + processes_per_core = getParamAsInt("SLURM.ProcessesPerCore") processes = cores // processes_per_core - shell = getParam('SLURM.Shell') + shell = getParam("SLURM.Shell") # N.B. "Failed to launch worker. You cannot use the --no-nanny argument when n_workers > 1." - nanny = getParamAsBoolean('SLURM.UseNanny') # "Whether to start a nanny process" + nanny = getParamAsBoolean("SLURM.UseNanny") # "Whether to start a nanny process" - job_script_prologue = None # ['conda activate opgee'] failed + job_script_prologue = None # ['conda activate opgee'] failed minutes_per_task = minutes_per_task or getParamAsInt("SLURM.MinutesPerTask") arg_dict = dict( - account = getParam('SLURM.Account') or None, - job_name = getParam('SLURM.JobName'), - queue = getParam('SLURM.Partition'), - walltime = _walltime(minutes_per_task), + account=getParam("SLURM.Account") or None, + job_name=getParam("SLURM.JobName"), + queue=getParam("SLURM.Partition"), + walltime=_walltime(minutes_per_task), cores=cores, processes=processes, - memory = getParam('SLURM.MemPerJob'), - local_directory = local_dir, - interface = getParam('SLURM.Interface') or None, - shebang = '#!' + shell if shell else None, - nanny = nanny, # can't seem to get nanny = False to work... - job_script_prologue = job_script_prologue, + memory=getParam("SLURM.MemPerJob"), + local_directory=local_dir, + interface=getParam("SLURM.Interface") or None, + shebang="#!" + shell if shell else None, + nanny=nanny, # can't seem to get nanny = False to work... + job_script_prologue=job_script_prologue, ) _logger.debug(f"calling SLURMCluster({arg_dict})") @@ -256,29 +255,31 @@ def start_cluster(self, num_workers=None, minutes_per_task=None): _logger.debug(f"calling cluster.scale(cores={num_workers})") cluster.scale(cores=num_workers) # scale up to the desired total number of cores - elif cluster_type == 'local': + elif cluster_type == "local": # Set processes=False and swap n_workers and threads_per_worker to use threads in one # process, which is helpful for debugging. Note that some packages are not thread-safe. # Running with n_workers=1, threads_per_worker=2 resulted in weird runtime errors in Chemical. # self.cluster = cluster = SubprocessCluster(n_workers=1, threads_per_worker=num_engines, processes=False) - self.cluster = cluster = SubprocessCluster(n_workers=num_workers, threads_per_worker=1, worker_kwargs=dict(local_directory=local_dir)) + self.cluster = cluster = SubprocessCluster( + n_workers=num_workers, threads_per_worker=1, worker_kwargs=dict(local_directory=local_dir) + ) else: raise McsSystemError(f"Unknown cluster type '{cluster_type}'. Valid options are 'slurm' and 'local'.") - _logger.info(f"Starting {cluster_type } cluster") + _logger.info(f"Starting {cluster_type} cluster") self.client = client = Client(cluster) _logger.info("Waiting for workers") while True: try: # print('.', sep='', end='') - client.wait_for_workers(1, 15) # wait for 1 worker with 15 sec timeout + client.wait_for_workers(1, 15) # wait for 1 worker with 15 sec timeout break except (dask.distributed.TimeoutError, asyncio.exceptions.TimeoutError): pass - #print(e) # prints "Only 0/1 workers arrived after 15" + # print(e) # prints "Only 0/1 workers arrived after 15" _logger.info("Workers are running") return client @@ -292,17 +293,19 @@ def stop_cluster(self): self.client.shutdown() sleep(5) - #self.client.retire_workers() - #sleep(1) - #self.client.scheduler.shutdown() + # self.client.retire_workers() + # sleep(1) + # self.client.scheduler.shutdown() self.client = self.cluster = None - def run_packets(self, - packets: list[AbsPacket], - result_type: str = SIMPLE_RESULT, - num_engines: int = 0, - minutes_per_task: int = 10): + def run_packets( + self, + packets: list[AbsPacket], + result_type: str = SIMPLE_RESULT, + num_engines: int = 0, + minutes_per_task: int = 10, + ): """ Run a set of packets (i.e., FieldPackets or TrialPackets) on a dask cluster. Yields each packet's results as they are available. @@ -313,7 +316,7 @@ def run_packets(self, :param minutes_per_task: (int) how many minutes of walltime to allocate for each worker. :return: (list of FieldResult) results for individual runs. """ - timer = Timer('Manager.run_packets') + timer = Timer("Manager.run_packets") # This is useful mainly for testing. Any real MCS will use a proper cluster. if self.cluster_type == CLUSTER_NONE: @@ -328,7 +331,7 @@ def run_packets(self, client = self.start_cluster(num_workers=num_engines, minutes_per_task=minutes_per_task) # Start the worker processes on all available CPUs. - futures = client.map(lambda pkt: pkt.run(result_type),packets) + futures = client.map(lambda pkt: pkt.run(result_type), packets) for future, results in as_completed(futures, with_results=True): yield results @@ -339,8 +342,8 @@ def run_packets(self, _logger.info(timer.stop()) return None -def _run_field(analysis_name, field_name, xml_string, result_type, - use_default_model=True): + +def _run_field(analysis_name, field_name, xml_string, result_type, use_default_model=True): """ Run a single field, once, using the model in ``xml_string`` and return a ``FieldResult`` instance with results of ``result_type``. @@ -360,11 +363,14 @@ def _run_field(analysis_name, field_name, xml_string, result_type, mf = None field = None try: - mf = ModelFile.from_xml_string(xml_string, add_stream_components=False, - use_class_path=False, - use_default_model=use_default_model, - analysis_names=[analysis_name], - field_names=[field_name]) + mf = ModelFile.from_xml_string( + xml_string, + add_stream_components=False, + use_class_path=False, + use_default_model=use_default_model, + analysis_names=[analysis_name], + field_names=[field_name], + ) analysis = mf.model.get_analysis(analysis_name) field = analysis.get_field(field_name) @@ -381,15 +387,15 @@ def _run_field(analysis_name, field_name, xml_string, result_type, return result + # TODO: could be method of Manager def run_serial(model_xml_file, analysis_name, field_names, result_type=DETAILED_RESULT): - timer = Timer('run_serial') + timer = Timer("run_serial") results = [] # even though we pass 10 field names by default, each is passed singularly to `_run_field` - for field_name, xml_string in extract_model(model_xml_file, analysis_name, - field_names): + for field_name, xml_string in extract_model(model_xml_file, analysis_name, field_names): result = _run_field(analysis_name, field_name, xml_string, result_type) if result.error: _logger.error(f"Failed: {result}") @@ -399,6 +405,7 @@ def run_serial(model_xml_file, analysis_name, field_names, result_type=DETAILED_ _logger.info(timer.stop()) return results + def save_results(results, output_dir, batch_num=None): """ Save "detailed" results, comprising top-level carbon intensity (CI) from @@ -427,11 +434,9 @@ def save_results(results, output_dir, batch_num=None): stream_dfs = [] audit_dfs = [] - def create_dict(analysis, field, trial, - name=None, value=None, unit_col=True): + def create_dict(analysis, field, trial, name=None, value=None, unit_col=True): # create the common portion of result dicts - d = {"analysis": analysis, - "field": field} + d = {"analysis": analysis, "field": field} if trial is not None: d["trial"] = trial @@ -439,7 +444,7 @@ def create_dict(analysis, field, trial, if name: if isinstance(value, pint.Quantity): if unit_col: - d['units'] = value.u + d["units"] = value.u value = value.m d[name] = value return d @@ -452,17 +457,17 @@ def create_dict(analysis, field, trial, if result.result_type == ERROR_RESULT: d = create_dict(analysis_name, field_name, trial) - d['error'] = result.error + d["error"] = result.error error_rows.append(d) audit_dfs.append(result.audit_data) continue if result.result_type != SIMPLE_RESULT: if trial is not None: - result.streams['trial'] = trial - result.gases['trial'] = trial - result.emissions['trial'] = trial - result.energy['trial'] = trial # energy consumption + result.streams["trial"] = trial + result.gases["trial"] = trial + result.emissions["trial"] = trial + result.energy["trial"] = trial # energy consumption energy_cols.append(result.energy) emission_cols.append(result.emissions) @@ -470,15 +475,14 @@ def create_dict(analysis, field, trial, gas_dfs.append(result.gases) # Add a row for total energy output - d = create_dict(analysis_name, field_name, trial, - name='energy_output', value=result.energy_output) + d = create_dict(analysis_name, field_name, trial, name="energy_output", value=result.energy_output) energy_output_rows.append(d) for name, ci in result.ci_results: - d = create_dict(analysis_name, field_name, trial, name='CI', value=ci) - d['node'] = name + d = create_dict(analysis_name, field_name, trial, name="CI", value=ci) + d["node"] = name ci_rows.append(d) - + # always audit if configured if result.audit_data is not None: audit_dfs.append(result.audit_data["field"]) @@ -488,7 +492,7 @@ def create_dict(analysis, field, trial, proc_graph.write_png(pathjoin(procs_path, f"{field_name}_process_graph.png")) # Append batch number to filename if not None - batch = '' if batch_num is None else f"_{batch_num}" + batch = "" if batch_num is None else f"_{batch_num}" def _to_csv(df, file_prefix): pathname = pathjoin(output_dir, f"{file_prefix}{batch}.csv") @@ -496,11 +500,11 @@ def _to_csv(df, file_prefix): df.to_csv(pathname, index=False) df = pd.DataFrame(data=ci_rows) - _to_csv(df, 'carbon_intensity') + _to_csv(df, "carbon_intensity") if error_rows: df = pd.DataFrame(data=error_rows) - _to_csv(df, 'errors') + _to_csv(df, "errors") audit_dfs = [pd.DataFrame(df) for df in audit_dfs if df is not None] if len(audit_dfs) > 0: @@ -514,11 +518,11 @@ def _save_dfs(dfs, file_prefix): def _reformat(df): df = df.sort_index(axis="rows").reset_index() - id_vars = ['process', 'unit'] - if 'trial' in df.columns: - id_vars.append('trial') + id_vars = ["process", "unit"] + if "trial" in df.columns: + id_vars.append("trial") - df = df.melt(value_name='value', var_name='field', id_vars=id_vars) + df = df.melt(value_name="value", var_name="field", id_vars=id_vars) return df dfs = [_reformat(df) for df in dfs] @@ -527,9 +531,9 @@ def _reformat(df): df = pd.concat(dfs, axis="rows") # reordering the columns - col_order = ['field', 'process', 'value', 'unit'] - if 'trial' in df.columns: - col_order.insert(0, 'trial') + col_order = ["field", "process", "value", "unit"] + if "trial" in df.columns: + col_order.insert(0, "trial") df = df[col_order] _to_csv(df, file_prefix) @@ -556,12 +560,13 @@ def _reformat(df): # Save any results captured by optional post-processor plugins PostProcessor.save_post_processor_results(output_dir) + def _combine_results(filenames, output_name, sort_by=None): if not filenames: return dfs = [pd.read_csv(name, index_col=False) for name in filenames] - combined = pd.concat(dfs, axis='rows') + combined = pd.concat(dfs, axis="rows") if sort_by: combined.sort_values(sort_by, inplace=True) @@ -570,8 +575,9 @@ def _combine_results(filenames, output_name, sort_by=None): combined.to_csv(output_name, index=False) -results_pat = re.compile(r'results-\d+\.csv$') -failures_pat = re.compile(r'failures-\d+\.csv$') +results_pat = re.compile(r"results-\d+\.csv$") +failures_pat = re.compile(r"failures-\d+\.csv$") + def combine_mcs_results(sim_dir, field_names, delete=False): """ @@ -588,16 +594,17 @@ def combine_mcs_results(sim_dir, field_names, delete=False): # TBD: handle case that field directory isn't present with pushd(field_name): # use glob with its limited wildcard capability, then filter for the real pattern - result_files = [name for name in glob(r'results-*.csv') if re.match(results_pat, name)] - _combine_results(result_files, RESULTS_CSV, sort_by='trial_num') + result_files = [name for name in glob(r"results-*.csv") if re.match(results_pat, name)] + _combine_results(result_files, RESULTS_CSV, sort_by="trial_num") - failure_files = [name for name in glob(r'failures-*.csv') if re.match(failures_pat, name)] - _combine_results(failure_files, FAILURES_CSV, sort_by='trial_num') + failure_files = [name for name in glob(r"failures-*.csv") if re.match(failures_pat, name)] + _combine_results(failure_files, FAILURES_CSV, sort_by="trial_num") if delete: for name in result_files + failure_files: os.remove(name) + def combine_field_results(output_dir, field_names, delete=False): """ Combine CSV files containing partial results/failures from an MCS into two files, @@ -612,10 +619,10 @@ def combine_field_results(output_dir, field_names, delete=False): for field_name in field_names: with pushd(field_name): # use glob with its limited wildcard capability, then filter for the real pattern - result_files = [name for name in glob(r'results-*.csv') if re.match(results_pat, name)] + result_files = [name for name in glob(r"results-*.csv") if re.match(results_pat, name)] _combine_results(result_files, RESULTS_CSV) - failure_files = [name for name in glob(r'failures-*.csv') if re.match(failures_pat, name)] + failure_files = [name for name in glob(r"failures-*.csv") if re.match(failures_pat, name)] _combine_results(failure_files, FAILURES_CSV) if delete: diff --git a/opgee/mcs/LHS.py b/opgee/mcs/LHS.py index e6b824e7..11038ab7 100644 --- a/opgee/mcs/LHS.py +++ b/opgee/mcs/LHS.py @@ -22,16 +22,17 @@ _logger = getLogger(__name__) + def rankCorrCoef(m): - ''' + """ Take a 2-D array of values and produce a array of rank correlation coefficients representing the rank correlation among the columns. - ''' + """ dummy, cols = m.shape corrCoef = np.zeros((cols, cols)) # @UndefinedVariable for i in range(cols): - corrCoef[i, i] = 1. # All columns are perfectly correlated with themselves + corrCoef[i, i] = 1.0 # All columns are perfectly correlated with themselves for j in range(i + 1, cols): corr = stats.spearmanr(m[:, i], m[:, j])[0] corrCoef[i, j] = corrCoef[j, i] = corr @@ -40,7 +41,7 @@ def rankCorrCoef(m): def genRankValues(params, trials, corrMat): - ''' + """ Generate a data set of 'trials' ranks for 'params' parameters that obey the given correlation matrix. @@ -63,7 +64,7 @@ def genRankValues(params, trials, corrMat): [6,5,2], [5,2,1], [3,6,4]] - ''' + """ # Create van der Waarden scores strata = np.arange(1.0, trials + 1) / (trials + 1) vdwScores = stats.norm().ppf(strata) @@ -79,7 +80,7 @@ def genRankValues(params, trials, corrMat): Q = np.array(np.linalg.cholesky(E)) final = np.dot(np.dot(S, np.linalg.inv(Q).T), P.T) - ranks = np.zeros((trials, params), dtype='i') + ranks = np.zeros((trials, params), dtype="i") for i in range(params): ranks[:, i] = stats.rankdata(final[:, i]) @@ -87,13 +88,13 @@ def genRankValues(params, trials, corrMat): def getPercentiles(trials=100): - ''' + """ Generate a list of 'trials' values, one from each of 'trials' equal-size segments from a uniform distribution. These are used with an RV's ppf (percent point function = inverse cumulative function) to retrieve the values for that RV at the corresponding percentiles. - ''' - segmentSize = float(1. / trials) + """ + segmentSize = float(1.0 / trials) points = stats.uniform.rvs(size=trials) * segmentSize + np.arange(trials) * segmentSize # @UndefinedVariable return points @@ -127,7 +128,7 @@ def lhs(paramList, trials, corrMat=None, columns=None, skip=None): for i, param in enumerate(paramList): if param in skip: - continue # process later + continue # process later values = param.ppf(getPercentiles(trials)) # extract values from the RV for these percentiles @@ -140,12 +141,13 @@ def lhs(paramList, trials, corrMat=None, columns=None, skip=None): np.random.shuffle(values) # randomize the stratified samples else: indices = ranks[:, i] - 1 # make them 0-relative - values = values[indices] # reorder to respect correlations + values = values[indices] # reorder to respect correlations samples[:, i] = values return DataFrame(samples, columns=columns) if columns else samples + def lhsAmend(df, rvList, trials, shuffle=True): """ Amend the DataFrame with LHS data by adding columns for the given parameters. @@ -160,10 +162,10 @@ def lhsAmend(df, rvList, trials, shuffle=True): for rv in rvList: values = rv.ppf(getPercentiles(trials)) # extract values from the RV for these percentiles if not isinstance(values, np.ndarray): - values = values.values # convert pandas Series if needed + values = values.values # convert pandas Series if needed if shuffle: - np.random.shuffle(values) # randomize the stratified samples + np.random.shuffle(values) # randomize the stratified samples param = rv.getParameter() paramName = param.getName() diff --git a/opgee/mcs/distributed_mcs_dask.py b/opgee/mcs/distributed_mcs_dask.py index fbb207b6..25ee80f6 100644 --- a/opgee/mcs/distributed_mcs_dask.py +++ b/opgee/mcs/distributed_mcs_dask.py @@ -8,6 +8,7 @@ # import asyncio import dask + # from dask_jobqueue import SLURMCluster from dask.distributed import Client, LocalCluster, as_completed from itertools import islice, product @@ -19,17 +20,18 @@ from ..core import OpgeeObject, Timer from ..config import getParam, getParamAsInt, getParamAsBoolean from ..error import RemoteError, McsSystemError, TrialErrorWrapper -from ..log import getLogger, setLogFile +from ..log import getLogger, setLogFile from .simulation import Simulation, combine_results _logger = getLogger(__name__) + # From recipes at https://docs.python.org/3/library/itertools.html def batched(iterable, n): "Batch data into tuples of length n. The last batch may be shorter." # batched('ABCDEFG', 3) --> ABC DEF G if n < 1: - raise ValueError('batched: n must be at least one') + raise ValueError("batched: n must be at least one") it = iter(iterable) while batch := tuple(islice(it, n)): @@ -43,13 +45,15 @@ def _walltime(minutes: int) -> str: :param minutes: (int) a number of minutes :return: (str) a string of the form "HH:MM:00" """ - return f"{minutes // 60 :02d}:{minutes % 60 :02d}:00" + return f"{minutes // 60:02d}:{minutes % 60:02d}:00" + # Global to track how many tasks each worker is running _task_count = 0 + class FieldResult(OpgeeObject): - __slots__ = ['ok', 'field_name', 'packet_num', 'duration', 'completed', 'task_count', 'error'] + __slots__ = ["ok", "field_name", "packet_num", "duration", "completed", "task_count", "error"] def __init__(self, field_name, duration, completed, packet_num=None, error=None): self.ok = error is None @@ -64,6 +68,7 @@ def __str__(self): packet_info = "" if self.packet_num is None else f"[{self.packet_num}]" return f"" + def run_field(sim_dir, field_name, trial_nums=None, packet_num=None): """ Run the trials ``trial_nums`` for ``field``, serially. In distributed mode, @@ -76,9 +81,9 @@ def run_field(sim_dir, field_name, trial_nums=None, packet_num=None): in ``field``. This is used to name files holding results for this packet. :return: (FieldResult) """ - timer = Timer('run_field').start() + timer = Timer("run_field").start() - sim = Simulation(sim_dir, field_names=[field_name], save_to_path='') + sim = Simulation(sim_dir, field_names=[field_name], save_to_path="") field = sim.analysis.get_field(field_name) if field.is_enabled(): @@ -125,9 +130,9 @@ def run_field(sim_dir, field_name, trial_nums=None, packet_num=None): class Manager(OpgeeObject): def __init__(self, cluster_type=None): - cluster_type = (cluster_type or getParam('OPGEE.ClusterType')).lower() + cluster_type = (cluster_type or getParam("OPGEE.ClusterType")).lower() - valid = ('local', 'slurm') + valid = ("local", "slurm") if cluster_type not in valid: raise McsSystemError(f"Unknown cluster type '{cluster_type}'. Valid options are {valid}.") @@ -140,35 +145,35 @@ def start_cluster(self, num_engines=None, minutes_per_task=None): _logger.info(f"Creating {cluster_type} cluster") - cores = getParamAsInt('SLURM.CoresPerNode') # "Total number of cores per job" + cores = getParamAsInt("SLURM.CoresPerNode") # "Total number of cores per job" - if cluster_type == 'slurm': + if cluster_type == "slurm": # "Cut the job up into this many processes. Good for GIL workloads or for nodes with # many cores. By default, process ~= sqrt(cores) so that the number of processes and # the number of threads per process is roughly the same." - processes_per_core = getParamAsInt('SLURM.ProcessesPerCore') + processes_per_core = getParamAsInt("SLURM.ProcessesPerCore") processes = cores // processes_per_core - shell = getParam('SLURM.Shell') + shell = getParam("SLURM.Shell") # N.B. "Failed to launch worker. You cannot use the --no-nanny argument when n_workers > 1." - nanny = getParamAsBoolean('SLURM.UseNanny') # "Whether to start a nanny process" + nanny = getParamAsBoolean("SLURM.UseNanny") # "Whether to start a nanny process" - job_script_prologue = None # ['conda activate opgee'] failed + job_script_prologue = None # ['conda activate opgee'] failed minutes_per_task = minutes_per_task or getParamAsInt("SLURM.MinutesPerTask") arg_dict = dict( - account = getParam('SLURM.Account') or None, - job_name = getParam('SLURM.JobName'), - queue = getParam('SLURM.Partition'), - walltime = _walltime(minutes_per_task), + account=getParam("SLURM.Account") or None, + job_name=getParam("SLURM.JobName"), + queue=getParam("SLURM.Partition"), + walltime=_walltime(minutes_per_task), cores=cores, processes=processes, - memory = getParam('SLURM.MemPerJob'), - local_directory = getParam('SLURM.TempDir'), - interface = getParam('SLURM.Interface') or None, - shebang = '#!' + shell if shell else None, - nanny = nanny, # can't seem to get nanny = False to work... - job_script_prologue = job_script_prologue, + memory=getParam("SLURM.MemPerJob"), + local_directory=getParam("SLURM.TempDir"), + interface=getParam("SLURM.Interface") or None, + shebang="#!" + shell if shell else None, + nanny=nanny, # can't seem to get nanny = False to work... + job_script_prologue=job_script_prologue, ) _logger.debug(f"calling SLURMCluster({arg_dict})") @@ -180,7 +185,7 @@ def start_cluster(self, num_engines=None, minutes_per_task=None): _logger.debug(f"calling cluster.scale(cores={num_engines})") cluster.scale(cores=num_engines) # scale up to the desired total number of cores - elif cluster_type == 'local': + elif cluster_type == "local": # Set processes=False and swap n_workers and threads_per_worker to use threads in one # process, which is helpful for debugging. Note that some packages are not thread-safe. # Running with n_workers=1, threads_per_worker=2 resulted in weird runtime errors in Chemical. @@ -191,18 +196,18 @@ def start_cluster(self, num_engines=None, minutes_per_task=None): else: raise McsSystemError(f"Unknown cluster type '{cluster_type}'. Valid options are 'slurm' and 'local'.") - _logger.info(f"Starting {cluster_type } cluster") + _logger.info(f"Starting {cluster_type} cluster") self.client = client = Client(cluster) _logger.info("Waiting for workers") while True: try: # print('.', sep='', end='') - client.wait_for_workers(1, 15) # wait for 1 worker with 15 sec timeout + client.wait_for_workers(1, 15) # wait for 1 worker with 15 sec timeout break except (dask.distributed.TimeoutError, asyncio.exceptions.TimeoutError): pass - #print(e) # prints "Only 0/1 workers arrived after 15" + # print(e) # prints "Only 0/1 workers arrived after 15" _logger.info("Workers are running") return client @@ -216,15 +221,23 @@ def stop_cluster(self): self.client.shutdown() sleep(5) - #self.client.retire_workers() - #sleep(1) - #self.client.scheduler.shutdown() + # self.client.retire_workers() + # sleep(1) + # self.client.scheduler.shutdown() self.client = self.cluster = None - def run_mcs(self, sim_dir, packet_size : int, field_names=None, num_engines=0, - trial_nums=None, minutes_per_task=None, collect=False, - delete_partials=False): + def run_mcs( + self, + sim_dir, + packet_size: int, + field_names=None, + num_engines=0, + trial_nums=None, + minutes_per_task=None, + collect=False, + delete_partials=False, + ): """ Run a Monte Carlo simulation on a dask cluster. @@ -242,9 +255,9 @@ def run_mcs(self, sim_dir, packet_size : int, field_names=None, num_engines=0, """ from ..utils import parseTrialString - timer = Timer('Manager.run_mcs').start() + timer = Timer("Manager.run_mcs").start() - sim = Simulation(sim_dir, field_names=field_names, save_to_path='') + sim = Simulation(sim_dir, field_names=field_names, save_to_path="") # Put the log for the monitor process in the simulation directory. # Workers will set the log file to within the directory for the @@ -252,7 +265,7 @@ def run_mcs(self, sim_dir, packet_size : int, field_names=None, num_engines=0, log_file = f"{sim_dir}/opgee-mcs.log" setLogFile(log_file, remove_old_file=True) - trial_nums = range(sim.trials) if trial_nums == 'all' else parseTrialString(trial_nums) + trial_nums = range(sim.trials) if trial_nums == "all" else parseTrialString(trial_nums) # TBD: check for unknown field names # Caller can specify a subset of possible fields to run. Default is to run all. @@ -267,9 +280,7 @@ def run_mcs(self, sim_dir, packet_size : int, field_names=None, num_engines=0, def _run_field(tup): field_name, (packet_num, trial_nums) = tup - return run_field(sim_dir, field_name, - trial_nums=trial_nums, - packet_num=packet_num) + return run_field(sim_dir, field_name, trial_nums=trial_nums, packet_num=packet_num) # Start the worker processes on all available CPUs. # Note that "list(product())" is required since map doesn't support iterators. @@ -278,7 +289,7 @@ def _run_field(tup): for future, result in as_completed(futures, with_results=True): if result.error: _logger.error(f"Failed: {result}") - #traceback.print_exc() + # traceback.print_exc() else: _logger.debug(f"Succeeded: {result}") @@ -289,4 +300,3 @@ def _run_field(tup): self.stop_cluster() _logger.info(timer.stop()) - diff --git a/opgee/mcs/distro.py b/opgee/mcs/distro.py index b5dc883d..46a80dac 100644 --- a/opgee/mcs/distro.py +++ b/opgee/mcs/distro.py @@ -24,27 +24,29 @@ def parseDistroKey(key): - ''' + """ Gets the name and list of dimensions from a distro key. Inverse of makeDistroKey - ''' - s = re.split(r'\[', key) - return s[0], s[1][:-1].split(',') + """ + s = re.split(r"\[", key) + return s[0], s[1][:-1].split(",") + def makeDistroKey(name, dimensions, dropZeros=False): - ''' + """ Generate a dictionary key for the variable and a list of dimension indices. This is a normal function because it is used by both the MatrixRV and ParameterSet classes. Inverse of parseDistroKey. - ''' + """ if not dimensions: return name if dropZeros and all(dim == 0 for dim in dimensions): return name - distroKey = name + re.sub(r'[\s\']', r'', str(dimensions)) + distroKey = name + re.sub(r"[\s\']", r"", str(dimensions)) return distroKey + # For debugging only def dumpDistros(distroDict): for key in sorted(distroDict.iterkeys()): @@ -52,62 +54,70 @@ def dumpDistros(distroDict): for d in subDistroDict.values(): _logger.info(d) + def uniformMinMax(min, max): return uniform(loc=min, scale=(max - min)) + def uniformRange(range): if range <= 0.0: raise OpgeeException("Uniform range must be > 0.0; %f was given" % range) return uniformMinMax(-range, range) + def uniformFactor(factor): if factor < 0.0 or factor > 1.0: raise OpgeeException("Uniform factor must be between 0.0 and 1.0; %f was given" % factor) return uniformMinMax(1 - factor, 1 + factor) + def uniformLogfactor(logfactor): if logfactor < 1.0: raise OpgeeException("Uniform logfactor must be > 1.0; %f was given" % logfactor) - return uniformMinMax(1.0/logfactor, logfactor) + return uniformMinMax(1.0 / logfactor, logfactor) + # # Various ways to specify a lognormal random variable: # def lognormalRvForNormal(mu, sigma): - ''' + """ Define a lognormal RV by the mean and stdev of the underlying Normal distribution - ''' + """ return lognorm(sigma, scale=math.exp(mu)) + def lognormalRv(logMean, logStd): - ''' + """ Define a lognormal RV by its own mean and stdev - ''' + """ logVar = float(logStd) ** 2 mSqrd = float(logMean) ** 2 mu = math.log(mSqrd / math.sqrt(logVar + mSqrd)) sigma = math.sqrt(math.log(logVar / mSqrd + 1)) return lognormalRvForNormal(mu, sigma) + # TBD: poorly documented... is this the 95% CI (2.5% to 97.5%?) or the 90% CI (5% to 95%)? def lognormalRvFor95th(lo, hi): - ''' + """ Define a lognormal RV by its 95% CI. - ''' + """ lo = math.log(float(lo)) hi = math.log(float(hi)) mu = (lo + hi) / 2.0 sigma = (hi - mu) / 1.96 # 95th percentile of normal is (+/- 1.96) * sigma return lognormalRvForNormal(mu, sigma) + # TBD: UNTESTED! def lognormalRvForIQR(q1, q3): - ''' + """ Define a lognormal RV by its Q1 and Q3 values - ''' + """ q1 = math.log(float(q1)) q3 = math.log(float(q3)) mu = (q1 + q3) / 2.0 @@ -115,6 +125,7 @@ def lognormalRvForIQR(q1, q3): sigma = iqr / 1.34896 return lognormalRvForNormal(mu, sigma) + def logfactor(factor): """ Define a lognormal distribution assuming the 2.5% and 97.5% values @@ -125,7 +136,8 @@ def logfactor(factor): return lognormalRvFor95th(1 / factor, factor) -class truncated_lognormal(): + +class truncated_lognormal: def __init__(self, logmean, logstdev, low, high): self.logmean = logmean self.logstdev = logstdev @@ -138,11 +150,12 @@ def ppf(self, q): y = self.rv.ppf(q) # simple truncation of values below low to low, above high to high - y[ y < self.low ] = self.low - y[ y > self.high] = self.high + y[y < self.low] = self.low + y[y > self.high] = self.high return y + def triangle(min, mode, max): # @ReservedAssignment # correct ordering if necessary if min > max: @@ -157,40 +170,46 @@ def triangle(min, mode, max): # @ReservedAssignment c = (mode - min) / scale # central value (mode) of the triangle return triang(c, loc=min, scale=scale) + def triangleRange(range): if range <= 0.0: raise OpgeeException("Triangle range must be between > 0.0; %f was given" % range) return triangle(-range, 0, range) + def triangleFactor(factor): if factor < 0.0 or factor > 1.0: raise OpgeeException("Triangle factor must be between 0.0 and 1.0; %f was given" % factor) return triangle(1 - factor, 1, 1 + factor) + def triangleLogfactor(logfactor): if logfactor < 1.0: raise OpgeeException("Triangle logfactor must be > 1.0; %f was given" % logfactor) - return triangle(1.0/logfactor, 1, logfactor) + return triangle(1.0 / logfactor, 1, logfactor) + def binary(): return rv_discrete(name="binary", values=[(0, 1), (0.5, 0.5)]) + def weighted_binary(prob_of_one): - return rv_discrete(name="weighted_binary", values=[(0, 1), (1-prob_of_one, prob_of_one)]) + return rv_discrete(name="weighted_binary", values=[(0, 1), (1 - prob_of_one, prob_of_one)]) + def integers(min, max): min = int(min) max = int(max) count = max - min + 1 - nums = list(range(min, max + 1)) - probs = [1.0/count] * count - return rv_discrete(name='integers', values=[nums, probs]) + nums = list(range(min, max + 1)) + probs = [1.0 / count] * count + return rv_discrete(name="integers", values=[nums, probs]) -class truncated_normal(): +class truncated_normal: def __init__(self, mean, stdev, low, high): self.mean = mean self.stdev = stdev @@ -208,11 +227,13 @@ def ppf(self, q): shifted = y * self.stdev + self.mean return shifted -class constant(): + +class constant: """ Return an object that produces an array holding the given constant value. Useful for forcing a parameter to a given value. """ + def __init__(self, value): self.value = value @@ -221,29 +242,33 @@ def ppf(self, q): n[:] = self.value return n -class sequence(): + +class sequence: """ Return an object that produces an array holding the given sequence of constant values. Useful for forcing parameters to given values. """ + def __init__(self, values): - self.values = [float(item) for item in values.split(',')] + self.values = [float(item) for item in values.split(",")] def ppf(self, q): n = len(q) # length of array to return - count = len(self.values) # items in the sequence + count = len(self.values) # items in the sequence - seq = ((int(n / count) + (1 if n % count else 0)) * self.values) + seq = (int(n / count) + (1 if n % count else 0)) * self.values # truncate in case n wasn't a multiple of count arr = np.array(seq[:n]) return arr -class Empirical(): + +class Empirical: """ Create an empirical distribution and ppf from an array of observations. """ - file_cache = {} # maps pathname of CSV file to dataframe + + file_cache = {} # maps pathname of CSV file to dataframe def __init__(self, values): self.values = sorted(values) @@ -256,14 +281,18 @@ def ppf(self, q): return result @classmethod - def from_csv(cls, pathname=None, colname=None): # both are required args, but must be keywords to use with DistroGen.makeRV() + def from_csv( + cls, pathname=None, colname=None + ): # both are required args, but must be keywords to use with DistroGen.makeRV() import pandas as pd df = cls.file_cache.get(pathname) if df is None: try: - csvdata = pathname if os.path.isabs(pathname) else resourceStream(pathname, stream_type='bytes', decode=None) + csvdata = ( + pathname if os.path.isabs(pathname) else resourceStream(pathname, stream_type="bytes", decode=None) + ) df = pd.read_csv(csvdata, index_col=False) except Exception as e: raise McsUserError(f"from_csv: Unable to read empirical data file '{pathname}': {e}") @@ -280,38 +309,40 @@ def from_csv(cls, pathname=None, colname=None): # both are required args, but mu def clear_file_cache(cls): cls.file_cache.clear() + class GridRV(object): - ''' + """ Return an object that behaves like an RV in that it returns N values when when requested via the ppf (percent point function), though the N values are merely a shuffled sequence of a "gridded" range repeated to produce N values. No other methods of the standard RV class are implemented. This is intended for use in CoreMCS and derivatives only. - ''' + """ + def __init__(self, min, max, count): self.values = np.linspace(min, max, count) _logger.debug("Generated values: %s", self.values) - def ppf(self, q): - ''' + """ Return 'n' values from this object's list of values, repeating those values as many times as necessary to produce 'n' values, where 'n' is the length of the percentile list given by 'q'. (We ignore the values, though.) - ''' + """ n = len(q) values = self.values assert len(values.shape) == 1, "Grid values were converted to ndarray of > 1 dimension" - count = values.shape[0] - reps = 1 if n <= count else np.ceil(float(n) / count) - tiled = np.tile(values, reps)[:n] - np.random.shuffle(tiled) # TBD: might be redundant as shuffle is called from LHS + count = values.shape[0] + reps = 1 if n <= count else np.ceil(float(n) / count) + tiled = np.tile(values, reps)[:n] + np.random.shuffle(tiled) # TBD: might be redundant as shuffle is called from LHS # _logger.debug("tiled=%s", tiled) return tiled + class linkedDistro(object): def __init__(self, parameter): - '''Linked to (i.e., shares RV data with) `withParameter`''' + """Linked to (i.e., shares RV data with) `withParameter`""" self.parameter = parameter # this is needed to handle linked parameters @@ -326,18 +357,20 @@ def getTrialData(cls): return cls.trialData def ppf(self, q): - return self.trialData[self.parameter] # TBD: return as an ndarray + return self.trialData[self.parameter] # TBD: return as an ndarray + class DistroGen(object): - ''' + """ Stores information required to generate a Distro instance from an argDict - ''' - instances = {} # Store a dict of our instances internally + """ + + instances = {} # Store a dict of our instances internally def __init__(self, distName, func): self.name = distName self.func = func - self.sig = DistroGen.signature(distName, inspect.signature(func).parameters) + self.sig = DistroGen.signature(distName, inspect.signature(func).parameters) DistroGen.instances[self.sig] = self def __str__(self): @@ -346,12 +379,12 @@ def __str__(self): @classmethod def signature(cls, distName, keywords): - ''' + """ Makes a unique signature for a distribution type out of its name and a collection of argument names. - ''' + """ lst = list(keywords) - lst.append('#' + distName.lower()) # assures that distname doesn't overlap with any of the argument names + lst.append("#" + distName.lower()) # assures that distname doesn't overlap with any of the argument names return frozenset(lst) @classmethod @@ -360,74 +393,72 @@ def generator(cls, sig): return cls.instances.get(sig, None) def makeRV(self, argDict): - 'Call the generator function with an argDict to create a frozen RV' + "Call the generator function with an argDict to create a frozen RV" return self.func(**argDict) @classmethod def genDistros(cls): - ''' + """ Generate a basic set of distributions - ''' + """ if cls.instances: return - cls('uniform', uniformMinMax) + cls("uniform", uniformMinMax) # range=0.2 means a Uniform(min=-0.2, max=0.2); used with apply="add" - cls('uniform', uniformRange) + cls("uniform", uniformRange) # factor=0.2 means Uniform(min=0.8, max=1.2); used with apply="multiply" - cls('uniform', uniformFactor) + cls("uniform", uniformFactor) # logfactor=3 means Uniform(1/3, 3); used with apply="multiply" - cls('uniform', uniformLogfactor) + cls("uniform", uniformLogfactor) - cls('weighted_binary', weighted_binary) + cls("weighted_binary", weighted_binary) # LogUniform distribution from 1/n to n, e.g., factor=3 => uniform(1/3, 3) - cls('loguniform', lambda factor: uniformMinMax(min=1 / factor, max=factor)) + cls("loguniform", lambda factor: uniformMinMax(min=1 / factor, max=factor)) - cls('normal', lambda mean, std: norm(loc=mean, scale=std)) - cls('normal', lambda mean, stdev: norm(loc=mean, scale=stdev)) # alternate spelling + cls("normal", lambda mean, std: norm(loc=mean, scale=std)) + cls("normal", lambda mean, stdev: norm(loc=mean, scale=stdev)) # alternate spelling - cls('lognormal', lambda logmean, logstdev: lognormalRv(logmean, logstdev)) - cls('lognormal', lambda mean, stdev: lognormalRvForNormal(mean, stdev)) - cls('lognormal', lambda low95, high95: lognormalRvFor95th(low95, high95)) - cls('lognormal', logfactor) + cls("lognormal", lambda logmean, logstdev: lognormalRv(logmean, logstdev)) + cls("lognormal", lambda mean, stdev: lognormalRvForNormal(mean, stdev)) + cls("lognormal", lambda low95, high95: lognormalRvFor95th(low95, high95)) + cls("lognormal", logfactor) - cls('truncated_lognormal', truncated_lognormal) + cls("truncated_lognormal", truncated_lognormal) # range=0.2 means a triangle with min, mode, max = (-0.2, 0, +0.2); for apply="add" - cls('triangle', triangleRange) # args: range (must be > 0) + cls("triangle", triangleRange) # args: range (must be > 0) # factor=0.2 means triangle with min, mode, max = (0.8, 1, 1.2); for apply="multiply" - cls('triangle', triangleFactor) # args: factor: must be > 0 and < 1 + cls("triangle", triangleFactor) # args: factor: must be > 0 and < 1 # logfactor=3 means triangle with min, mode, max = (1/3, 1, 3); for apply="multiply" - cls('triangle', triangleLogfactor) # args: logfactor: must be > 1 + cls("triangle", triangleLogfactor) # args: logfactor: must be > 1 - cls('triangle', triangle) # args: min, mode, max + cls("triangle", triangle) # args: min, mode, max - cls('truncated_normal', truncated_normal) + cls("truncated_normal", truncated_normal) - cls('binary', binary) - cls('integers', integers) # args: min, max (inclusive) + cls("binary", binary) + cls("integers", integers) # args: min, max (inclusive) # Gridded (non-random) sequence. # Returns a frozen RV-like object with a "ppf" method that returns a sequence of values # produced by cycling through 'count' values evenly spaced starting at 'min' and ending # at 'max'. - cls('grid', lambda min, max, count: GridRV(min, max, count)) - - cls('constant', lambda value: constant(value)) - - cls('sequence', lambda values: sequence(values)) + cls("grid", lambda min, max, count: GridRV(min, max, count)) - cls('linked', lambda parameter: linkedDistro(parameter)), + cls("constant", lambda value: constant(value)) - cls('empirical', Empirical.from_csv) + cls("sequence", lambda values: sequence(values)) + (cls("linked", lambda parameter: linkedDistro(parameter)),) + cls("empirical", Empirical.from_csv) def get_frozen_rv(distro_name, **kwargs): diff --git a/opgee/mcs/parameter_list.py b/opgee/mcs/parameter_list.py index 200cd481..79856a39 100644 --- a/opgee/mcs/parameter_list.py +++ b/opgee/mcs/parameter_list.py @@ -10,19 +10,22 @@ _logger = getLogger(__name__) -DISTROS_XML = 'mcs/etc/parameter-distributions.xml' +DISTROS_XML = "mcs/etc/parameter-distributions.xml" + class ParameterList(XMLFile): def __init__(self, filename, xml_string=None): - super().__init__(filename, - schemaPath='mcs/etc/parameter-schema.xsd', - xml_string=xml_string, - removeComments=True, - conditionalXML=False) + super().__init__( + filename, + schemaPath="mcs/etc/parameter-schema.xsd", + xml_string=xml_string, + removeComments=True, + conditionalXML=False, + ) root = self.getRoot() - params = [Parameter(elt) for elt in root.findall('Parameter')] - self.parameter_dict = {p.name : p for p in params if p.active} + params = [Parameter(elt) for elt in root.findall("Parameter")] + self.parameter_dict = {p.name: p for p in params if p.active} @classmethod def load(cls, pathname=None, xml_string=None): @@ -38,10 +41,8 @@ def load(cls, pathname=None, xml_string=None): if xml_string: distros_xml = None else: - default_path = getParam('OPGEE.DistributionFile') - distros_xml = pathname or resourceStream(default_path, - stream_type="bytes", - decode=None) + default_path = getParam("OPGEE.DistributionFile") + distros_xml = pathname or resourceStream(default_path, stream_type="bytes", decode=None) return ParameterList(distros_xml, xml_string=xml_string) @@ -59,10 +60,10 @@ def parameters(self): class Parameter(OpgeeObject): def __init__(self, elt): - self.name = elt.attrib['name'] - self.active = getBooleanXML(elt.attrib.get('active', '1')) + self.name = elt.attrib["name"] + self.active = getBooleanXML(elt.attrib.get("active", "1")) self.shape = None - distro = elt.find('Distribution') + distro = elt.find("Distribution") self.rv = self.create_rv(distro) if self.active else None def __str__(self): @@ -71,7 +72,7 @@ def __str__(self): def create_rv(self, distro_elt): name = self.name - child = distro_elt[0] # schema ensures only one sub-element + child = distro_elt[0] # schema ensures only one sub-element self.shape = shape = child.tag attrib = child.attrib @@ -79,14 +80,14 @@ def number(name, default=None): s = attrib.get(name) return default if s is None else float(s) - low = number('min') - high = number('max') - mean = number('mean') - stdev = number('stdev') - mode = number('mode') - log_mean = number('log_mean') - log_stdev = number('log_stdev') - prob_of_yes = number('prob_of_yes', 0.5) + low = number("min") + high = number("max") + mean = number("mean") + stdev = number("stdev") + mode = number("mode") + log_mean = number("log_mean") + log_stdev = number("log_stdev") + prob_of_yes = number("prob_of_yes", 0.5) # if low == '' and high == '' and mean == '' and prob_of_yes == '' and shape != 'empirical': # _logger.info(f"* {name} depends on other distributions / smart defaults") # TODO add in lookup of attribute value @@ -94,48 +95,47 @@ def number(name, default=None): rv = None - if shape == 'Binary': + if shape == "Binary": if prob_of_yes == 0 or prob_of_yes == 1: _logger.info(f"* Ignoring distribution on {name}, Binary distribution has prob_of_yes = {prob_of_yes}") else: - rv = get_frozen_rv('weighted_binary', prob_of_one=prob_of_yes) + rv = get_frozen_rv("weighted_binary", prob_of_one=prob_of_yes) - elif shape == 'Uniform': + elif shape == "Uniform": if low == high: _logger.info(f"* Ignoring distribution on {name}, Uniform high and low bounds are both {low}") else: - rv = get_frozen_rv('uniform', min=low, max=high) + rv = get_frozen_rv("uniform", min=low, max=high) - elif shape == 'Triangle': + elif shape == "Triangle": if low == high: _logger.info(f"* Ignoring distribution on {name}, Triangle high and low bounds are both {low}") else: - rv = get_frozen_rv('triangle', min=low, mode=mode, max=high) + rv = get_frozen_rv("triangle", min=low, mode=mode, max=high) - elif shape == 'Normal': + elif shape == "Normal": if stdev == 0.0: _logger.info(f"* Ignoring distribution on {name}, Normal has stdev = 0") else: if low is None or high is None: - rv = get_frozen_rv('normal', mean=mean, stdev=stdev) + rv = get_frozen_rv("normal", mean=mean, stdev=stdev) else: - rv = get_frozen_rv('truncated_normal', mean=mean, stdev=stdev, low=low, high=high) + rv = get_frozen_rv("truncated_normal", mean=mean, stdev=stdev, low=low, high=high) - elif shape == 'Lognormal': + elif shape == "Lognormal": if log_stdev == 0.0: _logger.info(f"* Ignoring distribution on {name}, Lognormal has stdev = 0") else: - if low is None or high is None: # must specify both low and high - rv = get_frozen_rv('lognormal', logmean=log_mean, logstdev=log_stdev) + if low is None or high is None: # must specify both low and high + rv = get_frozen_rv("lognormal", logmean=log_mean, logstdev=log_stdev) else: - rv = get_frozen_rv('truncated_lognormal', - logmean=log_mean, logstdev=log_stdev, low=low, high=high) + rv = get_frozen_rv("truncated_lognormal", logmean=log_mean, logstdev=log_stdev, low=low, high=high) - elif shape == 'Choice': + elif shape == "Choice": _logger.info("* Choice distribution is not yet supported") - elif shape == 'DataFile': - rv = get_frozen_rv('empirical', pathname=child.text, colname=self.name) + elif shape == "DataFile": + rv = get_frozen_rv("empirical", pathname=child.text, colname=self.name) else: raise McsSystemError(f"Unknown distribution shape: '{shape}'") diff --git a/opgee/mcs/simulation.py b/opgee/mcs/simulation.py index 75508c58..f1871549 100644 --- a/opgee/mcs/simulation.py +++ b/opgee/mcs/simulation.py @@ -55,9 +55,7 @@ def read_distributions(pathname=None): :param pathname: (str) the pathname of the CSV file describing parameter distributions :return: (none) """ - distros_csv = pathname or resourceStream( - DISTROS_CSV, stream_type="bytes", decode=None - ) + distros_csv = pathname or resourceStream(DISTROS_CSV, stream_type="bytes", decode=None) df = pd.read_csv(distros_csv, skip_blank_lines=True, comment="#").fillna("") @@ -75,13 +73,7 @@ def read_distributions(pathname=None): if name == "": continue - if ( - low == "" - and high == "" - and mean == "" - and prob_of_yes == "" - and shape != "empirical" - ): + if low == "" and high == "" and mean == "" and prob_of_yes == "" and shape != "empirical": _logger.info( f"* {name} depends on other distributions / smart defaults" ) # TODO add in lookup of attribute value @@ -89,29 +81,21 @@ def read_distributions(pathname=None): if shape == "binary": if prob_of_yes == 0 or prob_of_yes == 1: - _logger.info( - f"* Ignoring distribution on {name}, Binary distribution has prob_of_yes = {prob_of_yes}" - ) + _logger.info(f"* Ignoring distribution on {name}, Binary distribution has prob_of_yes = {prob_of_yes}") continue - rv = get_frozen_rv( - "weighted_binary", prob_of_one=0.5 if prob_of_yes == "" else prob_of_yes - ) + rv = get_frozen_rv("weighted_binary", prob_of_one=0.5 if prob_of_yes == "" else prob_of_yes) elif shape == "uniform": if low == high: - _logger.info( - f"* Ignoring distribution on {name}, Uniform high and low bounds are both {low}" - ) + _logger.info(f"* Ignoring distribution on {name}, Uniform high and low bounds are both {low}") continue rv = get_frozen_rv("uniform", min=low, max=high) elif shape == "triangular": if low == high: - _logger.info( - f"* Ignoring distribution on {name}, Triangle high and low bounds are both {low}" - ) + _logger.info(f"* Ignoring distribution on {name}, Triangle high and low bounds are both {low}") continue rv = get_frozen_rv("triangle", min=low, mode=default, max=high) @@ -124,15 +108,11 @@ def read_distributions(pathname=None): if low == "" or high == "": rv = get_frozen_rv("normal", mean=mean, stdev=stdev) else: - rv = get_frozen_rv( - "truncated_normal", mean=mean, stdev=stdev, low=low, high=high - ) + rv = get_frozen_rv("truncated_normal", mean=mean, stdev=stdev, low=low, high=high) elif shape == "lognormal": if stdev == 0.0: - _logger.info( - f"* Ignoring distribution on {name}, Lognormal has stdev = 0" - ) + _logger.info(f"* Ignoring distribution on {name}, Lognormal has stdev = 0") continue if low == "" or high == "": # must specify both low and high @@ -157,7 +137,6 @@ def read_distributions(pathname=None): class Distribution(OpgeeObject): - instances = {} def __init__(self, full_name, rv): @@ -223,7 +202,6 @@ def __init__( save_to_path=None, meta_data_only=False, ): - if not os.path.isdir(sim_dir): raise McsUserError(f"Simulation directory '{sim_dir}' does not exist.") @@ -248,9 +226,7 @@ def __init__( if trials > 0: self.generate() - if ( - meta_data_only - ): # a slight misnomer since we may generate trial_data.csv, too + if meta_data_only: # a slight misnomer since we may generate trial_data.csv, too return try: @@ -258,9 +234,7 @@ def __init__( with open(model_file) as f: self.model_xml_string = f.read() except Exception as e: - raise McsSystemError( - f"Failed to read model file '{model_file}' to XML string: {e}" - ) + raise McsSystemError(f"Failed to read model file '{model_file}' to XML string: {e}") # TBD: to allow the same trial_num to be run across fields, cache field # trial_data in a dict by field name rather than a single DF @@ -286,9 +260,7 @@ def load_model(self, save_to_path=None): self.analysis = self.model.get_analysis(self.analysis_name, raiseError=False) if not self.analysis: - raise CommandlineError( - f"Analysis '{self.analysis_name}' was not found in model" - ) + raise CommandlineError(f"Analysis '{self.analysis_name}' was not found in model") @classmethod def read_metadata(cls, sim_dir): @@ -319,9 +291,7 @@ def _load_meta_data(self, field_names): if field_names: names = set(field_names) # Use list comprehension rather than set.intersection to maintain original order - self.field_names = [ - name for name in metadata["field_names"] if name in names - ] + self.field_names = [name for name in metadata["field_names"] if name in names] else: self.field_names = metadata["field_names"] @@ -357,8 +327,7 @@ def new( if os.path.lexists(sim_dir): if not overwrite: raise McsUserError( - f"Directory '{sim_dir}' already exists. Use " - "Simulation.new(sim_dir, overwrite=True) to replace it." + f"Directory '{sim_dir}' already exists. Use Simulation.new(sim_dir, overwrite=True) to replace it." ) removeTree(sim_dir, ignore_errors=False) @@ -465,9 +434,7 @@ def lookup(self, full_name, field): else: obj = field.find_process(class_name) if obj is None: - raise McsUserError( - f"A process of class '{class_name}' was not found in {field}" - ) + raise McsUserError(f"A process of class '{class_name}' was not found in {field}") attr_obj = obj.attr_dict.get(attr_name) if attr_obj is None: @@ -495,13 +462,9 @@ def generate(self, corr_mat=None): for dist in distributions: rv_list.append(dist.rv) - cols.append( - dist.attr_name if dist.class_name == "Field" else dist.full_name - ) + cols.append(dist.attr_name if dist.class_name == "Field" else dist.full_name) - self.trial_data_df = df = lhs( - rv_list, trials, columns=cols, corrMat=corr_mat - ) + self.trial_data_df = df = lhs(rv_list, trials, columns=cols, corrMat=corr_mat) df.index.name = "trial_num" self.save_trial_data(field_name) @@ -644,9 +607,7 @@ def run_packet(self, packet, result_type=SIMPLE_RESULT): ) results.append(result) - _logger.warning( - f"Exception raised in trial {trial_num} in {field_name}: {e}" - ) + _logger.warning(f"Exception raised in trial {trial_num} in {field_name}: {e}") _logger.debug(traceback.format_exc()) continue diff --git a/opgee/model.py b/opgee/model.py index 2646b7ad..4d4f7651 100644 --- a/opgee/model.py +++ b/opgee/model.py @@ -21,13 +21,13 @@ _logger = getLogger(__name__) -class Model(Container): +class Model(Container): def __init__(self, name, attr_dict=None, table_updates=None): super().__init__(name, attr_dict=attr_dict, parent=None) Model.instance = self - self.schema_version = attr_dict.get('schema_version', DEFAULT_SCHEMA_VERSION) + self.schema_version = attr_dict.get("schema_version", DEFAULT_SCHEMA_VERSION) # These are set in from_xml after instantiation self.analysis_dict = None @@ -36,20 +36,22 @@ def __init__(self, name, attr_dict=None, table_updates=None): self.pathnames = None # set by calling set_pathnames(path) # parameters controlling process cyclic calculations - self.maximum_iterations = self.attr('maximum_iterations') - self.maximum_change = self.attr('maximum_change') + self.maximum_iterations = self.attr("maximum_iterations") + self.maximum_change = self.attr("maximum_change") self.table_mgr = tbl_mgr = TableManager(updates=table_updates) # load all the GWP options - df = tbl_mgr.get_table('GWP') + df = tbl_mgr.get_table("GWP") self.gwp_horizons = list(df.Years.unique()) self.gwp_versions = list(df.columns[2:]) - self.gwp_dict = {y: df.query('Years == @y').set_index('Gas', drop=True).drop('Years', axis='columns') for y in - self.gwp_horizons} + self.gwp_dict = { + y: df.query("Years == @y").set_index("Gas", drop=True).drop("Years", axis="columns") + for y in self.gwp_horizons + } - constants_df = tbl_mgr.get_table('constants') + constants_df = tbl_mgr.get_table("constants") self.constants = {name: ureg.Quantity(float(row.value), row.unit) for name, row in constants_df.iterrows()} # TODO: to support PRELIM, we might want a way to handle these that is less model-specific @@ -101,8 +103,8 @@ def __init__(self, name, attr_dict=None, table_updates=None): self.well_completion_and_workover_C1_rate = tbl_mgr.get_table("well-completion-and-workover-C1-rate") # parameters controlling process cyclic calculations - self.maximum_iterations = self.attr('maximum_iterations') - self.maximum_change = self.attr('maximum_change') + self.maximum_iterations = self.attr("maximum_iterations") + self.maximum_change = self.attr("maximum_change") self.pathnames = None # set by calling set_pathnames(path) # TBD: apply table updates @@ -175,8 +177,9 @@ def from_xml(cls, elt, parent=None, analysis_names=None, field_names=None): model.field_dict = model.adopt(fields, asDict=True) - analyses = instantiate_subelts(elt, Analysis, parent=model, include_names=analysis_names, - field_names=field_names) + analyses = instantiate_subelts( + elt, Analysis, parent=model, include_names=analysis_names, field_names=field_names + ) if analysis_names and not analyses: raise CommandlineError(f"Specified analyses {analysis_names} not found in model") diff --git a/opgee/model_file.py b/opgee/model_file.py index f86a89e2..644f213e 100644 --- a/opgee/model_file.py +++ b/opgee/model_file.py @@ -25,12 +25,14 @@ _logger = getLogger(__name__) + class ModelCache(object): """ Support for optimizing reading / running fields from large (i.e., thousands) of Fields from Model files generated from XML. XMLFile instances are cached by pathname to avoid rereading large files. """ + model_file_cache = {} @classmethod @@ -52,11 +54,12 @@ def get_xml_file(cls, model_xml): try: obj = cls.model_file_cache[model_xml] except KeyError: - obj = XMLFile(model_xml, schemaPath='etc/opgee.xsd') + obj = XMLFile(model_xml, schemaPath="etc/opgee.xsd") cls.model_file_cache[model_xml] = obj return obj + def model_analysis_names(model_xml): """ Return the names of all elements in the file ``model_xml``. @@ -67,9 +70,10 @@ def model_analysis_names(model_xml): xml_file_obj = ModelCache.get_xml_file(model_xml) root = xml_file_obj.getRoot() - analyses = root.xpath('/Model/Analysis/@name') + analyses = root.xpath("/Model/Analysis/@name") return analyses + def fields_for_analysis(model_xml, analysis_name): """ Return a list of the names of Fields in the Analysis @@ -81,7 +85,8 @@ def fields_for_analysis(model_xml, analysis_name): Analysis. """ import re - timer = Timer('fields_for_analysis') + + timer = Timer("fields_for_analysis") xml_file_obj = ModelCache.get_xml_file(model_xml) @@ -95,19 +100,19 @@ def fields_for_analysis(model_xml, analysis_name): group_fields = [] if len(groups_elts) > 0: - model_fields = root.xpath('/Model/Field/@name') - field_groups = root.xpath('/Model/Field/Group') + model_fields = root.xpath("/Model/Field/@name") + field_groups = root.xpath("/Model/Field/Group") for group_elt in groups_elts: text = group_elt.text - is_regex = getBooleanXML(group_elt.attrib.get('regex', '0')) + is_regex = getBooleanXML(group_elt.attrib.get("regex", "0")) if is_regex: prog = re.compile(text) matches = [name for name in model_fields if prog.match(name)] else: # find matching group declarations in elements - matches = [g.getparent().attrib['name'] for g in field_groups if g.text == text] + matches = [g.getparent().attrib["name"] for g in field_groups if g.text == text] group_fields.extend(matches) @@ -116,6 +121,7 @@ def fields_for_analysis(model_xml, analysis_name): _logger.debug(timer.stop()) return field_names + # TBD: this is used only by extract_model(..., as_string=True) so perhaps # this can be simplified or split into two functions def _get_xml_str(model_xml, analysis_name, field_name, with_model_elt=False): @@ -151,19 +157,20 @@ def _get_xml_str(model_xml, analysis_name, field_name, with_model_elt=False): field_def = field_defs[0] # Create a model with just the extracted Field and surrounding elements - model = ET.Element('Model') - analysis = ET.SubElement(model, 'Analysis', name=analysis_name) + model = ET.Element("Model") + analysis = ET.SubElement(model, "Analysis", name=analysis_name) analysis_attrs = root.xpath(f'/Model/Analysis[@name="{analysis_name}"]/A') for attr in analysis_attrs: analysis.append(deepcopy(attr)) - ET.SubElement(analysis, 'FieldRef', name=field_name) - + ET.SubElement(analysis, "FieldRef", name=field_name) + model.append(deepcopy(field_def)) xml_string = ET.tostring(model, pretty_print=True, encoding="unicode") return (xml_string, model) if with_model_elt else xml_string + def _get_tmp_xml_file(model_xml, analysis_name, field_name): """ Save an XML model file to the temp folder identified by config variable @@ -180,18 +187,17 @@ def _get_tmp_xml_file(model_xml, analysis_name, field_name): """ from lxml import etree as ET - xml_string, model = _get_xml_str(model_xml, analysis_name, field_name, - with_model_elt=True) + xml_string, model = _get_xml_str(model_xml, analysis_name, field_name, with_model_elt=True) # replaces spaces with underscores - field_name = field_name.replace(' ', '_') - tmp_dir = pathjoin(getParam('OPGEE.TempDir'), 'extracted_xml') + field_name = field_name.replace(" ", "_") + tmp_dir = pathjoin(getParam("OPGEE.TempDir"), "extracted_xml") mkdirs(tmp_dir) - xml_file = pathjoin(tmp_dir, field_name + '.xml') + xml_file = pathjoin(tmp_dir, field_name + ".xml") # Write the XML to a file in tmp_dir tree = ET.ElementTree(model) - tree.write(xml_file, xml_declaration=True, pretty_print=True, encoding='utf-8') + tree.write(xml_file, xml_declaration=True, pretty_print=True, encoding="utf-8") return xml_file @@ -216,10 +222,12 @@ def extract_model(model_xml, analysis_name, field_names): for field_name in field_names: yield field_name, _get_xml_str(model_xml, analysis_name, field_name) + # TBD: extract the XML file merging logic to a function or @classmethod so it can # be used prior to instantiating the ModelFile instance. Or, is it adequate to # pass the xml_string in lieu of the .xml file(s) to be merged? + class ModelFile(XMLFile): """ Represents the overall opgee.xml file. @@ -231,10 +239,18 @@ class ModelFile(XMLFile): _loaded_stream_components = False _loaded_user_classes = False - def __init__(self, pathnames, xml_string=None, add_stream_components=True, - use_class_path=True, use_default_model=True, - instantiate_model=True, save_to_path=None, - analysis_names=None, field_names=None): + def __init__( + self, + pathnames, + xml_string=None, + add_stream_components=True, + use_class_path=True, + use_default_model=True, + instantiate_model=True, + save_to_path=None, + analysis_names=None, + field_names=None, + ): """ Several steps are performed, some of which are dependent on the function's parameters: @@ -273,7 +289,7 @@ def __init__(self, pathnames, xml_string=None, add_stream_components=True, ignored when building the model from the XML. (Avoids long model build times for Monte Carlo simulations on a large number of fields.) """ - load_timer = Timer('ModelFile load XML') + load_timer = Timer("ModelFile load XML") source = "XML string" if xml_string else (pathnames or "default model") _logger.debug(f"Loading model from: {source}") @@ -284,33 +300,36 @@ def __init__(self, pathnames, xml_string=None, add_stream_components=True, if not (pathnames or use_default_model or xml_string): raise OpgeeException("ModelFile: no model XML file or string specified") - opgee_xml = getParam('OPGEE.ModelFile') # default is 'etc/opgee.xml' - attributes_xml = getParam('OPGEE.AttributesFile') # default is 'etc/attributes.xml' + opgee_xml = getParam("OPGEE.ModelFile") # default is 'etc/opgee.xml' + attributes_xml = getParam("OPGEE.AttributesFile") # default is 'etc/attributes.xml' base_stream = base_path = None # Assemble a list of built-in and user XML files to read and merge if use_default_model: if is_relpath(opgee_xml): - base_stream = resourceStream(opgee_xml, stream_type='bytes', decode=None) + base_stream = resourceStream(opgee_xml, stream_type="bytes", decode=None) else: base_path = opgee_xml else: base_path = pathnames.pop(0) if pathnames else None # Use superclass XMLFile to load base file we will merge into - super().__init__(base_stream or base_path, xml_string=xml_string, schemaPath='etc/opgee.xsd') + super().__init__(base_stream or base_path, xml_string=xml_string, schemaPath="etc/opgee.xsd") self.root = base_root = self.tree.getroot() # Read and validate the format of any other input files. - xml_files = [XMLFile(path, schemaPath='etc/opgee.xsd') for path in pathnames] + xml_files = [XMLFile(path, schemaPath="etc/opgee.xsd") for path in pathnames] if not xml_string: # Push the XMLFile for attributes.xml onto the front of 'xml_files' - attr_stream_or_path = (resourceStream(attributes_xml, stream_type='bytes', decode=None) - if is_relpath(attributes_xml) else attributes_xml) + attr_stream_or_path = ( + resourceStream(attributes_xml, stream_type="bytes", decode=None) + if is_relpath(attributes_xml) + else attributes_xml + ) - xml_file = XMLFile(attr_stream_or_path, schemaPath='etc/opgee.xsd') + xml_file = XMLFile(attr_stream_or_path, schemaPath="etc/opgee.xsd") xml_files.insert(0, xml_file) # Read all XML files and merge everything below into base_root @@ -321,14 +340,16 @@ def __init__(self, pathnames, xml_string=None, add_stream_components=True, # Find Fields with modifies="..." attribute, copy the indicated Field, merge in the # elements under the Field with modifies=, and replace elt. This is useful for # debugging and storing the expanded "final" XML facilitates publication and replication. - found = base_root.xpath('//Field[@modifies]') + found = base_root.xpath("//Field[@modifies]") for elt in found: attrib = elt.attrib - modifies = attrib['modifies'] - new_name = attrib['name'] + '__TMP__' + modifies = attrib["modifies"] + new_name = attrib["name"] + "__TMP__" if base_root.find(f"Field[@name='{new_name}']") is not None: - raise XmlFormatError(f"Can't copy field '{modifies}' to '{new_name}': a field named '{new_name}' already exists.") + raise XmlFormatError( + f"Can't copy field '{modifies}' to '{new_name}': a field named '{new_name}' already exists." + ) to_copy = base_root.find(f"Field[@name='{modifies}']") @@ -336,15 +357,15 @@ def __init__(self, pathnames, xml_string=None, add_stream_components=True, raise XmlFormatError(f"Can't create field '{new_name}': source field '{modifies}' not found.") # Change attribute from "modifies" to "modified" to record action and avoid redoing it - del attrib['modifies'] - attrib['modified'] = modifies + del attrib["modifies"] + attrib["modified"] = modifies - copied = deepcopy(to_copy) # don't modify the original - copied.attrib.update(attrib) # copy elt's attributes into `copied` + copied = deepcopy(to_copy) # don't modify the original + copied.attrib.update(attrib) # copy elt's attributes into `copied` # N.B. Elements don't match unless *all* attribs are identical. Maybe match only on tag and name attribute?? - merge_elements(copied, elt[:]) # merge elt's children into `copied` - base_root.append(copied) # add the copy to the Model + merge_elements(copied, elt[:]) # merge elt's children into `copied` + base_root.append(copied) # add the copy to the Model # Remove old after inserting the expanded copy parent = elt.getparent() @@ -353,14 +374,14 @@ def __init__(self, pathnames, xml_string=None, add_stream_components=True, # TBD: currently each worker overwrites the same file. Maybe just skip this next line? Skip if running MCS? if not xml_string: # function argument overrides config file variable - save_to_path = getParam('OPGEE.XmlSavePathname') if save_to_path is None else save_to_path + save_to_path = getParam("OPGEE.XmlSavePathname") if save_to_path is None else save_to_path # Save the merged file if indicated if save_to_path: save_xml(save_to_path, base_root, backup=True) # There must be exactly one as child of - found = base_root.findall('AttrDefs') + found = base_root.findall("AttrDefs") if found is None or len(found) == 0: raise XmlFormatError(f"Missing as child of in '{pathnames}'") @@ -371,9 +392,9 @@ def __init__(self, pathnames, xml_string=None, add_stream_components=True, # Process user configuration settings if add_stream_components: - extra_components = getParam('OPGEE.StreamComponents') # DOCUMENT this config parameter + extra_components = getParam("OPGEE.StreamComponents") # DOCUMENT this config parameter if extra_components: - names = splitAndStrip(extra_components, ',') + names = splitAndStrip(extra_components, ",") Stream.extend_components(names) def _load_from_path(module_path): @@ -383,11 +404,11 @@ def _load_from_path(module_path): # Load user classes, if indicated in config file, prior to parsing the XML structure if use_class_path: - class_path = getParam('OPGEE.ClassPath') + class_path = getParam("OPGEE.ClassPath") paths = [Path(path) for path in class_path.split(os.path.pathsep) if path] for path in paths: if path.is_dir(): - for module_path in path.glob('*.py'): # load all .py files found in directory + for module_path in path.glob("*.py"): # load all .py files found in directory _load_from_path(module_path) else: print(f"Loading module from '{path}'") @@ -400,7 +421,7 @@ def _load_from_path(module_path): # the merge subcommand specifies instantiate_model=False, but normally the model is loaded. if instantiate_model: - build_timer = Timer('ModelFile build model') + build_timer = Timer("ModelFile build model") _logger.debug(build_timer) # TODO: debugging support @@ -408,8 +429,7 @@ def _load_from_path(module_path): # print(f"Writing model to '{outfile}'") # save_xml(outfile, base_root, overwrite=True) - self.model = model = Model.from_xml(base_root, analysis_names=analysis_names, - field_names=field_names) + self.model = model = Model.from_xml(base_root, analysis_names=analysis_names, field_names=field_names) _logger.debug(build_timer.stop()) model.validate() @@ -421,9 +441,15 @@ def _load_from_path(module_path): self.model = None @classmethod - def from_xml_string(cls, xml_string, add_stream_components=True, - use_class_path=True, use_default_model=True, - analysis_names=None, field_names=None): + def from_xml_string( + cls, + xml_string, + add_stream_components=True, + use_class_path=True, + use_default_model=True, + analysis_names=None, + field_names=None, + ): """ Create a ModelFile instance from an XML string representing the XML model structure. This provides an alternative to storing the model in a separate XML file, e.g., for @@ -438,19 +464,21 @@ def from_xml_string(cls, xml_string, add_stream_components=True, import os from tempfile import mkstemp - fd, tmp_file = mkstemp(suffix='.xml', text=True) + fd, tmp_file = mkstemp(suffix=".xml", text=True) os.write(fd, str.encode(xml_string)) os.close(fd) try: - model_file = ModelFile([tmp_file], # TBD: use xml_string=xml_string instead of tmp_file - add_stream_components=add_stream_components, - use_class_path=use_class_path, - use_default_model=use_default_model, - analysis_names=analysis_names, - field_names=field_names, - instantiate_model=True, - save_to_path="") # ensures no saving. Passing None falls back to config var + model_file = ModelFile( + [tmp_file], # TBD: use xml_string=xml_string instead of tmp_file + add_stream_components=add_stream_components, + use_class_path=use_class_path, + use_default_model=use_default_model, + analysis_names=analysis_names, + field_names=field_names, + instantiate_model=True, + save_to_path="", + ) # ensures no saving. Passing None falls back to config var except Exception as e: raise XmlFormatError(f"Failed to create ModelFile from string: {e}") diff --git a/opgee/pkg_utils.py b/opgee/pkg_utils.py index 7651b2aa..88abdb72 100644 --- a/opgee/pkg_utils.py +++ b/opgee/pkg_utils.py @@ -1,4 +1,4 @@ -''' +""" .. Created as part of pygcam (2015) Imported into opgee (2021) @@ -6,13 +6,15 @@ .. Copyright (c) 2015-2022 Richard Plevin See the https://opensource.org/licenses/MIT for license details. -''' +""" + import io import pkgutil from .error import OpgeeException -DFLT_ENCODING = 'utf-8' +DFLT_ENCODING = "utf-8" + def getResource(relpath, decode=DFLT_ENCODING): """ @@ -24,10 +26,11 @@ def getResource(relpath, decode=DFLT_ENCODING): None to return the data without decoding. :return: the file contents """ - contents = pkgutil.get_data('opgee', relpath) + contents = pkgutil.get_data("opgee", relpath) return contents.decode(decode) if decode else contents -def resourceStream(relpath, stream_type='text', decode=DFLT_ENCODING): + +def resourceStream(relpath, stream_type="text", decode=DFLT_ENCODING): """ Return a stream on the resource found on the given path relative to the opgee package. @@ -36,9 +39,9 @@ def resourceStream(relpath, stream_type='text', decode=DFLT_ENCODING): :param stream_type: (str) the type of stream to create, either 'text' or 'bytes' :return: (file-like stream) a file-like buffer opened on the desired resource. """ - valid_types = ('text', 'bytes') + valid_types = ("text", "bytes") if stream_type not in valid_types: raise OpgeeException(f"resourceStream type argument {type} is not allowed; it must be one of {valid_types}") text = getResource(relpath, decode=decode) - return io.BytesIO(text) if stream_type == 'bytes' else io.StringIO(text) + return io.BytesIO(text) if stream_type == "bytes" else io.StringIO(text) diff --git a/opgee/post_processor.py b/opgee/post_processor.py index 5f09727a..c6ee13e3 100644 --- a/opgee/post_processor.py +++ b/opgee/post_processor.py @@ -11,6 +11,7 @@ from .core import OpgeeObject from .error import AbstractMethodError, McsUserError + class PostProcessor(OpgeeObject): """ Abstract base class for post-processing plugins. @@ -56,7 +57,7 @@ class PostProcessor(OpgeeObject): # List subclass instances in order defined on the command-line instances = [] - + _plugins_loaded: bool = False def __init__(self): @@ -76,7 +77,7 @@ def run(self, analysis, field, results): the Field. :return: nothing """ - raise AbstractMethodError(self.__class__, 'PostProcessor.run') + raise AbstractMethodError(self.__class__, "PostProcessor.run") def save(self, output_dir): """ @@ -140,7 +141,7 @@ def load_plugin(cls, path): @staticmethod def _getPluginDirs(): - pluginPath = getParam('OPGEE.PostProcPluginPath') + pluginPath = getParam("OPGEE.PostProcPluginPath") if not pluginPath: return [] @@ -163,7 +164,7 @@ def load_all_plugins(cls): return for dir in dirs: - files = sorted(glob.glob(os.path.join(dir, '*.py'))) + files = sorted(glob.glob(os.path.join(dir, "*.py"))) for file in files: cls.load_plugin(file) cls._plugins_loaded = True diff --git a/opgee/process.py b/opgee/process.py index 6d584c7c..a4ce0fff 100644 --- a/opgee/process.py +++ b/opgee/process.py @@ -41,7 +41,7 @@ def _subclass_dict(superclass): :return: (dict) subclasses keyed by name """ - allow_redef = getParamAsBoolean('OPGEE.AllowProcessRedefinition') # DOCUMENT this feature + allow_redef = getParamAsBoolean("OPGEE.AllowProcessRedefinition") # DOCUMENT this feature d = {} @@ -67,10 +67,12 @@ def _subclass_dict(superclass): # _Subclass_dict: Optional[dict] = None + def decache_subclasses(): global _Subclass_dict _Subclass_dict = None + def _get_subclass(cls, subclass_name, reload=False): """ Return the class for `subclass_name`, which must be a known subclass of `cls`. @@ -103,7 +105,7 @@ class IntermediateValues(OpgeeObject): """ def __init__(self): - self.data = pd.DataFrame(columns=('value', 'unit', 'desc')) + self.data = pd.DataFrame(columns=("value", "unit", "desc")) def store(self, name, value, unit=None, desc=None): # Strip magnitude and unit from Quantity objects @@ -111,7 +113,7 @@ def store(self, name, value, unit=None, desc=None): unit = str(value.u) value = value.m - self.data.loc[name, ('value', 'unit', 'desc')] = (value, unit or '', desc or '') + self.data.loc[name, ("value", "unit", "desc")] = (value, unit or "", desc or "") def get(self, name): """ @@ -136,12 +138,36 @@ def run_corr_eqns(x1, x2, x3, x4, x5, coef_df): """ x = pd.Series( - data=[1, x1, x2, x3, x4, x5, x1 * x2, x1 * x3, x1 * x4, x1 * x5, x2 * x3, x2 * x4, x2 * x5, x3 * x4, - x3 * x5, x4 * x5, x1 ** 2, x2 ** 2, x3 ** 2, x4 ** 2, x5 ** 2], index=coef_df.index) + data=[ + 1, + x1, + x2, + x3, + x4, + x5, + x1 * x2, + x1 * x3, + x1 * x4, + x1 * x5, + x2 * x3, + x2 * x4, + x2 * x5, + x3 * x4, + x3 * x5, + x4 * x5, + x1**2, + x2**2, + x3**2, + x4**2, + x5**2, + ], + index=coef_df.index, + ) df = coef_df.mul(x, axis=0) result = df.sum(axis="rows") return result + class Process(AttributeMixin, XmlInstantiable): """ The "leaf" node in the container/process hierarchy. ``Process`` is an abstract superclass: actual @@ -165,8 +191,8 @@ class Process(AttributeMixin, XmlInstantiable): """ # Constants to support stream "finding" methods - INPUT = 'input' - OUTPUT = 'output' + INPUT = "input" + OUTPUT = "output" # the processes that have set iteration values iterating_processes = [] @@ -177,15 +203,16 @@ class Process(AttributeMixin, XmlInstantiable): _required_inputs = [] _required_outputs = [] - def __init__(self, name, attr_dict=None, parent=None, desc=None, - cycle_start=False, impute_start=False, boundary=None): + def __init__( + self, name, attr_dict=None, parent=None, desc=None, cycle_start=False, impute_start=False, boundary=None + ): name = name or self.__class__.__name__ AttributeMixin.__init__(self, attr_dict=attr_dict) XmlInstantiable.__init__(self, name, parent=parent) - self.model = self.find_container('Model') - self.field = field = self.find_container('Field') + self.model = self.find_container("Model") + self.field = field = self.find_container("Field") # One or more of these are used by most processes self.gas = field.gas @@ -196,7 +223,7 @@ def __init__(self, name, attr_dict=None, parent=None, desc=None, self.check_attr_constraints(self.attr_dict) - self.boundary = boundary # the name of the boundary this Process defines, or None + self.boundary = boundary # the name of the boundary this Process defines, or None self.process_EF = self.get_process_EF() @@ -218,14 +245,13 @@ def __init__(self, name, attr_dict=None, parent=None, desc=None, self.intermediate_results = None # Support for cycles - self.visit_count = 0 # increment when the Process has been run + self.visit_count = 0 # increment when the Process has been run self.iteration_count = 0 self.iteration_value = None self.iteration_converged = False self.iteration_registered = False self.in_cycle = False - def check_enabled(self): return @@ -234,9 +260,9 @@ def __str__(self): if type_str == self.name: name_str = "" else: - name_str = f' name="{self.name}"' if self.name else '' + name_str = f' name="{self.name}"' if self.name else "" - return f'<{type_str}{name_str} enabled={self.enabled} @{id(self)}>' + return f"<{type_str}{name_str} enabled={self.enabled} @{id(self)}>" @classmethod def clear_iterating_process_list(cls): @@ -300,10 +326,10 @@ def validate_streams(self): # helper func consolidates input/output stream validation methods def _validate(direction): - if direction == 'input': + if direction == "input": required = self._required_inputs find_func = self.find_input_streams - elif direction == 'output': + elif direction == "output": find_func = self.find_output_streams required = self._required_outputs else: @@ -312,16 +338,15 @@ def _validate(direction): for contents in required: if isinstance(contents, tuple): # tuples indicate sets from which at least one must be present - found = [bool(find_func(c, as_list=True, regex=True, raiseError=False)) - for c in contents] + found = [bool(find_func(c, as_list=True, regex=True, raiseError=False)) for c in contents] if not any(found): msgs.append(f"{self} has no {direction} streams containing any of '{contents}'") elif isinstance(contents, dict): - # dicts are used to indicate max/min allowable occurrences of the given content pattern - pattern = contents['pattern'] - mn = contents['min'] - mx = contents['max'] + # dicts are used to indicate max/min allowable occurrences of the given content pattern + pattern = contents["pattern"] + mn = contents["min"] + mx = contents["max"] found = find_func(pattern, as_list=True, regex=True, raiseError=False) count = len(found) if not (mn <= count <= mx): @@ -330,11 +355,11 @@ def _validate(direction): elif not find_func(contents, as_list=True, regex=True, raiseError=False): msgs.append(f"{self} is missing a required {direction} stream containing '{contents}'") - _validate('input') - _validate('output') + _validate("input") + _validate("output") if msgs: - msg = f"Field {self.field}:\n" + '\n'.join(msgs) + msg = f"Field {self.field}:\n" + "\n".join(msgs) raise ModelValidationError(msg) def reset(self): @@ -446,7 +471,6 @@ def set_combustion_emissions(self): emissions = self.compute_emission_combustion() self.emissions.set_rate(EM_COMBUSTION, "CO2", emissions) - def add_energy_rate(self, carrier, rate): """ Set the rate of energy use for a single carrier. @@ -560,12 +584,9 @@ def find_stream(self, name, raiseError=False) -> Stream: """ return self.field.find_stream(name, raiseError=raiseError) - def _find_streams_by_type(self, direction, stream_type, - combine=False, - as_list=False, - regex=False, - raiseError=True) -> Union[ - Stream, list, dict]: + def _find_streams_by_type( + self, direction, stream_type, combine=False, as_list=False, regex=False, raiseError=True + ) -> Union[Stream, list, dict]: """ Find the input or output streams (indicated by `direction`) that contain the indicated `stream_type`, e.g., 'oil', 'water' and so on. @@ -584,21 +605,16 @@ def _find_streams_by_type(self, direction, stream_type, assert direction in {self.INPUT, self.OUTPUT} stream_list = self.inputs if direction == self.INPUT else self.outputs - streams = [stream for stream in stream_list if - stream.enabled and stream.contains(stream_type, regex=regex)] + streams = [stream for stream in stream_list if stream.enabled and stream.contains(stream_type, regex=regex)] if not streams and raiseError: raise OpgeeException(f"{self}: no {direction} streams contain '{stream_type}'") - return combine_streams(streams) if combine else ( - streams if as_list else {s.name: s for s in streams}) + return combine_streams(streams) if combine else (streams if as_list else {s.name: s for s in streams}) - def find_input_streams(self, stream_type, - combine=False, - as_list=False, - regex=False, - raiseError=True) -> Union[ - Stream, list, dict]: + def find_input_streams( + self, stream_type, combine=False, as_list=False, regex=False, raiseError=True + ) -> Union[Stream, list, dict]: """ Convenience method to call `_find_streams_by_type` with direction "input" @@ -610,15 +626,13 @@ def find_input_streams(self, stream_type, :return: (Stream, list or dict of Streams) depends on various keyword args :raises: OpgeeException if no processes handling `stream_type` are found and `raiseError` is True """ - return self._find_streams_by_type(self.INPUT, stream_type, combine=combine, - as_list=as_list, regex=regex, raiseError=raiseError) + return self._find_streams_by_type( + self.INPUT, stream_type, combine=combine, as_list=as_list, regex=regex, raiseError=raiseError + ) - def find_output_streams(self, stream_type, - combine=False, - as_list=False, - regex=False, - raiseError=True) -> Union[ - Stream, list, dict]: + def find_output_streams( + self, stream_type, combine=False, as_list=False, regex=False, raiseError=True + ) -> Union[Stream, list, dict]: """ Convenience method to call `_find_streams_by_type` with direction "output" @@ -630,8 +644,9 @@ def find_output_streams(self, stream_type, :return: (Stream, list or dict of Streams) depends on various keyword args :raises: OpgeeException if no processes handling `stream_type` are found and `raiseError` is True """ - return self._find_streams_by_type(self.OUTPUT, stream_type, regex=regex, combine=combine, as_list=as_list, - raiseError=raiseError) + return self._find_streams_by_type( + self.OUTPUT, stream_type, regex=regex, combine=combine, as_list=as_list, raiseError=raiseError + ) def find_input_stream(self, stream_type, regex=False, raiseError=True) -> Union[Stream, None]: """ @@ -761,8 +776,9 @@ def converged(prior_value, value): _logger.debug(f"current value is {value}") _logger.debug(f"prior value is {prior_value}") else: - pairs = zip(prior_value, value) if isinstance(value, (tuple, list)) \ - else [(prior_value, value)] # make a list of the one pair + pairs = ( + zip(prior_value, value) if isinstance(value, (tuple, list)) else [(prior_value, value)] + ) # make a list of the one pair if all([converged(old, new) for old, new in pairs]): self.iteration_converged = True @@ -822,7 +838,7 @@ def run(self, analysis): :param analysis: (Analysis) the `Analysis` used to retrieve global settings :return: None """ - raise AbstractMethodError(self.__class__, 'Process.run') + raise AbstractMethodError(self.__class__, "Process.run") # TODO: implement mass balance check def check_balances(self): @@ -857,17 +873,15 @@ def children(self): def run_children(self, **kwargs): pass - def print_running_msg(self): _logger.debug(f"Running {type(self)} name='{self.name}'") def venting_fugitive_rate(self): - loss_rate = self.field.component_fugitive_table # Get loss rate for downhole pump # if self.name == "DownholePump": - return self.attr('leak_rate') + return self.attr("leak_rate") def init_intermediate_results(self, names): """ @@ -959,26 +973,32 @@ def from_xml(cls, elt, parent=None): """ name = elt_name(elt) - if name == 'test_proc': + if name == "test_proc": pass a = elt.attrib - desc = a.get('desc') - impute_start = a.get('impute-start') - cycle_start = a.get('cycle-start') - boundary = a.get('boundary') # optional + desc = a.get("desc") + impute_start = a.get("impute-start") + cycle_start = a.get("cycle-start") + boundary = a.get("boundary") # optional - classname = a['class'] # required by XML schema + classname = a["class"] # required by XML schema subclass = _get_subclass(Process, classname) attr_dict = subclass.instantiate_attrs(elt, is_process=True) - proc = subclass(name, attr_dict=attr_dict, parent=parent, desc=desc, - cycle_start=cycle_start, impute_start=impute_start, - boundary=boundary) + proc = subclass( + name, + attr_dict=attr_dict, + parent=parent, + desc=desc, + cycle_start=cycle_start, + impute_start=impute_start, + boundary=boundary, + ) - proc.set_enabled(a.get('enabled', '1')) - proc.set_extend(a.get('extend', '0')) - proc.set_run_after(getBooleanXML(a.get('after', '0'))) + proc.set_enabled(a.get("enabled", "1")) + proc.set_extend(a.get("extend", "0")) + proc.set_run_after(getBooleanXML(a.get("after", "0"))) return proc @@ -987,12 +1007,13 @@ class Boundary(Process): """ Used to define system boundaries in XML, e.g., """ + def __init__(self, *args, **kwargs): boundary = kwargs.get("boundary") if not boundary: raise OpgeeException("XML elements of class 'Boundary' must define a 'boundary' attribute") - name = f"{boundary}Boundary" # e.g., "ProductionBoundary" + name = f"{boundary}Boundary" # e.g., "ProductionBoundary" super().__init__(name, **kwargs) def is_chosen_boundary(self, analysis): @@ -1009,23 +1030,22 @@ def set_enabled(self, value): for s in self.outputs: s.set_enabled(False) - def run(self, analysis): is_chosen_boundary = self.is_chosen_boundary(analysis) # TODO: # There's a bug in the handling of the streams at boundaries. Basically, if we select the Distribution Boundary, there's no stream # containing PC or oil connected (those are only inputs to the ProductionBoundry). Thus the exports are off # and can lead to divide by 0 errors. - # + # # How would we allow for accurate analysis at the various boundaries? # Option 1: remove "Boundary" processes from the graph. The idea of a boundary would instead be a partition of the underlying - # graph. This might present better accuracy and a simpler interface/xml structure. - # + # graph. This might present better accuracy and a simpler interface/xml structure. + # # Option 2: ensure that all output from intermediate boundaries is carried to the selected boundary. I don't know how we would # achieve this without double counting a lot of stream contents. Perhaps we could add a "remaining" output stream to all boundaries. # If an input stream doesn't have a commensurate output, we'd add its flow rates to the "remaining" stream. All boundaries would be # connected to each other by the "remaining" stream, ensuring that material flows are represented at any/all boundaries. - + # Process boundary if only if the chosen boundary has not been processed if self.field.get_process_data("is_chosen_boundary_processed") is None: # If we're an intermediate boundary, copy all inputs to outputs based on contents @@ -1035,8 +1055,10 @@ def run(self, analysis): break contents = in_stream.contents if len(contents) != 1: - raise ModelValidationError(f"Streams to and from boundaries must have only a " - f"single Content declaration; {self} inputs are {contents}") + raise ModelValidationError( + f"Streams to and from boundaries must have only a " + f"single Content declaration; {self} inputs are {contents}" + ) # If not exactly one stream that declares the same contents, raises error out_stream = self.find_output_stream(contents[0], raiseError=False) @@ -1059,7 +1081,7 @@ def run(self, analysis): exported_oil_LHV = combined_streams.liquid_flow_rate("oil") * self.field.oil.mass_energy_density() # calculate PC energy flow rate - exported_PC_LHV = combined_streams.liquid_flow_rate("PC") * self.model.const('petrocoke-heating-value') + exported_PC_LHV = combined_streams.liquid_flow_rate("PC") * self.model.const("petrocoke-heating-value") exported_prod_LHV = exported_gas_LPG_LHV + exported_oil_LHV + exported_PC_LHV @@ -1069,17 +1091,20 @@ def run(self, analysis): if exported_prod_LHV.m != 0: self.field.save_process_data(is_chosen_boundary_processed=True) + class Reservoir(Process): """ Reservoir represents natural resources such as oil and gas reservoirs, and water sources in the subsurface. Each Field object holds a single Reservoir instance. """ + def __init__(self, parent=None): - super().__init__("Reservoir", parent=parent, desc='The Reservoir') + super().__init__("Reservoir", parent=parent, desc="The Reservoir") def run(self, analysis): self.print_running_msg() + # # This class is defined here rather than in container.py to avoid import loops and to # allow the reference to Aggregator above. @@ -1116,10 +1141,8 @@ def from_xml(cls, elt, parent=None): return obj + def reload_subclass_dict(): global _Subclass_dict - _Subclass_dict = { - Aggregator: _subclass_dict(Aggregator), - Process: _subclass_dict(Process) - } + _Subclass_dict = {Aggregator: _subclass_dict(Aggregator), Process: _subclass_dict(Process)} diff --git a/opgee/process_groups.py b/opgee/process_groups.py index 87a7d188..c12a3fff 100644 --- a/opgee/process_groups.py +++ b/opgee/process_groups.py @@ -20,10 +20,11 @@ class ProcessChoice(XmlInstantiable): """ Contains a set of mutually-exclusive `ProcessGroups`. """ + def __init__(self, name, groups, extend, default): super().__init__(name) - self.extend = extend # TBD: unclear if this is used + self.extend = extend # TBD: unclear if this is used self.default = default # store the groups in a dict for fast lookup, but maintain order for display @@ -55,8 +56,8 @@ def from_xml(cls, elt, parent=None): name = elt_name(elt) a = elt.attrib - extend = getBooleanXML(a.get('extend', 'false')) - default = a.get('default') + extend = getBooleanXML(a.get("extend", "false")) + default = a.get("default") groups = instantiate_subelts(elt, ProcessGroup) @@ -69,6 +70,7 @@ class ProcessGroup(XmlInstantiable): Contains a set of `ProcessRef` and `StreamRef` instances defining one coherent choice of Processes. """ + def __init__(self, name, process_refs, stream_refs, choices): super().__init__(name) @@ -92,8 +94,8 @@ def processes_and_streams(self, field): :return: (tuple of lists of Process and Stream instances) """ - procs = [field.find_process(name) for name in self.process_refs] - streams = [field.find_stream(name) for name in self.stream_refs] + procs = [field.find_process(name) for name in self.process_refs] + streams = [field.find_stream(name) for name in self.stream_refs] return (procs, streams) @classmethod @@ -106,9 +108,9 @@ def from_xml(cls, elt, parent=None): :return: (Process) instance populated from XML """ name = elt_name(elt) - process_refs = [elt_name(node) for node in elt.findall('ProcessRef')] - stream_refs = [elt_name(node) for node in elt.findall('StreamRef')] + process_refs = [elt_name(node) for node in elt.findall("ProcessRef")] + stream_refs = [elt_name(node) for node in elt.findall("StreamRef")] - choices = instantiate_subelts(elt, ProcessChoice) # nested choices + choices = instantiate_subelts(elt, ProcessChoice) # nested choices return ProcessGroup(name, process_refs, stream_refs, choices) diff --git a/opgee/processes/CO2_injection_well.py b/opgee/processes/CO2_injection_well.py index 48ee30f1..a8150e02 100644 --- a/opgee/processes/CO2_injection_well.py +++ b/opgee/processes/CO2_injection_well.py @@ -15,14 +15,15 @@ class CO2InjectionWell(Process): """ - This process models a injection well used for injecting CO2 into the reservoir. + This process models a injection well used for injecting CO2 into the reservoir. - input streams: - - gas: gas stream with CO2 for injection + input streams: + - gas: gas stream with CO2 for injection - output streams: - - gas for gas partition: gas stream with CO2 injected into reservoir + output streams: + - gas for gas partition: gas stream with CO2 injected into reservoir """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) diff --git a/opgee/processes/CO2_membrane.py b/opgee/processes/CO2_membrane.py index 86995bef..cc2c2d44 100644 --- a/opgee/processes/CO2_membrane.py +++ b/opgee/processes/CO2_membrane.py @@ -28,6 +28,7 @@ class CO2Membrane(Process): - gas for CO2 compressor """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -80,11 +81,9 @@ def run(self, analysis): inlet_pressure_after_membrane = max(std_pressure, input.tp.P - self.press_drop) discharge_press = input.tp.P overall_compression_ratio = discharge_press / inlet_pressure_after_membrane - energy_consumption, temp, _ = Compressor.get_compressor_energy_consumption(field, - self.prime_mover_type, - self.eta_compressor, - overall_compression_ratio, - input) + energy_consumption, temp, _ = Compressor.get_compressor_energy_consumption( + field, self.prime_mover_type, self.eta_compressor, overall_compression_ratio, input + ) # energy-use energy_use = self.energy energy_carrier = get_energy_carrier(self.prime_mover_type) diff --git a/opgee/processes/CO2_reinjection_compressor.py b/opgee/processes/CO2_reinjection_compressor.py index 02beb1d5..b706ea48 100644 --- a/opgee/processes/CO2_reinjection_compressor.py +++ b/opgee/processes/CO2_reinjection_compressor.py @@ -18,26 +18,27 @@ class CO2ReinjectionCompressor(Process): """ - A process that compresses CO2 gas for reinjection into the reservoir. + A process that compresses CO2 gas for reinjection into the reservoir. - Inputs: - - gas for CO2 compressor: The inlet stream of CO2 gas to the compressor. + Inputs: + - gas for CO2 compressor: The inlet stream of CO2 gas to the compressor. - Outputs: - - gas: The outlet stream of CO2 gas that is reinjected into the reservoir. + Outputs: + - gas: The outlet stream of CO2 gas that is reinjected into the reservoir. - Attributes: - - res_press: The reservoir pressure in psia. - - eta_compressor: The compressor efficiency. - - prime_mover_type: The type of prime mover used to power the compressor. + Attributes: + - res_press: The reservoir pressure in psia. + - eta_compressor: The compressor efficiency. + - prime_mover_type: The type of prime mover used to power the compressor. """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) # TODO: avoid process names in contents. self._required_inputs = [ - "gas for CO2 compressor", # might be multiple + "gas for CO2 compressor", # might be multiple ] self._required_outputs = [ @@ -77,12 +78,9 @@ def run(self, analysis): input_streams = self.find_input_streams("gas for CO2 compressor") for _, input_stream in input_streams.items(): overall_compression_ratio = discharge_press / input_stream.tp.P - energy_consumption, out_temp, _ = \ - Compressor.get_compressor_energy_consumption(field, - self.prime_mover_type, - self.eta_compressor, - overall_compression_ratio, - input_stream) + energy_consumption, out_temp, _ = Compressor.get_compressor_energy_consumption( + field, self.prime_mover_type, self.eta_compressor, overall_compression_ratio, input_stream + ) total_energy_consumption += energy_consumption # Set output stream and iteration value diff --git a/opgee/processes/LNG_liquefaction.py b/opgee/processes/LNG_liquefaction.py index b8e194c2..b2d98a6d 100644 --- a/opgee/processes/LNG_liquefaction.py +++ b/opgee/processes/LNG_liquefaction.py @@ -16,6 +16,7 @@ class LNGLiquefaction(Process): """ LNG liquefaction calculate emission of produced gas to liquefaction """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -28,7 +29,6 @@ def __init__(self, name, **kwargs): # "gas fugitives" # TODO: future feature ] - self.ancillary_loads = None self.compression_refrigeration_load = None self.NG_to_liq_rate = None @@ -55,11 +55,10 @@ def run(self, analysis): gas_to_transport.copy_flow_rates_from(input) gas_to_transport.tp.set(T=self.field.LNG_temp) - #TODO: Future versions of OPGEE may treat this process in more detail. + # TODO: Future versions of OPGEE may treat this process in more detail. # loss_rate = self.venting_fugitive_rate() # gas_fugitives_temp = self.set_gas_fugitives(input, loss_rate) # gas_fugitives = self.find_output_stream("gas fugitives") # gas_fugitives.copy_flow_rates_from(gas_fugitives_temp) # gas_fugitives.set_temperature_and_pressure(self.std_temp, self.std_press) - diff --git a/opgee/processes/LNG_regasification.py b/opgee/processes/LNG_regasification.py index ebbf6afa..25b2bdeb 100644 --- a/opgee/processes/LNG_regasification.py +++ b/opgee/processes/LNG_regasification.py @@ -17,6 +17,7 @@ class LNGRegasification(Process): """ LNG liquefaction calculate emission of transported gas to regasification """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -63,10 +64,3 @@ def run(self, analysis): # emissions self.set_combustion_emissions() - - - - - - - diff --git a/opgee/processes/LNG_transport.py b/opgee/processes/LNG_transport.py index a44f188c..46673c0b 100644 --- a/opgee/processes/LNG_transport.py +++ b/opgee/processes/LNG_transport.py @@ -52,13 +52,9 @@ def run(self, analysis): # energy use energy_use = self.energy - fuel_consumption = \ - field.transport_energy.get_transport_energy_dict(self.field, - self.transport_parameter, - self.transport_share_fuel, - self.transport_by_mode, - gas_LHV_rate, - "LNG") + fuel_consumption = field.transport_energy.get_transport_energy_dict( + self.field, self.transport_parameter, self.transport_share_fuel, self.transport_by_mode, gas_LHV_rate, "LNG" + ) for name, value in fuel_consumption.items(): energy_use.set_rate(get_energy_carrier(name), value.to("mmBtu/day")) diff --git a/opgee/processes/VF_partition.py b/opgee/processes/VF_partition.py index a699f9f3..6a9f50c0 100644 --- a/opgee/processes/VF_partition.py +++ b/opgee/processes/VF_partition.py @@ -18,6 +18,7 @@ class VFPartition(Process): """ VF (Venting and Flaring) partition is to check the reasonable amount of gas goes to venting, flaring and further process """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -72,8 +73,7 @@ def run(self, analysis): volume_rate_gas_slip = temp * (1 - self.combusted_gas_frac) temp = field.gas.component_gas_rho_STP[volume_rate_gas_combusted.index] - mass_rate_gas_combusted =\ - volume_rate_gas_combusted * temp + mass_rate_gas_combusted = volume_rate_gas_combusted * temp mass_rate_gas_slip = volume_rate_gas_slip * temp gas_to_flare = self.find_output_stream("gas for flaring") @@ -83,7 +83,9 @@ def run(self, analysis): methane_slip = self.find_output_stream("methane slip") temp = Stream("temp", tp=input.tp) temp.copy_flow_rates_from(input) - methane_slip.set_rates_from_series(mass_rate_gas_slip, PHASE_GAS, temp.subtract_rates_from(gas_to_flare, PHASE_GAS)) + methane_slip.set_rates_from_series( + mass_rate_gas_slip, PHASE_GAS, temp.subtract_rates_from(gas_to_flare, PHASE_GAS) + ) methane_slip.set_tp(tp=STP) output_gas = self.find_output_stream("gas for venting") diff --git a/opgee/processes/VRU_compressor.py b/opgee/processes/VRU_compressor.py index 3ee566e2..bde78f65 100644 --- a/opgee/processes/VRU_compressor.py +++ b/opgee/processes/VRU_compressor.py @@ -17,25 +17,22 @@ class VRUCompressor(Process): """ - TBD + TBD - input streams: - - + input streams: + - - output streams: - - + output streams: + - """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) # TODO: avoid process names in contents. - self._required_inputs = [ - "gas for VRU" - ] + self._required_inputs = ["gas for VRU"] - self._required_outputs = [ - "gas" - ] + self._required_outputs = ["gas"] self.discharge_press = None self.eta_compressor = None @@ -62,13 +59,9 @@ def run(self, analysis): gas_to_gathering = self.find_output_stream("gas") overall_compression_ratio = self.discharge_press / input.tp.P - energy_consumption, output_temp, output_press = \ - Compressor.get_compressor_energy_consumption( - self.field, - self.prime_mover_type, - self.eta_compressor, - overall_compression_ratio, - input) + energy_consumption, output_temp, output_press = Compressor.get_compressor_energy_consumption( + self.field, self.prime_mover_type, self.eta_compressor, overall_compression_ratio, input + ) gas_to_gathering.copy_flow_rates_from(input) gas_to_gathering.subtract_rates_from(gas_fugitives) diff --git a/opgee/processes/__init__.py b/opgee/processes/__init__.py index 2293bbd7..b1487f3b 100644 --- a/opgee/processes/__init__.py +++ b/opgee/processes/__init__.py @@ -44,4 +44,3 @@ from .venting import Venting from .water_injection import WaterInjection from .water_treatment import WaterTreatment - diff --git a/opgee/processes/acid_gas_removal.py b/opgee/processes/acid_gas_removal.py index 97f3653d..053e696a 100644 --- a/opgee/processes/acid_gas_removal.py +++ b/opgee/processes/acid_gas_removal.py @@ -16,11 +16,7 @@ _logger = getLogger(__name__) -amine_solution_K_value_dict = { "conv DEA" : 1.45, - "high DEA": 0.95, - "MEA" : 2.05, - "DGA" : 1.28, - "MDEA" : 1.25} +amine_solution_K_value_dict = {"conv DEA": 1.45, "high DEA": 0.95, "MEA": 2.05, "DGA": 1.28, "MDEA": 1.25} class AcidGasRemoval(Process): @@ -59,19 +55,22 @@ class AcidGasRemoval(Process): - calculate_energy_consumption_from_textbook(input_stream, mol_frac_CO2, mol_frac_H2S): Calculates energy consumption for the acid gas removal process using a textbook method. """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) self._required_inputs = [ - "gas for AGR", # TODO: avoid process names in contents. Should be "acidic gas"? + "gas for AGR", # TODO: avoid process names in contents. Should be "acidic gas"? ] self._required_outputs = [ # TODO: If the process name were avoided, we could have just one output stream # with, say, "deacidified gas". Should describe the contents, not the destination. # One of these must exist. - ("gas for demethanizer", # TODO: avoid process names in contents - "gas for gas partition") # TODO: avoid process names in contents + ( + "gas for demethanizer", # TODO: avoid process names in contents + "gas for gas partition", + ) # TODO: avoid process names in contents ] # Optional streams include: @@ -121,8 +120,7 @@ def cache_attributes(self): self.air_cooler_press_drop = self.attr("air_cooler_press_drop") self.air_elevation_const = m.const("air-elevation-corr") self.air_density_ratio = m.const("air-density-ratio") - self.water_press = (field.water.density() * self.air_cooler_press_drop * - m.const("gravitational-acceleration")) + self.water_press = field.water.density() * self.air_cooler_press_drop * m.const("gravitational-acceleration") self.air_cooler_fan_eff = self.attr("air_cooler_fan_eff") self.air_cooler_speed_reducer_eff = self.attr("air_cooler_speed_reducer_eff") self.AGR_table = m.AGR_tbl @@ -156,8 +154,9 @@ def run(self, analysis): H2S_to_demethanizer = ureg.Quantity(0.0, "tonne/day") # Calculate output stream for demethanizer - output_gas = self.find_output_stream("gas for demethanizer", raiseError=False) or \ - self.find_output_stream("gas for gas partition") + output_gas = self.find_output_stream("gas for demethanizer", raiseError=False) or self.find_output_stream( + "gas for gas partition" + ) output_gas.copy_flow_rates_from(gas_input_stream) output_gas.set_gas_flow_rate("CO2", CO2_to_demethanizer) if field.gas_path != "CO2-EOR Membrane": @@ -186,18 +185,21 @@ def run(self, analysis): return if mol_frac_H2S.m <= 0.15 and mol_frac_CO2 <= 0.2: - compressor_energy_consumption, reboiler_fuel_use, electricity_consump =\ + compressor_energy_consumption, reboiler_fuel_use, electricity_consump = ( self.calculate_energy_consumption_from_Aspen(gas_input_stream, output_gas, mol_frac_CO2, mol_frac_H2S) + ) else: - compressor_energy_consumption, reboiler_fuel_use, electricity_consump = \ + compressor_energy_consumption, reboiler_fuel_use, electricity_consump = ( self.calculate_energy_consumption_from_textbook(gas_input_stream, mol_frac_CO2, mol_frac_H2S) + ) # energy-use energy_use = self.energy energy_carrier = get_energy_carrier(self.prime_mover_type) energy_use.set_rate(energy_carrier, compressor_energy_consumption + reboiler_fuel_use) - energy_use.add_rate(EN_ELECTRICITY, electricity_consump) \ - if energy_carrier == EN_ELECTRICITY else energy_use.set_rate(EN_ELECTRICITY, electricity_consump) + energy_use.add_rate( + EN_ELECTRICITY, electricity_consump + ) if energy_carrier == EN_ELECTRICITY else energy_use.set_rate(EN_ELECTRICITY, electricity_consump) # import and export self.set_import_from_energy(energy_use) @@ -207,7 +209,6 @@ def run(self, analysis): # TODO: consider more intelligent splitting into emissions categories self.emissions.set_from_stream(EM_FUGITIVES, gas_fugitives) - def calculate_energy_consumption_from_Aspen(self, input, output_gas, mol_frac_CO2, mol_frac_H2S): """ Calculates the energy consumption in the AGR gas processing unit using Aspen simulation. @@ -221,11 +222,13 @@ def calculate_energy_consumption_from_Aspen(self, input, output_gas, mol_frac_CO """ # Define a dictionary of the bounds for each variable used in the calculation from HYSYS - variable_bound_dict = {"mol_frac_CO2": [0.0, 0.2], - "mol_frac_H2S": [0.0, 0.15], - "reflux_ratio": [1.5, 3.0], - "regen_temp": [190.0, 220.0], # unit in degree F - "feed_gas_press": [14.7, 514.7]} # unit in psia + variable_bound_dict = { + "mol_frac_CO2": [0.0, 0.2], + "mol_frac_H2S": [0.0, 0.15], + "reflux_ratio": [1.5, 3.0], + "regen_temp": [190.0, 220.0], # unit in degree F + "feed_gas_press": [14.7, 514.7], + } # unit in psia # Bound the values for mol_frac_CO2 and mol_frac_H2S using the defined bounds mol_frac_CO2 = get_bounded_value(mol_frac_CO2.to("frac").m, "mol_frac_CO2", variable_bound_dict) @@ -264,41 +267,41 @@ def calculate_energy_consumption_from_Aspen(self, input, output_gas, mol_frac_CO # Calculate the reboiler fuel use overall_compression_ratio = ureg.Quantity(feed_gas_press, "psia") / input.tp.P - compressor_energy_consumption, temp, _ = \ - Compressor.get_compressor_energy_consumption(self.field, - self.prime_mover_type, - self.eta_compressor, - overall_compression_ratio, - output_gas, - inlet_tp=input.tp) + compressor_energy_consumption, temp, _ = Compressor.get_compressor_energy_consumption( + self.field, + self.prime_mover_type, + self.eta_compressor, + overall_compression_ratio, + output_gas, + inlet_tp=input.tp, + ) electricity_consump = pump_duty_elec + condenser_elec_consumption + amine_cooler_elec_consumption return compressor_energy_consumption, reboiler_fuel_use, electricity_consump def calculate_energy_consumption_from_textbook(self, input, mol_frac_CO2, mol_frac_H2S): """ - Calculate energy consumption for the amine unit using the textbook method. + Calculate energy consumption for the amine unit using the textbook method. - :param input: Stream object, input stream to the amine unit. - :param mol_frac_CO2: (Quantity) Molar fraction of CO2 in the input gas stream - :param mol_frac_H2S: (Quantity) Molar fraction of H2S in the input gas stream - :return: (tuple) Compressor energy consumption (Quantity) == 0, Reboiler fuel use (Quantity), and - Electricity consumption (Quantity) + :param input: Stream object, input stream to the amine unit. + :param mol_frac_CO2: (Quantity) Molar fraction of CO2 in the input gas stream + :param mol_frac_H2S: (Quantity) Molar fraction of H2S in the input gas stream + :return: (tuple) Compressor energy consumption (Quantity) == 0, Reboiler fuel use (Quantity), and + Electricity consumption (Quantity) """ # Calculate feed gas volume rate at STP feedin_gas_volume_rate_STP = self.gas.volume_flow_rates_STP(input) # Calculate CO2 and H2S volume rate, if not present set it to 0 - CO2_volume_rate =\ - feedin_gas_volume_rate_STP["CO2"] if mol_frac_CO2.m != 0 else ureg.Quantity(0, "mmscf/day") - H2S_volume_rate =\ - feedin_gas_volume_rate_STP["H2S"] if mol_frac_H2S.m != 0 else ureg.Quantity(0, "mmscf/day") + CO2_volume_rate = feedin_gas_volume_rate_STP["CO2"] if mol_frac_CO2.m != 0 else ureg.Quantity(0, "mmscf/day") + H2S_volume_rate = feedin_gas_volume_rate_STP["H2S"] if mol_frac_H2S.m != 0 else ureg.Quantity(0, "mmscf/day") amine_circulation_rate = self.amine_solution_K_value * (CO2_volume_rate + H2S_volume_rate) # Pumps energy consumption - circulation_pump_HP =\ - ureg.Quantity(amine_circulation_rate.to("gallon / minute").m * (input.tp.P.m + 50) * 0.00065, "horsepower") + circulation_pump_HP = ureg.Quantity( + amine_circulation_rate.to("gallon / minute").m * (input.tp.P.m + 50) * 0.00065, "horsepower" + ) booster_pump_HP = ureg.Quantity(amine_circulation_rate.to("gallon / minute").m * 0.06, "horsepower") reflux_pump_HP = booster_pump_HP total_pump_HP = circulation_pump_HP + booster_pump_HP + reflux_pump_HP @@ -314,10 +317,8 @@ def calculate_energy_consumption_from_textbook(self, input, mol_frac_CO2, mol_fr reboiler_heat_duty = amine_circulation_rate * ureg.Quantity(72000, "btu*minute/gallon/hr") * 1.15 total_reboiler_erergy_consump = self.eta_reboiler * reboiler_heat_duty - return ureg.Quantity(0, "mmbtu/day"), total_reboiler_erergy_consump, total_coolers_energy_consump + total_pump_energy_consump - - - - - - + return ( + ureg.Quantity(0, "mmbtu/day"), + total_reboiler_erergy_consump, + total_coolers_energy_consump + total_pump_energy_consump, + ) diff --git a/opgee/processes/bitumen_mining.py b/opgee/processes/bitumen_mining.py index 92b9bd1d..ba2e1f83 100644 --- a/opgee/processes/bitumen_mining.py +++ b/opgee/processes/bitumen_mining.py @@ -21,30 +21,31 @@ class BitumenMining(Process): # TODO: documentation below describes input streams that do not appear in the code. """ - This process takes input streams and produces output streams as part of an - oil sands mining operation. - - Inputs: - - Streams from bitumen path dictionary - - Outputs: - - Bitumen stream for upgrading or dilution - - Gas stream for partition - - Attributes: - - oil_sands_mine: Name of the oil sands mine - - API_bitumen: API gravity of the bitumen - - bitumen_SG: Specific gravity of the bitumen - - mined_bitumen_tp: Temperature and pressure of the mined bitumen - - oil_prod_rate: Oil production rate - - upgrader_type: Type of upgrader used - - gas_comp: Gas composition - - FOR: Flaring oil ratio - - VOR: Venting oil ratio - - bitumen_path_dict: Dictionary of possible paths for the bitumen stream - - water_density: Density of water - - CH4_loss_rate: Methane loss rate + This process takes input streams and produces output streams as part of an + oil sands mining operation. + + Inputs: + - Streams from bitumen path dictionary + + Outputs: + - Bitumen stream for upgrading or dilution + - Gas stream for partition + + Attributes: + - oil_sands_mine: Name of the oil sands mine + - API_bitumen: API gravity of the bitumen + - bitumen_SG: Specific gravity of the bitumen + - mined_bitumen_tp: Temperature and pressure of the mined bitumen + - oil_prod_rate: Oil production rate + - upgrader_type: Type of upgrader used + - gas_comp: Gas composition + - FOR: Flaring oil ratio + - VOR: Venting oil ratio + - bitumen_path_dict: Dictionary of possible paths for the bitumen stream + - water_density: Density of water + - CH4_loss_rate: Methane loss rate """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -53,16 +54,18 @@ def __init__(self, name, **kwargs): self._required_outputs = [ # TODO: If the process names were avoided, we might have just one output stream # with, say, "heavy oil". Should describe the contents, not the destination. - ("oil for upgrading", # TODO: avoid process names in contents. - "oil for dilution"), # TODO: avoid process names in contents. - + ( + "oil for upgrading", # TODO: avoid process names in contents. + "oil for dilution", + ), # TODO: avoid process names in contents. "gas for partition", ] - - self.bitumen_path_dict = {"Integrated with upgrader": "oil for upgrading", - "Integrated with diluent": "oil for dilution", - "Integrated with both": "oil for dilution"} + self.bitumen_path_dict = { + "Integrated with upgrader": "oil for upgrading", + "Integrated with diluent": "oil for dilution", + "Integrated with both": "oil for dilution", + } self.water_density = self.water.density() self.CH4_loss_rate = None @@ -87,8 +90,7 @@ def cache_attributes(self): self.mined_bitumen_t = field.mined_bitumen_t self.mined_bitumen_p = field.mined_bitumen_p - self.mined_bitumen_tp = TemperaturePressure(self.mined_bitumen_t, - self.mined_bitumen_p) + self.mined_bitumen_tp = TemperaturePressure(self.mined_bitumen_t, self.mined_bitumen_p) self.downhole_pump = field.downhole_pump self.oil_volume_rate = field.oil_volume_rate self.upgrader_type = field.upgrader_type @@ -105,13 +107,14 @@ def run(self, analysis): try: output = self.bitumen_path_dict[self.oil_sands_mine] except: - raise OpgeeException(f"{self.name} bitumen is not recognized:{self.oil_sands_mine}." - f"Must be one of {list(self.bitumen_path_dict.keys())}") + raise OpgeeException( + f"{self.name} bitumen is not recognized:{self.oil_sands_mine}." + f"Must be one of {list(self.bitumen_path_dict.keys())}" + ) output_bitumen = self.find_output_stream(output) output_tp = self.mined_bitumen_tp - output_bitumen.\ - set_liquid_flow_rate("oil", bitumen_mass_rate, tp=output_tp) + output_bitumen.set_liquid_flow_rate("oil", bitumen_mass_rate, tp=output_tp) output_bitumen.set_API(field.attr("API")) self.set_iteration_value(output_bitumen.total_flow_rate()) @@ -132,14 +135,19 @@ def run(self, analysis): # energy-use energy_use = self.energy - NG_consumption = \ - self.oil_volume_rate * ureg.Quantity(mining_intensity_table["Natural gas use"], - unit_col["Natural gas use"]) * self.model.const("NG-heating-value") - diesel_consumption = \ - self.oil_volume_rate * ureg.Quantity(mining_intensity_table["Diesel fuel use"], - unit_col["Diesel fuel use"]) * self.model.const("diesel-LHV") - electricity_consumption = \ - self.oil_volume_rate * ureg.Quantity(mining_intensity_table["Electricity use"], unit_col["Electricity use"]) + NG_consumption = ( + self.oil_volume_rate + * ureg.Quantity(mining_intensity_table["Natural gas use"], unit_col["Natural gas use"]) + * self.model.const("NG-heating-value") + ) + diesel_consumption = ( + self.oil_volume_rate + * ureg.Quantity(mining_intensity_table["Diesel fuel use"], unit_col["Diesel fuel use"]) + * self.model.const("diesel-LHV") + ) + electricity_consumption = self.oil_volume_rate * ureg.Quantity( + mining_intensity_table["Electricity use"], unit_col["Electricity use"] + ) energy_use.set_rate(EN_NATURAL_GAS, NG_consumption.to("mmBtu/day")) energy_use.set_rate(EN_DIESEL, diesel_consumption.to("mmBtu/day")) energy_use.set_rate(EN_ELECTRICITY, electricity_consumption.to("mmBtu/day")) diff --git a/opgee/processes/compressor.py b/opgee/processes/compressor.py index 02b68d57..6a5807c8 100644 --- a/opgee/processes/compressor.py +++ b/opgee/processes/compressor.py @@ -20,15 +20,21 @@ Q_IntTuple = Tuple[Q_Float, int] - _power = [1, 1 / 2, 1 / 3, 1 / 4, 1 / 5] + class Compressor(OpgeeObject): def __init__(self, field): self.field = field @staticmethod - def get_compressor_work_temp(field, inlet_temp, inlet_press, gas_stream, compression_ratio, num_of_compression, + def get_compressor_work_temp( + field, + inlet_temp, + inlet_press, + gas_stream, + compression_ratio, + num_of_compression, ) -> Tuple[Q_Float, Q_Float, Q_Float]: """ @@ -52,7 +58,7 @@ def get_compressor_work_temp(field, inlet_temp, inlet_press, gas_stream, compres inlet_reduced_press = inlet_press / corrected_press z_factor = gas.Z_factor(inlet_reduced_temp, inlet_reduced_press) - work_temp2 = (compression_ratio ** z_factor) ** ratio - 1 + work_temp2 = (compression_ratio**z_factor) ** ratio - 1 work += work_temp1 * work_temp2 * inlet_temp.to("rankine") delta_temp = (inlet_temp.to("rankine") * compression_ratio ** (z_factor * ratio) - inlet_temp) * 0.2 @@ -63,9 +69,7 @@ def get_compressor_work_temp(field, inlet_temp, inlet_press, gas_stream, compres return work_sum, inlet_temp, inlet_press @staticmethod - def get_compression_ratio_stages( - overall_compression_ratio_stages: Sequence[Q_Float] - ) -> Sequence[Q_IntTuple]: + def get_compression_ratio_stages(overall_compression_ratio_stages: Sequence[Q_Float]) -> Sequence[Q_IntTuple]: compression_ratios: map[Optional[Q_IntTuple]] = map( Compressor.get_compression_ratio_and_stage, overall_compression_ratio_stages ) @@ -76,7 +80,7 @@ def get_compression_ratio_stages( def get_compression_ratio_and_stage(overall_compression_ratio: float) -> Optional[Q_IntTuple]: max_stages = len(_power) for pow in _power: - comp_raised = overall_compression_ratio ** pow + comp_raised = overall_compression_ratio**pow if comp_raised < max_stages: return comp_raised, int(1 / pow) @@ -110,12 +114,9 @@ def get_compressor_energy_consumption( return energy_consumption, inlet_temp, inlet_press compression_ratio, num_stages = Compressor.get_compression_ratio_and_stage(overall_compression_ratio) - total_work, outlet_temp, outlet_press = Compressor.get_compressor_work_temp(field, - inlet_temp, - inlet_press, - inlet_stream, - compression_ratio, - num_stages) + total_work, outlet_temp, outlet_press = Compressor.get_compressor_work_temp( + field, inlet_temp, inlet_press, inlet_stream, compression_ratio, num_stages + ) volume_flow_rate_STP = field.gas.volume_flow_rate_STP(inlet_stream) total_energy = total_work * volume_flow_rate_STP brake_horse_power = total_energy / eta_compressor diff --git a/opgee/processes/crude_oil_dewatering.py b/opgee/processes/crude_oil_dewatering.py index dd40b84e..9744f462 100644 --- a/opgee/processes/crude_oil_dewatering.py +++ b/opgee/processes/crude_oil_dewatering.py @@ -19,20 +19,21 @@ class CrudeOilDewatering(Process): """ - A subclass of the Process class that represents a crude oil dewatering process - in an oil and gas production system. - - Attributes: - field (Field): The field associated with the dewatering process. - heater_treater (bool): Whether a heater treater is used in the process. - temperature_heater_treater (Quantity): Temperature of the heater treater. - heat_loss (Quantity): Heat loss in the process. - prime_mover_type (str): Type of prime mover used for energy consumption. - eta_gas (Quantity): Efficiency of natural gas engine. - eta_electricity (Quantity): Efficiency of electricity. - oil_path (str): The oil path in the process. - oil_path_dict (dict): Dictionary mapping oil paths to their descriptions. + A subclass of the Process class that represents a crude oil dewatering process + in an oil and gas production system. + + Attributes: + field (Field): The field associated with the dewatering process. + heater_treater (bool): Whether a heater treater is used in the process. + temperature_heater_treater (Quantity): Temperature of the heater treater. + heat_loss (Quantity): Heat loss in the process. + prime_mover_type (str): Type of prime mover used for energy consumption. + eta_gas (Quantity): Efficiency of natural gas engine. + eta_electricity (Quantity): Efficiency of electricity. + oil_path (str): The oil path in the process. + oil_path_dict (dict): Dictionary mapping oil paths to their descriptions. """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -40,20 +41,19 @@ def __init__(self, name, **kwargs): # TODO: avoid process names in contents. Can all the subsequent processes just # look for "oil"? Then we'd have a single output stream. - self.oil_path_dict = {"Stabilization": "oil for stabilization", - "Storage": "oil for storage", - "Upgrading": "oil for upgrading", - "Dilution": "oil for dilution", - "Dilution and Upgrading": "oil for dilution"} + self.oil_path_dict = { + "Stabilization": "oil for stabilization", + "Storage": "oil for storage", + "Upgrading": "oil for upgrading", + "Dilution": "oil for dilution", + "Dilution and Upgrading": "oil for dilution", + } self._required_inputs = [ "oil", ] - self._required_outputs = [ - "water", - self.oil_path_dict[self.field.oil_path] - ] + self._required_outputs = ["water", self.oil_path_dict[self.field.oil_path]] self.eta_electricity = None self.eta_gas = None @@ -88,8 +88,10 @@ def run(self, analysis): try: output = self.oil_path_dict[self.oil_path] except: - raise OpgeeException(f"{self.name} oil path is not recognized:{self.oil_path}." - f"Must be one of {list(self.oil_path_dict.keys())}") + raise OpgeeException( + f"{self.name} oil path is not recognized:{self.oil_path}." + f"Must be one of {list(self.oil_path_dict.keys())}" + ) output_oil = self.find_output_stream(output) output_tp = TemperaturePressure(temp, input_P) @@ -108,13 +110,15 @@ def run(self, analysis): water_heat_capacity = self.field.water.specific_heat(average_oil_temp) delta_temp = abs(input_T - temp) eff = (1 + self.heat_loss.to("frac")).to("frac") - heat_duty = ((oil_rate * oil_heat_capacity + water_rate * water_heat_capacity) * - delta_temp * eff).to("mmBtu/day") + heat_duty = ((oil_rate * oil_heat_capacity + water_rate * water_heat_capacity) * delta_temp * eff).to( + "mmBtu/day" + ) # energy_use energy_use = self.energy - energy_consumption = \ + energy_consumption = ( heat_duty / self.eta_gas if self.prime_mover_type == "NG_engine" else heat_duty / self.eta_electricity + ) energy_carrier = get_energy_carrier(self.prime_mover_type) energy_use.set_rate(energy_carrier, energy_consumption) diff --git a/opgee/processes/crude_oil_stabilization.py b/opgee/processes/crude_oil_stabilization.py index df40e1ef..4beb9233 100644 --- a/opgee/processes/crude_oil_stabilization.py +++ b/opgee/processes/crude_oil_stabilization.py @@ -16,23 +16,25 @@ _logger = getLogger(__name__) + class CrudeOilStabilization(Process): """ - CrudeOilStabilization is a subclass of the Process class that represents a crude oil stabilization process in an oil and gas production system. - This class handles the stabilization of oil by removing gas, managing energy use, and calculating emissions associated - with the stabilization process. - - Attributes: - field (Field): The field associated with the stabilization process. - stab_tp (TemperaturePressure): The temperature and pressure of the stabilizer column. - mol_per_scf (float): The number of moles per standard cubic feet. - stab_gas_press (Quantity): The pressure of the stabilized gas. - eps_stab (Quantity): Stabilization heat duty multiplier. - eta_gas (Quantity): Efficiency of natural gas engine. - eta_electricity (Quantity): Efficiency of electricity. - prime_mover_type (str): Type of prime mover used for energy consumption. - eta_compressor (Quantity): Efficiency of the compressor. - """ + CrudeOilStabilization is a subclass of the Process class that represents a crude oil stabilization process in an oil and gas production system. + This class handles the stabilization of oil by removing gas, managing energy use, and calculating emissions associated + with the stabilization process. + + Attributes: + field (Field): The field associated with the stabilization process. + stab_tp (TemperaturePressure): The temperature and pressure of the stabilizer column. + mol_per_scf (float): The number of moles per standard cubic feet. + stab_gas_press (Quantity): The pressure of the stabilized gas. + eps_stab (Quantity): Stabilization heat duty multiplier. + eta_gas (Quantity): Efficiency of natural gas engine. + eta_electricity (Quantity): Efficiency of electricity. + prime_mover_type (str): Type of prime mover used for energy consumption. + eta_compressor (Quantity): Efficiency of the compressor. + """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -59,8 +61,7 @@ def __init__(self, name, **kwargs): def cache_attributes(self): field = self.field - self.stab_tp = TemperaturePressure(self.attr("stabilizer_column_temp"), - self.attr("stabilizer_column_press")) + self.stab_tp = TemperaturePressure(self.attr("stabilizer_column_temp"), self.attr("stabilizer_column_press")) self.mol_per_scf = field.model.const("mol-per-scf") self.stab_gas_press = field.stab_gas_press self.eps_stab = self.attr("eps_stab") @@ -84,19 +85,10 @@ def run(self, analysis): oil_specific_heat = oil.specific_heat(input.API, average_temp) stream = Stream("out_stream", self.stab_tp) oil_SG = oil.specific_gravity(input.API) - solution_GOR_inlet = oil.solution_gas_oil_ratio(input, - oil_SG, - oil.gas_specific_gravity, - oil.gas_oil_ratio) - solution_GOR_outlet = oil.solution_gas_oil_ratio(stream, - oil_SG, - oil.gas_specific_gravity, - oil.gas_oil_ratio) + solution_GOR_inlet = oil.solution_gas_oil_ratio(input, oil_SG, oil.gas_specific_gravity, oil.gas_oil_ratio) + solution_GOR_outlet = oil.solution_gas_oil_ratio(stream, oil_SG, oil.gas_specific_gravity, oil.gas_oil_ratio) oil_mass_rate = input.flow_rate("oil", PHASE_LIQUID) - oil_density = oil.density(input, - oil_SG, - oil.gas_specific_gravity, - oil.gas_oil_ratio) + oil_density = oil.density(input, oil_SG, oil.gas_specific_gravity, oil.gas_oil_ratio) gas_removed_by_stabilizer = oil_mass_rate * (solution_GOR_inlet - solution_GOR_outlet) / oil_density gas_removed_molar_rate = gas_removed_by_stabilizer * self.mol_per_scf * oil.gas_comp # Pandas Series gas_removed_mass_rate = oil.component_MW[gas_removed_molar_rate.index] * gas_removed_molar_rate @@ -123,16 +115,20 @@ def run(self, analysis): energy_use = self.energy energy_carrier = get_energy_carrier(self.prime_mover_type) - energy_consumption = heat_duty / self.eta_gas if self.prime_mover_type == "NG_engine" else heat_duty / self.eta_electricity + energy_consumption = ( + heat_duty / self.eta_gas if self.prime_mover_type == "NG_engine" else heat_duty / self.eta_electricity + ) # boosting compressor for stabilizer overall_compression_ratio = self.stab_gas_press / input.tp.P - compressor_energy, _, _ = Compressor.get_compressor_energy_consumption(field, - self.prime_mover_type, - self.eta_compressor, - overall_compression_ratio, - output_stab_gas, - inlet_tp=input.tp) + compressor_energy, _, _ = Compressor.get_compressor_energy_consumption( + field, + self.prime_mover_type, + self.eta_compressor, + overall_compression_ratio, + output_stab_gas, + inlet_tp=input.tp, + ) energy_consumption += compressor_energy energy_use.set_rate(energy_carrier, energy_consumption.to("mmBtu/day")) diff --git a/opgee/processes/crude_oil_storage.py b/opgee/processes/crude_oil_storage.py index 0911e92b..49bcb84d 100644 --- a/opgee/processes/crude_oil_storage.py +++ b/opgee/processes/crude_oil_storage.py @@ -18,30 +18,31 @@ class CrudeOilStorage(Process): """ - A process that represents the storage of crude oil in a field. - - This process takes crude oil as an input and produces three output streams: - - gas for partition: gas that is flared - - gas for VRU: gas that is sent to a vapor recovery unit - - oil: crude oil that is transported out of the storage facility - - The process calculates the mass rate of crude oil input, as well as the amount of gas that is exsolved upon flashing. - It then calculates the rates of gas that are sent to the flare, vapor recovery unit, and fugitives, based on the - exsolved gas and user-defined factors. Finally, it calculates the mass rate of crude oil that is transported out of - the storage facility, and sets the output streams accordingly. - - This process does not use any energy, and only produces emissions from the gas fugitives stream. - - Attributes: - field: The `Field` object that this process belongs to. - oil: The `Oil` object representing the type of crude oil being stored. - oil_sands_mine: A string representing the name of the oil sands mine, or "None" if there is no mine. - API: The API gravity of the crude oil being stored. - storage_gas_comp: The composition of the storage gas. - CH4_comp: The methane component of the storage gas composition. - f_FG_CS_VRU: The fraction of exsolved gas that is sent to the vapor recovery unit. - f_FG_CS_FL: The fraction of exsolved gas that is flared. + A process that represents the storage of crude oil in a field. + + This process takes crude oil as an input and produces three output streams: + - gas for partition: gas that is flared + - gas for VRU: gas that is sent to a vapor recovery unit + - oil: crude oil that is transported out of the storage facility + + The process calculates the mass rate of crude oil input, as well as the amount of gas that is exsolved upon flashing. + It then calculates the rates of gas that are sent to the flare, vapor recovery unit, and fugitives, based on the + exsolved gas and user-defined factors. Finally, it calculates the mass rate of crude oil that is transported out of + the storage facility, and sets the output streams accordingly. + + This process does not use any energy, and only produces emissions from the gas fugitives stream. + + Attributes: + field: The `Field` object that this process belongs to. + oil: The `Oil` object representing the type of crude oil being stored. + oil_sands_mine: A string representing the name of the oil sands mine, or "None" if there is no mine. + API: The API gravity of the crude oil being stored. + storage_gas_comp: The composition of the storage gas. + CH4_comp: The methane component of the storage gas composition. + f_FG_CS_VRU: The fraction of exsolved gas that is sent to the vapor recovery unit. + f_FG_CS_FL: The fraction of exsolved gas that is flared. """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -87,8 +88,11 @@ def run(self, analysis): loss_rate = field.component_fugitive_table[self.name] loss_rate = ureg.Quantity(loss_rate.m, "kg/bbl_oil") oil_volume_rate = oil_mass_rate / (field.oil.specific_gravity(input_stream.API) * field.water.density()) - gas_exsolved_upon_flashing = oil_volume_rate * loss_rate / self.CH4_comp \ - if self.oil_sands_mine == "None" else ureg.Quantity(0, "tonne/day") + gas_exsolved_upon_flashing = ( + oil_volume_rate * loss_rate / self.CH4_comp + if self.oil_sands_mine == "None" + else ureg.Quantity(0, "tonne/day") + ) # Calculate vapor to flare, VRU, and gas fugitives temp = gas_exsolved_upon_flashing * self.storage_gas_comp @@ -111,18 +115,21 @@ def run(self, analysis): gas_fugitive_stream.set_tp(stp) output_transport = self.find_output_stream("oil") - oil_to_transport_mass_rate = (oil_mass_rate - - output_VRU.total_gas_rate() - - output_flare.total_gas_rate() - - gas_fugitive_stream.total_gas_rate()) + oil_to_transport_mass_rate = ( + oil_mass_rate + - output_VRU.total_gas_rate() + - output_flare.total_gas_rate() + - gas_fugitive_stream.total_gas_rate() + ) output_transport.set_liquid_flow_rate("oil", oil_to_transport_mass_rate, tp=stp) output_transport.set_API(input_stream.API) - iteration_value =\ - output_flare.total_flow_rate() +\ - output_VRU.total_flow_rate() +\ - gas_fugitive_stream.total_flow_rate() +\ - output_transport.total_flow_rate() + iteration_value = ( + output_flare.total_flow_rate() + + output_VRU.total_flow_rate() + + gas_fugitive_stream.total_flow_rate() + + output_transport.total_flow_rate() + ) self.set_iteration_value(iteration_value) # No energy-use for storage diff --git a/opgee/processes/crude_oil_transport.py b/opgee/processes/crude_oil_transport.py index 6319216c..fc97a4a7 100644 --- a/opgee/processes/crude_oil_transport.py +++ b/opgee/processes/crude_oil_transport.py @@ -13,21 +13,18 @@ _logger = getLogger(__name__) + class CrudeOilTransport(Process): """ Crude oil transport calculate emissions from crude oil to the market """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) - self._required_inputs = [ - "oil" - ] - - self._required_outputs = [ - "oil" - ] + self._required_inputs = ["oil"] + self._required_outputs = ["oil"] self.transport_share_fuel = self.model.transport_share_fuel.loc["Crude"] self.transport_parameter = self.model.transport_parameter[["Crude", "Units"]] @@ -64,12 +61,14 @@ def run(self, analysis): output.copy_flow_rates_from(input_oil) # energy use - fuel_consumption = field.transport_energy.get_transport_energy_dict(self.field, - self.transport_parameter, - self.transport_share_fuel, - self.transport_by_mode, - oil_LHV_rate, - "Crude") + fuel_consumption = field.transport_energy.get_transport_energy_dict( + self.field, + self.transport_parameter, + self.transport_share_fuel, + self.transport_by_mode, + oil_LHV_rate, + "Crude", + ) energy_use = self.energy for name, value in fuel_consumption.items(): energy_use.set_rate(get_energy_carrier(name), value.to("mmBtu/day")) diff --git a/opgee/processes/demethanizer.py b/opgee/processes/demethanizer.py index 191ecbfd..e8d3160a 100644 --- a/opgee/processes/demethanizer.py +++ b/opgee/processes/demethanizer.py @@ -60,15 +60,13 @@ class Demethanizer(Process): run(analysis) Simulates the Demethanizer process to separate the incoming gas stream into a methane-rich stream and a heavier hydrocarbon stream. - """ + """ def __init__(self, name, **kwargs): super().__init__(name, **kwargs) # TODO: avoid process names in contents. - self._required_inputs = [ - "gas for demethanizer" - ] + self._required_inputs = ["gas for demethanizer"] self._required_outputs = [ "gas for gas partition", @@ -103,9 +101,9 @@ def cache_attributes(self): self.air_cooler_delta_T = self.attr("air_cooler_delta_T") self.air_cooler_fan_eff = self.attr("air_cooler_fan_eff") self.air_cooler_press_drop = self.attr("air_cooler_press_drop") - self.water_press = (field.water.density() * - self.air_cooler_press_drop * - field.model.const("gravitational-acceleration")) + self.water_press = ( + field.water.density() * self.air_cooler_press_drop * field.model.const("gravitational-acceleration") + ) self.eta_compressor = self.attr("eta_compressor") self.prime_mover_type = self.attr("prime_mover_type") @@ -116,7 +114,7 @@ def run(self, analysis): # mass rate input = self.find_input_stream("gas for demethanizer") processing_unit_loss_rate_df = field.get_process_data("processing_unit_loss_rate_df") - if input.is_uninitialized() or processing_unit_loss_rate_df is None: + if input.is_uninitialized() or processing_unit_loss_rate_df is None: return loss_rate = processing_unit_loss_rate_df.T[self.name].values[0] @@ -124,17 +122,20 @@ def run(self, analysis): # Demethanizer modeling based on Aspen HYSYS # Input values for variable getting from HYSYS - variable_bound_dict = {"feed_gas_press": [600.0, 1000.0], # unit in psia - "column_press": [155.0, 325.0], # unit in psia - "methane_to_LPG_ratio": [0.01, 0.05], - "inlet_C1_mol_frac": [0.50, 0.95], - "inlet_C2_mol_frac": [0.05, 0.95]} - feed_gas_press =\ - get_bounded_value(self.feed_press_demethanizer.to("psia").m, "feed_gas_press", variable_bound_dict) - column_press =\ - get_bounded_value(self.column_pressure.to("psia").m, "column_press", variable_bound_dict) - methane_to_LPG_ratio =\ - get_bounded_value(self.methane_to_LPG_ratio.to("frac").m, "methane_to_LPG_ratio", variable_bound_dict) + variable_bound_dict = { + "feed_gas_press": [600.0, 1000.0], # unit in psia + "column_press": [155.0, 325.0], # unit in psia + "methane_to_LPG_ratio": [0.01, 0.05], + "inlet_C1_mol_frac": [0.50, 0.95], + "inlet_C2_mol_frac": [0.05, 0.95], + } + feed_gas_press = get_bounded_value( + self.feed_press_demethanizer.to("psia").m, "feed_gas_press", variable_bound_dict + ) + column_press = get_bounded_value(self.column_pressure.to("psia").m, "column_press", variable_bound_dict) + methane_to_LPG_ratio = get_bounded_value( + self.methane_to_LPG_ratio.to("frac").m, "methane_to_LPG_ratio", variable_bound_dict + ) feed_gas_mol_frac = self.gas.component_molar_fractions(input) @@ -142,15 +143,17 @@ def run(self, analysis): _logger.warning("Feed gas does not contain C1") inlet_C1_mol_frac = 0 else: - inlet_C1_mol_frac =\ - get_bounded_value(feed_gas_mol_frac["C1"].to("frac").m, "inlet_C1_mol_frac", variable_bound_dict) + inlet_C1_mol_frac = get_bounded_value( + feed_gas_mol_frac["C1"].to("frac").m, "inlet_C1_mol_frac", variable_bound_dict + ) if "C2" not in feed_gas_mol_frac.index: _logger.warning("Feed gas does not contain C2") inlet_C2_mol_frac = 0 else: - inlet_C2_mol_frac =\ - get_bounded_value(feed_gas_mol_frac["C2"].to("frac").m, "inlet_C2_mol_frac", variable_bound_dict) + inlet_C2_mol_frac = get_bounded_value( + feed_gas_mol_frac["C2"].to("frac").m, "inlet_C2_mol_frac", variable_bound_dict + ) gas_volume_rate = self.gas.volume_flow_rate_STP(input) @@ -164,27 +167,37 @@ def run(self, analysis): x4 = inlet_C1_mol_frac x5 = inlet_C2_mol_frac corr_result_df = run_corr_eqns(x1, x2, x3, x4, x5, self.demethanizer_tbl) - reboiler_heavy_duty = ureg.Quantity(max(0., corr_result_df.loc["Reboiler", :].sum() * scale_value), "kW") - cooler_thermal_load = ureg.Quantity(max(0., corr_result_df.loc["HEX", :].sum() * scale_value), "kW") + reboiler_heavy_duty = ureg.Quantity(max(0.0, corr_result_df.loc["Reboiler", :].sum() * scale_value), "kW") + cooler_thermal_load = ureg.Quantity(max(0.0, corr_result_df.loc["HEX", :].sum() * scale_value), "kW") NGL_label = ["NGL C1", "NGL C2", "NGL C3", "NGL C4"] fuel_gas_label = ["fuel gas C1", "fuel gas C2", "fuel gas C3", "fuel gas C4"] hydrocarbon_label = ["C1", "C2", "C3", "C4"] - NGL_mol_frac = pd.Series({name: max(0, corr_result_df.loc[tbl_name, :].sum()) for name, tbl_name in - zip(hydrocarbon_label, NGL_label)}, - dtype="pint[frac]") - fuel_gas_mol_frac = pd.Series({name: max(0, corr_result_df.loc[tbl_name, :].sum()) for name, tbl_name in - zip(hydrocarbon_label, fuel_gas_label)}, - dtype="pint[frac]") + NGL_mol_frac = pd.Series( + { + name: max(0, corr_result_df.loc[tbl_name, :].sum()) + for name, tbl_name in zip(hydrocarbon_label, NGL_label) + }, + dtype="pint[frac]", + ) + fuel_gas_mol_frac = pd.Series( + { + name: max(0, corr_result_df.loc[tbl_name, :].sum()) + for name, tbl_name in zip(hydrocarbon_label, fuel_gas_label) + }, + dtype="pint[frac]", + ) gas_volume_rates = field.gas.volume_flow_rates_STP(input) - if NGL_mol_frac["C2"].m == 0 or ( - fuel_gas_mol_frac["C1"] - NGL_mol_frac["C1"] * fuel_gas_mol_frac["C2"] / NGL_mol_frac["C2"]).m == 0: + if ( + NGL_mol_frac["C2"].m == 0 + or (fuel_gas_mol_frac["C1"] - NGL_mol_frac["C1"] * fuel_gas_mol_frac["C2"] / NGL_mol_frac["C2"]).m == 0 + ): fuel_gas_prod = ureg.Quantity(0, "mole/day") else: - fuel_gas_prod = \ - (gas_volume_rates["C1"] - NGL_mol_frac["C1"] / NGL_mol_frac["C2"] * gas_volume_rates["C2"]) / \ - (fuel_gas_mol_frac["C1"] - NGL_mol_frac["C1"] * fuel_gas_mol_frac["C2"] / NGL_mol_frac["C2"]) + fuel_gas_prod = ( + gas_volume_rates["C1"] - NGL_mol_frac["C1"] / NGL_mol_frac["C2"] * gas_volume_rates["C2"] + ) / (fuel_gas_mol_frac["C1"] - NGL_mol_frac["C1"] * fuel_gas_mol_frac["C2"] / NGL_mol_frac["C2"]) reboiler_fuel_use = reboiler_heavy_duty * self.eta_reboiler_demethanizer cooler_energy_consumption = predict_blower_energy_use(self, cooler_thermal_load) @@ -209,31 +222,30 @@ def run(self, analysis): input_tp = input.tp # inlet boosting compressor - inlet_compressor_energy_consump, _, _ = \ - Compressor.get_compressor_energy_consumption(field, - self.prime_mover_type, - self.eta_compressor, - self.feed_press_demethanizer / input_tp.P, - input, - inlet_tp=input.tp) + inlet_compressor_energy_consump, _, _ = Compressor.get_compressor_energy_consumption( + field, + self.prime_mover_type, + self.eta_compressor, + self.feed_press_demethanizer / input_tp.P, + input, + inlet_tp=input.tp, + ) # outlet compressor fuel_gas_exit_press = ureg.Quantity(corr_result_df.loc["fuel gas pressure", :].sum(), "psia") fuel_gas_stream = Stream("fuel_gas", tp=TemperaturePressure(input_tp.T, fuel_gas_exit_press)) fuel_gas_stream.set_rates_from_series(fuel_gas_mass, PHASE_GAS, upper_bound_stream=input) - outlet_compressor_energy_consump, _, _ = \ - Compressor.get_compressor_energy_consumption(field, - self.prime_mover_type, - self.eta_compressor, - input_tp.P / fuel_gas_exit_press, - fuel_gas_stream) + outlet_compressor_energy_consump, _, _ = Compressor.get_compressor_energy_consumption( + field, self.prime_mover_type, self.eta_compressor, input_tp.P / fuel_gas_exit_press, fuel_gas_stream + ) # energy-use energy_use = self.energy energy_carrier = get_energy_carrier(self.prime_mover_type) - energy_use.set_rate(energy_carrier, - inlet_compressor_energy_consump + outlet_compressor_energy_consump + reboiler_fuel_use) + energy_use.set_rate( + energy_carrier, inlet_compressor_energy_consump + outlet_compressor_energy_consump + reboiler_fuel_use + ) energy_use.set_rate(EN_ELECTRICITY, cooler_energy_consumption) # import/export diff --git a/opgee/processes/downhole_pump.py b/opgee/processes/downhole_pump.py index 805c1911..0e2d97d1 100644 --- a/opgee/processes/downhole_pump.py +++ b/opgee/processes/downhole_pump.py @@ -62,6 +62,7 @@ class DownholePump(Process): impute() Estimates the completion and workover fugitive stream and adjusts the input stream. """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -117,7 +118,7 @@ def run(self, analysis): if input.is_uninitialized(): return - lift_gas = self.find_input_stream('lifting gas', raiseError=None) + lift_gas = self.find_input_stream("lifting gas", raiseError=None) if lift_gas is not None and lift_gas.is_initialized(): input = combine_streams([input, lift_gas]) @@ -143,30 +144,16 @@ def run(self, analysis): energy_carrier = get_energy_carrier(self.prime_mover_type) if not self.gas_lifting: oil_SG = oil.specific_gravity(input.API) - solution_gas_oil_ratio_input = oil.solution_gas_oil_ratio(input, - oil_SG, - oil.gas_specific_gravity, - oil.gas_oil_ratio) - solution_gas_oil_ratio_output = oil.solution_gas_oil_ratio(output, - oil_SG, - oil.gas_specific_gravity, - oil.gas_oil_ratio) - oil_density_input = oil.density(input, - oil_SG, - oil.gas_specific_gravity, - oil.gas_oil_ratio) - oil_density_output = oil.density(output, - oil_SG, - oil.gas_specific_gravity, - oil.gas_oil_ratio) - volume_oil_lifted_input = oil.volume_flow_rate(input, - oil_SG, - oil.gas_specific_gravity, - oil.gas_oil_ratio) - volume_oil_lifted_output = oil.volume_flow_rate(output, - oil_SG, - oil.gas_specific_gravity, - oil.gas_oil_ratio) + solution_gas_oil_ratio_input = oil.solution_gas_oil_ratio( + input, oil_SG, oil.gas_specific_gravity, oil.gas_oil_ratio + ) + solution_gas_oil_ratio_output = oil.solution_gas_oil_ratio( + output, oil_SG, oil.gas_specific_gravity, oil.gas_oil_ratio + ) + oil_density_input = oil.density(input, oil_SG, oil.gas_specific_gravity, oil.gas_oil_ratio) + oil_density_output = oil.density(output, oil_SG, oil.gas_specific_gravity, oil.gas_oil_ratio) + volume_oil_lifted_input = oil.volume_flow_rate(input, oil_SG, oil.gas_specific_gravity, oil.gas_oil_ratio) + volume_oil_lifted_output = oil.volume_flow_rate(output, oil_SG, oil.gas_specific_gravity, oil.gas_oil_ratio) # properties of crude oil (all at average conditions along wellbore, in production tubing) average_solution_GOR = (solution_gas_oil_ratio_input + solution_gas_oil_ratio_output) / 2 @@ -190,22 +177,27 @@ def run(self, analysis): gas_FVF = gas.volume_factor(stream) gas_density = gas.density(stream) volume_free_gas = free_gas * gas_FVF - volume_free_gas_lifted = (volume_free_gas * self.oil_volume_rate) + volume_free_gas_lifted = volume_free_gas * self.oil_volume_rate - total_volume_fluid_lifted = (average_volume_oil_lifted + - volume_water_lifted + - volume_free_gas_lifted) - fluid_velocity = (total_volume_fluid_lifted / (self.prod_tubing_xsection_area * self.num_prod_wells)) + total_volume_fluid_lifted = average_volume_oil_lifted + volume_water_lifted + volume_free_gas_lifted + fluid_velocity = total_volume_fluid_lifted / (self.prod_tubing_xsection_area * self.num_prod_wells) - total_mass_fluid_lifted = (average_oil_density * average_volume_oil_lifted + - water_density * volume_water_lifted + - gas_density * volume_free_gas_lifted) - fluid_lifted_density = (total_mass_fluid_lifted / total_volume_fluid_lifted) + total_mass_fluid_lifted = ( + average_oil_density * average_volume_oil_lifted + + water_density * volume_water_lifted + + gas_density * volume_free_gas_lifted + ) + fluid_lifted_density = total_mass_fluid_lifted / total_volume_fluid_lifted # downhole pump pressure_drop_elev = fluid_lifted_density * self.gravitational_acceleration * self.depth - pressure_drop_fric = (fluid_lifted_density * self.friction_factor * self.depth * fluid_velocity ** 2 / - (2 * self.prod_tubing_diam)) + pressure_drop_fric = ( + fluid_lifted_density + * self.friction_factor + * self.depth + * fluid_velocity**2 + / (2 * self.prod_tubing_diam) + ) pressure_drop_total = pressure_drop_fric + pressure_drop_elev pressure_for_lifting = max(ureg.Quantity(0.0, "psia"), wellhead_P + pressure_drop_total - input.tp.P) liquid_flow_rate_per_well = (average_volume_oil_lifted + volume_water_lifted) / self.num_prod_wells diff --git a/opgee/processes/drilling.py b/opgee/processes/drilling.py index 820f67ec..14578800 100644 --- a/opgee/processes/drilling.py +++ b/opgee/processes/drilling.py @@ -44,6 +44,7 @@ class Drilling(Process): num_wells : int The total number of wells (production + water injection). """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -92,19 +93,23 @@ def run(self, analysis): land_use_intensity = land_use_intensity_df.loc[self.field_development_intensity] stream = Stream("stream_stp", tp=field.stp) - oil_SG = field.oil.oil_specific_gravity boundary_API = field.get_process_data("boundary_API") if boundary_API is not None: oil_SG = field.oil.specific_gravity(boundary_API) stream.set_API(boundary_API) - land_use_emission = \ - (land_use_intensity.sum() * field.oil_volume_rate * field.oil.volume_energy_density( - stream, - oil_SG, - field.oil.gas_specific_gravity, - field.oil.gas_oil_ratio)) if not field.offshore else ureg.Quantity(0, "tonne/day") + land_use_emission = ( + ( + land_use_intensity.sum() + * field.oil_volume_rate + * field.oil.volume_energy_density( + stream, oil_SG, field.oil.gas_specific_gravity, field.oil.gas_oil_ratio + ) + ) + if not field.offshore + else ureg.Quantity(0, "tonne/day") + ) # energy-use energy_use = self.energy @@ -123,7 +128,7 @@ def get_fracture_constant(self): value = self.pressure_gradient_fracturing tbl = self.fracture_consumption_tbl - result = [np.interp(value.m, tbl[col].index, tbl[col].values) for col in ['a', 'b', 'c']] + result = [np.interp(value.m, tbl[col].index, tbl[col].values) for col in ["a", "b", "c"]] return result diff --git a/opgee/processes/exploration.py b/opgee/processes/exploration.py index 44779a10..74c7a8c1 100644 --- a/opgee/processes/exploration.py +++ b/opgee/processes/exploration.py @@ -18,11 +18,12 @@ class Exploration(Process): """ - The Exploration class represents the exploration phase of an oil field project. + The Exploration class represents the exploration phase of an oil field project. - This class calculates the energy consumption and emissions associated with - drilling, surveying, and transporting crude oil during the exploration phase. + This class calculates the energy consumption and emissions associated with + drilling, surveying, and transporting crude oil during the exploration phase. """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -61,10 +62,12 @@ def cache_attributes(self): self.well_size = field.well_size self.well_complexity = field.well_complexity self.eta_rig = field.eta_rig - self.vertical_drill_energy_intensity = \ - (self.vertical_drill_df.loc[self.eta_rig]).loc[self.well_size][self.well_complexity] - self.horizontal_drill_energy_intensity = \ - (self.horizontal_drill_df.loc[self.eta_rig]).loc[self.well_size][self.well_complexity] + self.vertical_drill_energy_intensity = (self.vertical_drill_df.loc[self.eta_rig]).loc[self.well_size][ + self.well_complexity + ] + self.horizontal_drill_energy_intensity = (self.horizontal_drill_df.loc[self.eta_rig]).loc[self.well_size][ + self.well_complexity + ] self.oil_sands_mine = field.oil_sands_mine self.offshore = field.offshore @@ -84,9 +87,10 @@ def cache_attributes(self): self.length_lateral = field.length_lateral self.field_production_lifetime = field.field_production_lifetime - self.drill_fuel_consumption = \ - (self.vertical_drill_energy_intensity * (1 - self.frac_wells_horizontal) * self.depth + - self.horizontal_drill_energy_intensity * self.frac_wells_horizontal * self.length_lateral) * self.num_wells + self.drill_fuel_consumption = ( + self.vertical_drill_energy_intensity * (1 - self.frac_wells_horizontal) * self.depth + + self.horizontal_drill_energy_intensity * self.frac_wells_horizontal * self.length_lateral + ) * self.num_wells self.drill_energy_consumption = field.model.const("diesel-LHV") * self.drill_fuel_consumption def run(self, analysis): @@ -99,20 +103,25 @@ def run(self, analysis): if self.field.get_process_data("crude_LHV") is None: self.field.save_process_data(crude_LHV=oil_mass_energy_density) - ocean_tank_energy_intensity = \ - field.transport_energy.get_ocean_tanker_dest_energy_intensity(self.transport_parameter) + ocean_tank_energy_intensity = field.transport_energy.get_ocean_tanker_dest_energy_intensity( + self.transport_parameter + ) truck_energy_intensity = field.transport_energy.energy_intensity_truck export_LHV = field.get_process_data("exported_prod_LHV") cumulative_export_LHV = export_LHV * self.field_production_lifetime * m.const("days-per-year") - survey_vehicle_energy_consumption = (truck_energy_intensity * self.weight_land_survey * - self.distance_survey if not self.offshore else - ocean_tank_energy_intensity * self.weight_ocean_survey * - self.distance_survey) - - drill_consumption_per_well = (self.drill_energy_consumption / self.num_wells - if self.oil_sands_mine == "None" else ureg.Quantity(0.0, "mmbtu")) + survey_vehicle_energy_consumption = ( + truck_energy_intensity * self.weight_land_survey * self.distance_survey + if not self.offshore + else ocean_tank_energy_intensity * self.weight_ocean_survey * self.distance_survey + ) + + drill_consumption_per_well = ( + self.drill_energy_consumption / self.num_wells + if self.oil_sands_mine == "None" + else ureg.Quantity(0.0, "mmbtu") + ) drill_energy_consumption = drill_consumption_per_well * (self.number_wells_dry + self.number_wells_exploratory) frac_energy_consumption = (survey_vehicle_energy_consumption + drill_energy_consumption) / cumulative_export_LHV diff --git a/opgee/processes/flaring.py b/opgee/processes/flaring.py index 50780eb9..152cd6c8 100644 --- a/opgee/processes/flaring.py +++ b/opgee/processes/flaring.py @@ -13,16 +13,13 @@ _logger = getLogger(__name__) -class Flaring(Process): +class Flaring(Process): def __init__(self, name, **kwargs): super().__init__(name, **kwargs) # TODO: avoid process names in contents. - self._required_inputs = [ - "gas for flaring", - "methane slip" - ] + self._required_inputs = ["gas for flaring", "methane slip"] self._required_outputs = [] diff --git a/opgee/processes/gas_dehydration.py b/opgee/processes/gas_dehydration.py index 45964a4a..af9d0d69 100644 --- a/opgee/processes/gas_dehydration.py +++ b/opgee/processes/gas_dehydration.py @@ -23,35 +23,38 @@ class GasDehydration(Process): """ - This class represents the gas dehydration process in an oil and gas field. - It calculates the energy consumption and emissions related to the gas dehydration process. - - Attributes: - gas_dehydration_tbl (DataFrame): A table containing gas dehydration correlations. - mol_to_scf (float): Constant to convert moles to standard cubic feet. - air_elevation_const (float): Constant used for air elevation correction. - air_density_ratio (float): Constant used for air density ratio calculation. - reflux_ratio (float): Reflux ratio used in the gas dehydration process. - regeneration_feed_temp (float): Regeneration feed temperature used in the gas dehydration process. - eta_reboiler_dehydrator (float): Efficiency of the reboiler in the dehydrator. - air_cooler_delta_T (float): Temperature difference across the air cooler. - air_cooler_press_drop (float): Pressure drop across the air cooler. - air_cooler_fan_eff (float): Efficiency of the air cooler fan. - air_cooler_speed_reducer_eff (float): Efficiency of the air cooler speed reducer. - water_press (float): Pressure of the water in the process. - gas_path (str): The path of the gas in the process. - gas_path_dict (dict): Dictionary mapping gas path names to stream names. + This class represents the gas dehydration process in an oil and gas field. + It calculates the energy consumption and emissions related to the gas dehydration process. + + Attributes: + gas_dehydration_tbl (DataFrame): A table containing gas dehydration correlations. + mol_to_scf (float): Constant to convert moles to standard cubic feet. + air_elevation_const (float): Constant used for air elevation correction. + air_density_ratio (float): Constant used for air density ratio calculation. + reflux_ratio (float): Reflux ratio used in the gas dehydration process. + regeneration_feed_temp (float): Regeneration feed temperature used in the gas dehydration process. + eta_reboiler_dehydrator (float): Efficiency of the reboiler in the dehydrator. + air_cooler_delta_T (float): Temperature difference across the air cooler. + air_cooler_press_drop (float): Pressure drop across the air cooler. + air_cooler_fan_eff (float): Efficiency of the air cooler fan. + air_cooler_speed_reducer_eff (float): Efficiency of the air cooler speed reducer. + water_press (float): Pressure of the water in the process. + gas_path (str): The path of the gas in the process. + gas_path_dict (dict): Dictionary mapping gas path names to stream names. """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) - self.gas_path_dict = {"Minimal": "gas for gas partition", - "Acid Gas": "gas for AGR", - "Acid Wet Gas": "gas for AGR", - "CO2-EOR Membrane": "gas for chiller", - "CO2-EOR Ryan Holmes": "gas for Ryan Holmes", - "Sour Gas Reinjection": "gas for sour gas compressor", - "Wet Gas": "gas for demethanizer"} + self.gas_path_dict = { + "Minimal": "gas for gas partition", + "Acid Gas": "gas for AGR", + "Acid Wet Gas": "gas for AGR", + "CO2-EOR Membrane": "gas for chiller", + "CO2-EOR Ryan Holmes": "gas for Ryan Holmes", + "Sour Gas Reinjection": "gas for sour gas compressor", + "Wet Gas": "gas for demethanizer", + } # TODO: avoid process names in contents. self._required_inputs = [ @@ -91,9 +94,9 @@ def cache_attributes(self): self.air_cooler_fan_eff = self.attr("air_cooler_fan_eff") self.air_cooler_speed_reducer_eff = self.attr("air_cooler_speed_reducer_eff") - self.water_press = field.water.density() * \ - self.air_cooler_press_drop * \ - field.model.const("gravitational-acceleration") + self.water_press = ( + field.water.density() * self.air_cooler_press_drop * field.model.const("gravitational-acceleration") + ) self.gas_path = field.gas_path @@ -113,8 +116,10 @@ def run(self, analysis): try: output = self.gas_path_dict[self.gas_path] except: - raise OpgeeException(f"{self.name} gas path is not recognized:{self.gas_path}. " - f"Must be one of {list(self.gas_path_dict.keys())}") + raise OpgeeException( + f"{self.name} gas path is not recognized:{self.gas_path}. " + f"Must be one of {list(self.gas_path_dict.keys())}" + ) output_gas = self.find_output_stream(output) output_gas.copy_flow_rates_from(input) @@ -144,11 +149,13 @@ def run(self, analysis): # Gas dehydration modeling based on Aspen HYSYS # Input values for variable getting from HYSYS - variable_bound_dict = {"feed_gas_press": [14.7, 1014.7], # unit in psia - "feed_gas_temp": [80.0, 100.0], # unit in degree F - "water_content_volume": [0.0005, 0.005], - "reflux_ratio": [1.5, 3.0], - "regeneration_feed_temp": [190.0, 200.0]} # unit in degree F + variable_bound_dict = { + "feed_gas_press": [14.7, 1014.7], # unit in psia + "feed_gas_temp": [80.0, 100.0], # unit in degree F + "water_content_volume": [0.0005, 0.005], + "reflux_ratio": [1.5, 3.0], + "regeneration_feed_temp": [190.0, 200.0], + } # unit in degree F x1 = get_bounded_value(feed_gas_press.to("psia").m, "feed_gas_press", variable_bound_dict) x2 = get_bounded_value(feed_gas_temp.to("degF").m, "feed_gas_temp", variable_bound_dict) @@ -157,12 +164,12 @@ def run(self, analysis): x5 = get_bounded_value(self.regeneration_feed_temp.to("degF").m, "regeneration_feed_temp", variable_bound_dict) corr_result_df = run_corr_eqns(x1, x2, x3, x4, x5, self.gas_dehydration_tbl) - reboiler_heavy_duty = ureg.Quantity(max(0., corr_result_df["Reboiler"] * gas_multiplier), "kW") + reboiler_heavy_duty = ureg.Quantity(max(0.0, corr_result_df["Reboiler"] * gas_multiplier), "kW") pump_duty = ureg.Quantity(max(0, corr_result_df["Pump"] * gas_multiplier), "kW") - condenser_thermal_load = ureg.Quantity(max(0., corr_result_df["Condenser"] * gas_multiplier), "kW") + condenser_thermal_load = ureg.Quantity(max(0.0, corr_result_df["Condenser"] * gas_multiplier), "kW") # TODO: Add this stream to water treatment process - water_output = ureg.Quantity(max(0., corr_result_df["Resid water"]), "lb/mmscf") * gas_volume_rate + water_output = ureg.Quantity(max(0.0, corr_result_df["Resid water"]), "lb/mmscf") * gas_volume_rate reboiler_fuel_use = reboiler_heavy_duty * self.eta_reboiler_dehydrator air_cooler_energy_consumption = predict_blower_energy_use(self, condenser_thermal_load) @@ -200,11 +207,8 @@ def pseudo_pressure(tau, Tc_over_T, critical_pressure): Tc_over_T = Tc_over_T.m critical_pressure = critical_pressure.m - Pv_over_Pc = np.exp((a1 * tau + - a2 * tau ** 1.5 + - a3 * tau ** 3 + - a4 * tau ** 3.5 + - a5 * tau ** 4 + - a6 * tau ** 7.5) * Tc_over_T) + Pv_over_Pc = np.exp( + (a1 * tau + a2 * tau**1.5 + a3 * tau**3 + a4 * tau**3.5 + a5 * tau**4 + a6 * tau**7.5) * Tc_over_T + ) result = Pv_over_Pc * critical_pressure return ureg.Quantity(result, "Pa") diff --git a/opgee/processes/gas_distribution.py b/opgee/processes/gas_distribution.py index 436f86a5..0fbe3668 100644 --- a/opgee/processes/gas_distribution.py +++ b/opgee/processes/gas_distribution.py @@ -18,13 +18,12 @@ class GasDistribution(Process): """ Gas distribution calculates emission of gas to distribution """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) # TODO: avoid process names in contents. - self._required_inputs = [ - "gas for distribution" - ] + self._required_inputs = ["gas for distribution"] self._required_outputs = [ "gas", @@ -33,9 +32,9 @@ def __init__(self, name, **kwargs): self.cache_attributes() def cache_attributes(self): - self.frac_loss = (self.attr("frac_loss_distribution") + - self.attr("frac_loss_meter") + - self.attr("frac_loss_enduse")) + self.frac_loss = ( + self.attr("frac_loss_distribution") + self.attr("frac_loss_meter") + self.attr("frac_loss_enduse") + ) def run(self, analysis): self.print_running_msg() @@ -61,8 +60,3 @@ def run(self, analysis): # emissions emissions = self.emissions emissions.set_from_stream(EM_FUGITIVES, gas_fugitives) - - - - - diff --git a/opgee/processes/gas_gathering.py b/opgee/processes/gas_gathering.py index 4a0c730a..a4325c87 100644 --- a/opgee/processes/gas_gathering.py +++ b/opgee/processes/gas_gathering.py @@ -21,14 +21,9 @@ def __init__(self, name, **kwargs): super().__init__(name, **kwargs) # TODO: avoid process names in contents. - self._required_inputs = [ - "gas" - ] + self._required_inputs = ["gas"] - self._required_outputs = [ - ("gas for gas dehydration", - "gas for gas partition") - ] + self._required_outputs = [("gas for gas dehydration", "gas for gas partition")] self.site_fugitive_breakdown = self.model.site_fugitive_processing_unit_breakdown @@ -59,7 +54,9 @@ def run(self, analysis): input_stream_STP = Stream("input_stream_STP", tp=field.stp) input_stream_STP.copy_flow_rates_from(input, tp=field.stp) - loss_rate = self.calculate_site_fugitive_loss_rate(input_stream_STP, self.gathering_site_average_site_throughput) + loss_rate = self.calculate_site_fugitive_loss_rate( + input_stream_STP, self.gathering_site_average_site_throughput + ) gas_fugitives = self.set_gas_fugitives(input, loss_rate) output_gas = self.find_output_stream("gas for gas dehydration", raiseError=False) @@ -71,9 +68,10 @@ def run(self, analysis): self.set_iteration_value(output_gas.total_flow_rate()) # Calculate site fugitive loss rate for processing unit breakdown - processing_unit_loss_rate_df =\ - self.site_fugitive_breakdown * \ - self.calculate_site_fugitive_loss_rate(input_stream_STP, self.processing_plant_average_site_throughput).m + processing_unit_loss_rate_df = ( + self.site_fugitive_breakdown + * self.calculate_site_fugitive_loss_rate(input_stream_STP, self.processing_plant_average_site_throughput).m + ) field.save_process_data(processing_unit_loss_rate_df=processing_unit_loss_rate_df) # emissions @@ -81,17 +79,15 @@ def run(self, analysis): emissions.set_from_stream(EM_FUGITIVES, gas_fugitives) def calculate_site_fugitive_loss_rate(self, input_stream, site_throughput): - input_mass_rate = input_stream.total_flow_rate() input_volume_rate = self.field.gas.volume_flow_rate(input_stream) num_of_sites = input_volume_rate / site_throughput mass_rate_per_site = (input_mass_rate / num_of_sites).to("tonne/hr").m mass_rate_per_site = math.log10(mass_rate_per_site) - loss_rate = \ - ureg.Quantity(10 ** (self.site_fugitive_intercept.to("frac").m + self.site_fugitive_slope.to( - "frac").m * mass_rate_per_site), "frac") + loss_rate = ureg.Quantity( + 10 + ** (self.site_fugitive_intercept.to("frac").m + self.site_fugitive_slope.to("frac").m * mass_rate_per_site), + "frac", + ) return loss_rate - - - diff --git a/opgee/processes/gas_lifting_compressor.py b/opgee/processes/gas_lifting_compressor.py index 68ad4200..0cb9b7ab 100644 --- a/opgee/processes/gas_lifting_compressor.py +++ b/opgee/processes/gas_lifting_compressor.py @@ -21,13 +21,9 @@ def __init__(self, name, **kwargs): super().__init__(name, **kwargs) # TODO: avoid process names in contents. - self._required_inputs = [ - "lifting gas" - ] + self._required_inputs = ["lifting gas"] - self._required_outputs = [ - "lifting gas" - ] + self._required_outputs = ["lifting gas"] self.res_press = None self.prime_mover_type = None @@ -67,12 +63,9 @@ def run(self, analysis): input_tp = input.tp discharge_press = (self.res_press + input_tp.P) / 2 + ureg.Quantity(100.0, "psia") overall_compression_ratio = discharge_press / input_tp.P - energy_consumption, output_temp, _ = \ - Compressor.get_compressor_energy_consumption(field, - self.prime_mover_type, - self.eta_compressor, - overall_compression_ratio, - input) + energy_consumption, output_temp, _ = Compressor.get_compressor_energy_consumption( + field, self.prime_mover_type, self.eta_compressor, overall_compression_ratio, input + ) lifting_gas.tp.set(T=output_temp, P=discharge_press) diff --git a/opgee/processes/gas_partition.py b/opgee/processes/gas_partition.py index c42a486e..230a9f81 100644 --- a/opgee/processes/gas_partition.py +++ b/opgee/processes/gas_partition.py @@ -26,6 +26,7 @@ class GasPartition(Process): """ Gas partition is to check the reasonable amount of gas goes to gas lifting and gas reinjection """ + iteration_tolerance = 0.000001 def __init__(self, name, **kwargs): @@ -33,9 +34,7 @@ def __init__(self, name, **kwargs): field = self.field # TODO: avoid process names in contents. - self._required_inputs = [ - "gas for gas partition" - ] + self._required_inputs = ["gas for gas partition"] self._required_outputs = [ "exported gas", @@ -91,15 +90,9 @@ def cache_attributes(self): self.WOR = field.WOR self.flood_gas_type = field.flood_gas_type - self.N2_flooding_tp = TemperaturePressure( - self.attr("N2_flooding_temp"), self.attr("N2_flooding_press") - ) - self.C1_flooding_tp = TemperaturePressure( - self.attr("C1_flooding_temp"), self.attr("C1_flooding_press") - ) - self.CO2_flooding_tp = TemperaturePressure( - self.attr("CO2_flooding_temp"), self.attr("CO2_flooding_press") - ) + self.N2_flooding_tp = TemperaturePressure(self.attr("N2_flooding_temp"), self.attr("N2_flooding_press")) + self.C1_flooding_tp = TemperaturePressure(self.attr("C1_flooding_temp"), self.attr("C1_flooding_press")) + self.CO2_flooding_tp = TemperaturePressure(self.attr("CO2_flooding_temp"), self.attr("CO2_flooding_press")) self.GFIR = field.GFIR self.gas_flooding_vol_rate = self.oil_volume_rate * self.GFIR @@ -127,15 +120,11 @@ def run(self, analysis): if self.gas_lifting: lifting_gas_to_compressor = self.find_output_stream("lifting gas") if self.is_first_loop: - init_stream = get_init_lifting_stream( - self.field.gas, input, gas_lifting_vol_rate - ) + init_stream = get_init_lifting_stream(self.field.gas, input, gas_lifting_vol_rate) lifting_gas_to_compressor.copy_flow_rates_from(init_stream) self.is_first_loop = False - iteration_series = ( - lifting_gas_to_compressor.components.gas - input.components.gas - ).astype(float) + iteration_series = (lifting_gas_to_compressor.components.gas - input.components.gas).astype(float) iteration_series[iteration_series < 0] = 0 if sum(iteration_series) >= self.iteration_tolerance: @@ -143,26 +132,18 @@ def run(self, analysis): lifting_gas_to_compressor.copy_flow_rates_from(input) return - exported_gas_stream.subtract_rates_from( - lifting_gas_to_compressor, PHASE_GAS - ) + exported_gas_stream.subtract_rates_from(lifting_gas_to_compressor, PHASE_GAS) if self.gas_flooding and not self.is_gas_flooding_visited: reinjected_gas_stream = Stream("reinjected_gas_stream", tp=field.stp) - self.gas_flooding_setup( - import_product, reinjected_gas_stream, exported_gas_stream - ) + self.gas_flooding_setup(import_product, reinjected_gas_stream, exported_gas_stream) field.save_process_data(gas_flooding_stream=reinjected_gas_stream) self.is_gas_flooding_visited = True if self.natural_gas_reinjection: reinjected_HC_stream = Stream("reinjected_HC_stream", tp=field.stp) - NG_energy_flow_rate_needed = field.import_export.import_df[ - EN_NATURAL_GAS - ].sum() - reinjected_gas_energy_flow_rate = field.gas.energy_flow_rate( - exported_gas_stream - ) + NG_energy_flow_rate_needed = field.import_export.import_df[EN_NATURAL_GAS].sum() + reinjected_gas_energy_flow_rate = field.gas.energy_flow_rate(exported_gas_stream) if reinjected_gas_energy_flow_rate <= NG_energy_flow_rate_needed: reinjected_HC_stream.set_tp(exported_gas_stream.tp) exported_gas_stream.reset() @@ -170,16 +151,12 @@ def run(self, analysis): else: fuel_stream = Stream("fuel_stream", tp=exported_gas_stream.tp) fuel_stream.copy_flow_rates_from(exported_gas_stream) - fuel_fraction = ( - NG_energy_flow_rate_needed / reinjected_gas_energy_flow_rate - ) + fuel_fraction = NG_energy_flow_rate_needed / reinjected_gas_energy_flow_rate fuel_stream.multiply_flow_rates(fuel_fraction) reinjected_HC_stream.copy_flow_rates_from(exported_gas_stream) reinjected_HC_stream.subtract_rates_from(fuel_stream) - reinjected_HC_stream.multiply_flow_rates( - self.fraction_remaining_gas_inj - ) + reinjected_HC_stream.multiply_flow_rates(self.fraction_remaining_gas_inj) exported_gas_stream.subtract_rates_from(reinjected_HC_stream) exported_gas_stream.subtract_rates_from(fuel_stream) @@ -188,15 +165,11 @@ def run(self, analysis): combined_gas_stream = reinjected_HC_stream if field.get_process_data("gas_flooding_stream") is not None: gas_flooding_stream = field.get_process_data("gas_flooding_stream") - combined_gas_stream = combine_streams( - [gas_flooding_stream, reinjected_HC_stream] - ) + combined_gas_stream = combine_streams([gas_flooding_stream, reinjected_HC_stream]) gas_to_reinjection.copy_flow_rates_from(combined_gas_stream) field.save_process_data( - NG_energy_rate_consumption=min( - NG_energy_flow_rate_needed, reinjected_gas_energy_flow_rate - ) + NG_energy_rate_consumption=min(NG_energy_flow_rate_needed, reinjected_gas_energy_flow_rate) ) exported_gas = self.find_output_stream("exported gas") @@ -235,49 +208,31 @@ def gas_flooding_setup(self, import_product, reinjected_gas_stream, exported_gas known_types = ["N2", "NG", "CO2"] if self.flood_gas_type not in known_types: - raise OpgeeException( - f"{self.flood_gas_type} is not in the known gas type: {known_types}" - ) + raise OpgeeException(f"{self.flood_gas_type} is not in the known gas type: {known_types}") if self.flood_gas_type == "N2": - N2_mass_rate = ( - self.gas_flooding_vol_rate * field.gas.component_gas_rho_STP["N2"] - ) + N2_mass_rate = self.gas_flooding_vol_rate * field.gas.component_gas_rho_STP["N2"] reinjected_gas_stream.set_gas_flow_rate("N2", N2_mass_rate) reinjected_gas_stream.set_tp(self.N2_flooding_tp) - field.save_process_data( - N2_reinjection_volume_rate=self.gas_flooding_vol_rate - ) + field.save_process_data(N2_reinjection_volume_rate=self.gas_flooding_vol_rate) import_product.set_import(self.name, N2, N2_mass_rate) elif self.flood_gas_type == "CO2": - CO2_mass_rate = ( - self.gas_flooding_vol_rate * field.gas.component_gas_rho_STP["CO2"] - ) + CO2_mass_rate = self.gas_flooding_vol_rate * field.gas.component_gas_rho_STP["CO2"] if field.get_process_data("CO2_flooding_rate_init") is None: field.save_process_data(CO2_flooding_rate_init=CO2_mass_rate) prod_CO2_mass_rate = exported_gas_stream.gas_flow_rate("CO2") - CO2_mass_rate = max( - ureg.Quantity(0, "tonne/day"), CO2_mass_rate - prod_CO2_mass_rate - ) + CO2_mass_rate = max(ureg.Quantity(0, "tonne/day"), CO2_mass_rate - prod_CO2_mass_rate) - impurity_type = ( - "C1" if self.CO2_source == "Natural subsurface reservoir" else "N2" - ) - impurity_rate = ( - self.impurity_CH4_in_CO2 - if impurity_type == "C1" - else self.impurity_N2_in_CO2 - ) + impurity_type = "C1" if self.CO2_source == "Natural subsurface reservoir" else "N2" + impurity_rate = self.impurity_CH4_in_CO2 if impurity_type == "C1" else self.impurity_N2_in_CO2 impurity_mass_rate = CO2_mass_rate * impurity_rate reinjected_gas_stream.set_gas_flow_rate(impurity_type, impurity_mass_rate) reinjected_gas_stream.set_gas_flow_rate("CO2", CO2_mass_rate) reinjected_gas_stream.set_tp(self.CO2_flooding_tp) - import_product.set_import( - self.name, CO2_Flooding, CO2_mass_rate + impurity_mass_rate - ) + import_product.set_import(self.name, CO2_Flooding, CO2_mass_rate + impurity_mass_rate) field.save_process_data(CO2_mass_rate=CO2_mass_rate) else: input_STP = Stream("input_stream_at_STP", tp=STP) @@ -287,30 +242,19 @@ def gas_flooding_setup(self, import_product, reinjected_gas_stream, exported_gas exported_gas_mass_rate = exported_gas_stream.total_gas_rate() input_STP.copy_flow_rates_from(exported_gas_stream, tp=STP) - exported_gas_volume_rate = exported_gas_mass_rate / field.gas.density( - input_STP - ) + exported_gas_volume_rate = exported_gas_mass_rate / field.gas.density(input_STP) NG_flooding_volume_rate = self.gas_flooding_vol_rate # The mass of produced processed NG is enough for NG flooding if NG_flooding_volume_rate < exported_gas_volume_rate: - NG_flooding_mass_rate = NG_flooding_volume_rate * field.gas.density( - input_STP - ) - reinjected_gas_series = ( - NG_flooding_mass_rate - * field.gas.component_mass_fractions( - field.gas.component_molar_fractions(exported_gas_stream) - ) - ) - reinjected_gas_stream.set_rates_from_series( - reinjected_gas_series, PHASE_GAS + NG_flooding_mass_rate = NG_flooding_volume_rate * field.gas.density(input_STP) + reinjected_gas_series = NG_flooding_mass_rate * field.gas.component_mass_fractions( + field.gas.component_molar_fractions(exported_gas_stream) ) + reinjected_gas_stream.set_rates_from_series(reinjected_gas_series, PHASE_GAS) reinjected_gas_stream.set_tp(exported_gas_stream.tp) - exported_gas_stream.subtract_rates_from( - reinjected_gas_stream, PHASE_GAS - ) + exported_gas_stream.subtract_rates_from(reinjected_gas_stream, PHASE_GAS) # The imported NG is need for NG flooding else: @@ -320,9 +264,7 @@ def gas_flooding_setup(self, import_product, reinjected_gas_stream, exported_gas * self.model.const("mol-per-scf") ) imported_NG_series *= field.gas.component_MW[imported_NG_series.index] - imported_NG_stream = Stream( - "imported_NG_stream", tp=self.C1_flooding_tp - ) + imported_NG_stream = Stream("imported_NG_stream", tp=self.C1_flooding_tp) imported_NG_stream.set_rates_from_series(imported_NG_series, PHASE_GAS) imported_NG_energy_rate = field.gas.energy_flow_rate(imported_NG_stream) @@ -331,9 +273,7 @@ def gas_flooding_setup(self, import_product, reinjected_gas_stream, exported_gas reinjected_gas_stream.add_flow_rates_from(exported_gas_stream) exported_gas_stream.reset() exported_gas_stream.set_tp(tp=STP) - import_product.set_import( - self.name, NATURAL_GAS, imported_NG_energy_rate - ) + import_product.set_import(self.name, NATURAL_GAS, imported_NG_energy_rate) gas_to_reinjection = self.find_output_stream("gas") if reinjected_gas_stream.total_flow_rate().m != 0: diff --git a/opgee/processes/gas_reinjection_compressor.py b/opgee/processes/gas_reinjection_compressor.py index 02ba6712..76ec6fe7 100644 --- a/opgee/processes/gas_reinjection_compressor.py +++ b/opgee/processes/gas_reinjection_compressor.py @@ -21,13 +21,9 @@ class GasReinjectionCompressor(Process): def __init__(self, name, **kwargs): super().__init__(name, **kwargs) - self._required_inputs = [ - "gas" - ] + self._required_inputs = ["gas"] - self._required_outputs = [ - "gas" - ] + self._required_outputs = ["gas"] self.air_separation_energy_intensity = None self.eta_compressor = None @@ -70,14 +66,11 @@ def run(self, analysis): gas_to_well.copy_flow_rates_from(input) gas_to_well.subtract_rates_from(gas_fugitives) - discharge_press = self.res_press + ureg.Quantity(500., "psi") + discharge_press = self.res_press + ureg.Quantity(500.0, "psi") overall_compression_ratio = discharge_press / input.tp.P energy_consumption, output_temp, _ = Compressor.get_compressor_energy_consumption( - self.field, - self.prime_mover_type, - self.eta_compressor, - overall_compression_ratio, - input) + self.field, self.prime_mover_type, self.eta_compressor, overall_compression_ratio, input + ) gas_to_well.tp.set(T=output_temp, P=discharge_press) @@ -93,7 +86,6 @@ def run(self, analysis): energy_consump_air_separation = N2_volume_rate * self.air_separation_energy_intensity energy_use.set_rate(EN_ELECTRICITY, energy_consump_air_separation) - # import/export self.set_import_from_energy(energy_use) diff --git a/opgee/processes/gas_reinjection_well.py b/opgee/processes/gas_reinjection_well.py index 2fc4322f..0857b163 100644 --- a/opgee/processes/gas_reinjection_well.py +++ b/opgee/processes/gas_reinjection_well.py @@ -18,9 +18,7 @@ def __init__(self, name, **kwargs): super().__init__(name, **kwargs) # TODO: avoid process names in contents. - self._required_inputs = [ - "gas" - ] + self._required_inputs = ["gas"] self._required_outputs = [ "gas", diff --git a/opgee/processes/heavy_oil_dilution.py b/opgee/processes/heavy_oil_dilution.py index 289134a1..c3d36c14 100644 --- a/opgee/processes/heavy_oil_dilution.py +++ b/opgee/processes/heavy_oil_dilution.py @@ -17,14 +17,9 @@ def __init__(self, name, **kwargs): super().__init__(name, **kwargs) # TODO: avoid process names in contents. - self._required_inputs = [ - "oil for dilution" - ] + self._required_inputs = ["oil for dilution"] - self._required_outputs = [ - ("oil for storage", - "oil for upgrading") - ] + self._required_outputs = [("oil for storage", "oil for upgrading")] self.water_density = self.water.density() @@ -55,8 +50,7 @@ def cache_attributes(self): self.downhole_pump = field.downhole_pump self.oil_sands_mine = field.oil_sands_mine - self.bitumen_tp = TemperaturePressure(field.mined_bitumen_t, - field.mined_bitumen_p) + self.bitumen_tp = TemperaturePressure(field.mined_bitumen_t, field.mined_bitumen_p) self.diluent_API = self.attr("diluent_API") self.dilution_SG = self.oil.specific_gravity(self.diluent_API) @@ -64,12 +58,11 @@ def cache_attributes(self): self.dilbit_SG = self.oil.specific_gravity(self.dilbit_API) self.dilution_type = self.attr("dilution_type") - self.diluent_tp = TemperaturePressure(self.attr("diluent_temp"), - self.attr("diluent_temp")) - self.before_diluent_tp = TemperaturePressure(self.attr("before_diluent_temp"), - self.attr("before_diluent_press")) - self.final_mix_tp = TemperaturePressure(self.attr("final_mix_temp"), - self.attr("final_mix_press")) + self.diluent_tp = TemperaturePressure(self.attr("diluent_temp"), self.attr("diluent_temp")) + self.before_diluent_tp = TemperaturePressure( + self.attr("before_diluent_temp"), self.attr("before_diluent_press") + ) + self.final_mix_tp = TemperaturePressure(self.attr("final_mix_temp"), self.attr("final_mix_press")) def run(self, analysis): self.print_running_msg() @@ -90,8 +83,11 @@ def run(self, analysis): input_liquid_volume_rate = input_liquid_mass_rate / (oil_SG * self.water_density) frac_diluent = self.frac_diluent - expected_volume_oil_bitumen = input_liquid_volume_rate if abs(frac_diluent.to("frac").m - 1) <= 0.01 else \ - input_liquid_volume_rate / (1 - frac_diluent) + expected_volume_oil_bitumen = ( + input_liquid_volume_rate + if abs(frac_diluent.to("frac").m - 1) <= 0.01 + else input_liquid_volume_rate / (1 - frac_diluent) + ) required_volume_diluent = expected_volume_oil_bitumen * frac_diluent if self.dilution_type == DILUENT: @@ -100,8 +96,9 @@ def run(self, analysis): diluent_LHV = field.oil.mass_energy_density(API=self.diluent_API) else: total_mass_diluted_oil = expected_volume_oil_bitumen * self.dilbit_SG * self.water_density - required_mass_dilution = total_mass_diluted_oil if frac_diluent == 1 else \ - max(0, total_mass_diluted_oil - input_liquid_mass_rate) + required_mass_dilution = ( + total_mass_diluted_oil if frac_diluent == 1 else max(0, total_mass_diluted_oil - input_liquid_mass_rate) + ) diluent_SG = required_mass_dilution / required_volume_diluent / self.water_density diluent_LHV = field.oil.mass_energy_density(API=field.oil.API_from_SG(diluent_SG)) @@ -113,20 +110,21 @@ def run(self, analysis): field.save_process_data(final_diluent_LHV_mass=diluent_LHV) - final_diluent_SG = \ - total_mass_diluted_oil / expected_volume_oil_bitumen / self.water_density + final_diluent_SG = total_mass_diluted_oil / expected_volume_oil_bitumen / self.water_density final_diluent_API = field.oil.API_from_SG(final_diluent_SG) output_oil.set_API(final_diluent_API) diluent_energy_rate = required_mass_dilution * diluent_LHV # Calculate imported diluent energy consumption - fuel_consumption = field.transport_energy.get_transport_energy_dict(self.field, - self.transport_parameter, - self.transport_share_fuel, - self.transport_by_mode, - diluent_energy_rate, - DILUENT) + fuel_consumption = field.transport_energy.get_transport_energy_dict( + self.field, + self.transport_parameter, + self.transport_share_fuel, + self.transport_by_mode, + diluent_energy_rate, + DILUENT, + ) energy_use = self.energy for name, value in fuel_consumption.items(): diff --git a/opgee/processes/heavy_oil_upgrading.py b/opgee/processes/heavy_oil_upgrading.py index 6ca7f9ef..4d35efa2 100644 --- a/opgee/processes/heavy_oil_upgrading.py +++ b/opgee/processes/heavy_oil_upgrading.py @@ -26,9 +26,7 @@ def __init__(self, name, **kwargs): super().__init__(name, **kwargs) # TODO: avoid process names in contents. - self._required_inputs = [ - "oil for upgrading" - ] + self._required_inputs = ["oil for upgrading"] self._required_outputs = [ "oil for storage", @@ -62,7 +60,6 @@ def cache_attributes(self): self.oil_sands_mine = field.oil_sands_mine self.upgrader_type = field.upgrader_type - def check_enabled(self): if self.upgrader_type == "None": self.set_enabled(False) @@ -90,9 +87,9 @@ def run(self, analysis): heavy_oil_upgrading_table = df[self.upgrader_type] heavy_oil_upgrading_table.index = df["Items"] - upgrader_process_gas_heating_value = (self.upgrader_gas_comp * - self.oil.component_LHV_molar[self.upgrader_gas_comp.index] * - self.mole_to_scf).sum() + upgrader_process_gas_heating_value = ( + self.upgrader_gas_comp * self.oil.component_LHV_molar[self.upgrader_gas_comp.index] * self.mole_to_scf + ).sum() SCO_bitumen_ratio = heavy_oil_upgrading_table["SCO/bitumen ratio"] field.save_process_data(SCO_bitumen_ratio=SCO_bitumen_ratio) # used in the Flaring process @@ -135,16 +132,21 @@ def calculate_mass_rate_from_volume_rate(volume_rate, gas_comp): # NG calculation NG_dict = d["Natural gas intensity (W/O cogen)"] * SCO_output - NG_to_cogen_yield = \ - frac_electricity_self_gene * electricity_yield / heavy_oil_upgrading_table[ - "Cogen turbine efficiency"] / self.NG_heating_value + NG_to_cogen_yield = ( + frac_electricity_self_gene + * electricity_yield + / heavy_oil_upgrading_table["Cogen turbine efficiency"] + / self.NG_heating_value + ) NG_to_H2 = NG_dict["Fraction NG - H2"] NG_to_H2_mass_rate = calculate_mass_rate_from_volume_rate(NG_to_H2, self.NG_comp) NG_to_cogen = NG_to_cogen_yield * SCO_output - heat_from_cogen = \ + heat_from_cogen = ( NG_to_cogen * self.NG_heating_value * heavy_oil_upgrading_table["Cogeneration steam efficiency"] + ) NG_to_heat = max( - NG_dict["Fraction NG - Heating (W/O cogen)"] - heat_from_cogen / upgrader_process_gas_heating_value, 0) + NG_dict["Fraction NG - Heating (W/O cogen)"] - heat_from_cogen / upgrader_process_gas_heating_value, 0 + ) proc_gas_flaring_mass_rate = calculate_mass_rate_from_volume_rate(proc_gas_flared, self.upgrader_gas_comp) flaring_gas = self.find_output_stream("gas for flaring") @@ -153,23 +155,34 @@ def calculate_mass_rate_from_volume_rate(volume_rate, gas_comp): # Petrocoke calculation coke_dict = d["Coke yield per bbl SCO output"] * SCO_output - coke_to_stockpile_and_transport = \ - ureg.Quantity(max(0, ( - input_liquid_mass_rate - - SCO_output_mass_rate - - proc_gas_exported_mass_rate.sum() - - proc_gas_flaring_mass_rate.sum()).to("tonne/day").m), "tonne/day") - coke_to_heat = \ - ureg.Quantity(max(0, (coke_dict.sum() - coke_to_stockpile_and_transport).to("tonne/day").m), "tonne/day") + coke_to_stockpile_and_transport = ureg.Quantity( + max( + 0, + ( + input_liquid_mass_rate + - SCO_output_mass_rate + - proc_gas_exported_mass_rate.sum() + - proc_gas_flaring_mass_rate.sum() + ) + .to("tonne/day") + .m, + ), + "tonne/day", + ) + coke_to_heat = ureg.Quantity( + max(0, (coke_dict.sum() - coke_to_stockpile_and_transport).to("tonne/day").m), "tonne/day" + ) coke_to_transport = self.find_output_stream("petrocoke") coke_to_transport.set_solid_flow_rate("PC", coke_to_stockpile_and_transport) coke_to_transport.set_tp(STP) - self.set_iteration_value(SCO_to_storage.total_flow_rate() + - coke_to_transport.total_flow_rate() + - flaring_gas.total_flow_rate() + - output_proc_gas.total_flow_rate()) + self.set_iteration_value( + SCO_to_storage.total_flow_rate() + + coke_to_transport.total_flow_rate() + + flaring_gas.total_flow_rate() + + output_proc_gas.total_flow_rate() + ) # energy use energy_use = self.energy @@ -177,9 +190,9 @@ def calculate_mass_rate_from_volume_rate(volume_rate, gas_comp): NG_stream = Stream("NG_stream", tp=STP) upgrader_gas_stream = Stream("upgrader_gas_stream", tp=STP) NG_mass_rate = calculate_mass_rate_from_volume_rate(NG_to_cogen + NG_to_heat + NG_to_H2, self.NG_comp) - upgrader_mass_rate = \ - calculate_mass_rate_from_volume_rate(proc_gas_to_heat + proc_gas_to_H2 + proc_gas_flared, - self.upgrader_gas_comp) + upgrader_mass_rate = calculate_mass_rate_from_volume_rate( + proc_gas_to_heat + proc_gas_to_H2 + proc_gas_flared, self.upgrader_gas_comp + ) NG_stream.set_rates_from_series(NG_mass_rate, PHASE_GAS) upgrader_gas_stream.set_rates_from_series(upgrader_mass_rate, PHASE_GAS) diff --git a/opgee/processes/natural_gas_liquid.py b/opgee/processes/natural_gas_liquid.py index 4c8f39b9..7b65a89b 100644 --- a/opgee/processes/natural_gas_liquid.py +++ b/opgee/processes/natural_gas_liquid.py @@ -11,6 +11,7 @@ _logger = getLogger(__name__) + class NGL(Process): def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -23,6 +24,7 @@ def __init__(self, name, **kwargs): self._required_outputs = [ "LPG", ] + def run(self, analysis): self.print_running_msg() @@ -35,4 +37,3 @@ def run(self, analysis): output = self.find_output_stream("LPG") output.copy_flow_rates_from(input) - diff --git a/opgee/processes/petrocoke_transport.py b/opgee/processes/petrocoke_transport.py index 007962e6..70d1d79b 100644 --- a/opgee/processes/petrocoke_transport.py +++ b/opgee/processes/petrocoke_transport.py @@ -18,13 +18,12 @@ class PetrocokeTransport(Process): """ Petrocoke transport calculate emissions from petrocoke to the market """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) model = self.model - self._required_inputs = [ - "petrocoke" - ] + self._required_inputs = ["petrocoke"] # TODO: avoid process names in contents. self._required_outputs = [ @@ -53,13 +52,14 @@ def run(self, analysis): # energy use energy_use = self.energy - fuel_consumption = \ - field.transport_energy.get_transport_energy_dict(self.field, - self.transport_parameter, - self.transport_share_fuel, - self.transport_by_mode, - petrocoke_LHV_rate, - "Petrocoke") + fuel_consumption = field.transport_energy.get_transport_energy_dict( + self.field, + self.transport_parameter, + self.transport_share_fuel, + self.transport_by_mode, + petrocoke_LHV_rate, + "Petrocoke", + ) for name, value in fuel_consumption.items(): energy_use.set_rate(get_energy_carrier(name), value.to("mmBtu/day")) diff --git a/opgee/processes/post_storage_compressor.py b/opgee/processes/post_storage_compressor.py index e7b9d3c4..529f48ae 100644 --- a/opgee/processes/post_storage_compressor.py +++ b/opgee/processes/post_storage_compressor.py @@ -14,10 +14,12 @@ _logger = getLogger(__name__) + class PostStorageCompressor(Process): """ Storage compressor calculate emission from compressing produced gas for long-term (i.e., seasonal) storage. """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -30,7 +32,6 @@ def __init__(self, name, **kwargs): "gas for distribution", ] - self.discharge_press = None self.eta_compressor = None self.prime_mover_type = None @@ -56,13 +57,9 @@ def run(self, analysis): input_energy_flow_rate = self.field.gas.energy_flow_rate(input) overall_compression_ratio = self.discharge_press / input.tp.P - energy_consumption, output_temp, output_press = \ - Compressor.get_compressor_energy_consumption( - self.field, - self.prime_mover_type, - self.eta_compressor, - overall_compression_ratio, - input) + energy_consumption, output_temp, output_press = Compressor.get_compressor_energy_consumption( + self.field, self.prime_mover_type, self.eta_compressor, overall_compression_ratio, input + ) # energy-use energy_use = self.energy diff --git a/opgee/processes/pre_membrane_chiller.py b/opgee/processes/pre_membrane_chiller.py index efef8eae..625b77c0 100644 --- a/opgee/processes/pre_membrane_chiller.py +++ b/opgee/processes/pre_membrane_chiller.py @@ -56,8 +56,9 @@ def run(self, analysis): self.set_iteration_value(gas_to_compressor.total_flow_rate()) delta_temp = input.tp.T - self.outlet_temp - energy_consumption = (self.compressor_load * input.total_gas_rate() / - self.feed_stream_mass_rate * delta_temp / self.pressure_drop) + energy_consumption = ( + self.compressor_load * input.total_gas_rate() / self.feed_stream_mass_rate * delta_temp / self.pressure_drop + ) # energy-use energy_use = self.energy diff --git a/opgee/processes/pre_membrane_compressor.py b/opgee/processes/pre_membrane_compressor.py index 3bc25c3c..64797abf 100644 --- a/opgee/processes/pre_membrane_compressor.py +++ b/opgee/processes/pre_membrane_compressor.py @@ -56,13 +56,9 @@ def run(self, analysis): self.set_iteration_value(gas_to_CO2_membrane.total_flow_rate()) overall_compression_ratio = self.discharge_press / input.tp.P - energy_consumption, output_temp, output_press = \ - Compressor.get_compressor_energy_consumption( - self.field, - self.prime_mover_type, - self.eta_compressor, - overall_compression_ratio, - input) + energy_consumption, output_temp, output_press = Compressor.get_compressor_energy_consumption( + self.field, self.prime_mover_type, self.eta_compressor, overall_compression_ratio, input + ) # energy-use energy_use = self.energy diff --git a/opgee/processes/reservoir_well_interface.py b/opgee/processes/reservoir_well_interface.py index 5b8bb122..191d9a3a 100644 --- a/opgee/processes/reservoir_well_interface.py +++ b/opgee/processes/reservoir_well_interface.py @@ -41,8 +41,7 @@ def __init__(self, name, **kwargs): def cache_attributes(self): field = self.field - self.res_tp = TemperaturePressure(field.res_temp, - field.res_press) + self.res_tp = TemperaturePressure(field.res_temp, field.res_press) self.num_prod_wells = field.num_prod_wells self.productivity_index = field.productivity_index self.permeability = self.attr("res_perm") @@ -102,10 +101,9 @@ def get_bottomhole_press(self, input_stream): std_P = STP.P.to("Pa") # injection and production rate - oil_prod_volume_rate = oil.volume_flow_rate(input_stream, - oil.oil_specific_gravity, - oil.gas_specific_gravity, - oil.gas_oil_ratio).to("bbl_oil/day") # bbl/day + oil_prod_volume_rate = oil.volume_flow_rate( + input_stream, oil.oil_specific_gravity, oil.gas_specific_gravity, oil.gas_oil_ratio + ).to("bbl_oil/day") # bbl/day water_prod_volume_rate = water.volume_flow_rate(input_stream).to("bbl_water/day") # bbl/day gas_prod_volume_rate = gas.volume_flow_rate(input_stream).to("ft**3/day") fluid_rate_per_well = (oil_prod_volume_rate + water_prod_volume_rate) / self.num_prod_wells @@ -117,20 +115,32 @@ def get_bottomhole_press(self, input_stream): gas_formation_volume_factor = gas.volume_factor(input_stream) # reservoir and flowing pressures at wellbore interface - prod_liquid_flowing_BHP = max((input_stream.tp.P - fluid_rate_per_well / self.productivity_index).to("psia"), - ureg.Quantity(100, "psia")) + prod_liquid_flowing_BHP = max( + (input_stream.tp.P - fluid_rate_per_well / self.productivity_index).to("psia"), ureg.Quantity(100, "psia") + ) - boundary = ureg.Quantity(2000., "psia") + boundary = ureg.Quantity(2000.0, "psia") if res_press <= boundary: # flowing bottomhole pressure at producer (gas phase, low pressure) - delta_P_square = (gas_viscosity * z_factor * std_P * stream_temp * - np.log(1000 / 0.5) * gas_rate_per_well / - (np.pi * self.permeability * self.res_thickness * std_T)).to("psia**2") - delta = res_press ** 2 - delta_P_square + delta_P_square = ( + gas_viscosity + * z_factor + * std_P + * stream_temp + * np.log(1000 / 0.5) + * gas_rate_per_well + / (np.pi * self.permeability * self.res_thickness * std_T) + ).to("psia**2") + delta = res_press**2 - delta_P_square prod_gas_flowing_BHP = np.sqrt(delta) if delta > 0.0 else STP.P else: - delta_P_high = (gas_viscosity * gas_formation_volume_factor * np.log(1000 / 0.5) * gas_rate_per_well / - (2 * np.pi * self.permeability * self.res_thickness)).to("psia") + delta_P_high = ( + gas_viscosity + * gas_formation_volume_factor + * np.log(1000 / 0.5) + * gas_rate_per_well + / (2 * np.pi * self.permeability * self.res_thickness) + ).to("psia") prod_gas_flowing_BHP = res_press - delta_P_high prod_flowing_BHP = max(min(prod_liquid_flowing_BHP, prod_gas_flowing_BHP), STP.P) diff --git a/opgee/processes/ryan_holmes.py b/opgee/processes/ryan_holmes.py index 70afb32b..b71f274e 100644 --- a/opgee/processes/ryan_holmes.py +++ b/opgee/processes/ryan_holmes.py @@ -73,29 +73,32 @@ def run(self, analysis): # Ryan-Holmes Process volume_flow_rate_STP = self.gas.volume_flow_rate_STP(input) - feed_stream_rate = ureg.Quantity(45., "mmscf/day") - turbine_consume_rate = ureg.Quantity(25800., "scf/hr") + feed_stream_rate = ureg.Quantity(45.0, "mmscf/day") + turbine_consume_rate = ureg.Quantity(25800.0, "scf/hr") tot_turbine_consumption_rate = volume_flow_rate_STP / feed_stream_rate * turbine_consume_rate turbine_energy_consumption = self.gas.component_HHV_molar["C1"] * self.mol_to_scf * tot_turbine_consumption_rate - compressor_consume_rate = ureg.Quantity(110519., "scf/hr") + compressor_consume_rate = ureg.Quantity(110519.0, "scf/hr") tot_compressor_consumption_rate = volume_flow_rate_STP / feed_stream_rate * compressor_consume_rate - compressor_energy_consumption = self.gas.component_HHV_molar[ - "C1"] * self.mol_to_scf * tot_compressor_consumption_rate + compressor_energy_consumption = ( + self.gas.component_HHV_molar["C1"] * self.mol_to_scf * tot_compressor_consumption_rate + ) - hotoil_heater_consume_rate = ureg.Quantity(14589., "scf/hr") + hotoil_heater_consume_rate = ureg.Quantity(14589.0, "scf/hr") tot_heater_consumption_rate = volume_flow_rate_STP / feed_stream_rate * hotoil_heater_consume_rate heater_energy_consumption = self.gas.component_HHV_molar["C1"] * self.mol_to_scf * tot_heater_consumption_rate - diesel_consume_rate = ureg.Quantity(57., "gal/day") - tot_diesel_consumption_rate = (volume_flow_rate_STP / feed_stream_rate * - diesel_consume_rate * self.daily_use_engine) + diesel_consume_rate = ureg.Quantity(57.0, "gal/day") + tot_diesel_consumption_rate = ( + volume_flow_rate_STP / feed_stream_rate * diesel_consume_rate * self.daily_use_engine + ) diesel_energy_consumption = self.diesel_LHV * tot_diesel_consumption_rate # energy-use energy_use = self.energy - energy_use.set_rate(EN_NATURAL_GAS, - turbine_energy_consumption + compressor_energy_consumption + heater_energy_consumption) + energy_use.set_rate( + EN_NATURAL_GAS, turbine_energy_consumption + compressor_energy_consumption + heater_energy_consumption + ) energy_use.set_rate(EN_DIESEL, diesel_energy_consumption) # import/export diff --git a/opgee/processes/separation.py b/opgee/processes/separation.py index 98e98d93..f48ee325 100644 --- a/opgee/processes/separation.py +++ b/opgee/processes/separation.py @@ -28,7 +28,7 @@ def __init__(self, name, **kwargs): ] self._required_outputs = [ - "gas for partition", # TODO: this is called "gas for gas partition" elsewhere + "gas for partition", # TODO: this is called "gas for gas partition" elsewhere ] self.compressor_eff = None @@ -61,13 +61,12 @@ def cache_attributes(self): self.loss_rate = self.venting_fugitive_rate() self.loss_rate = (1 / (1 - self.loss_rate)).to("frac") - self.outlet_tp = TemperaturePressure(self.attr("temperature_outlet"), - self.attr("pressure_outlet")) + self.outlet_tp = TemperaturePressure(self.attr("temperature_outlet"), self.attr("pressure_outlet")) self.temperature_stage1 = field.wellhead_t self.temperature_stage2 = (self.temperature_stage1.to("kelvin") + self.outlet_tp.T.to("kelvin")) / 2 - #TODO: move it to smart default + # TODO: move it to smart default self.pressure_stage1 = min(field.wellhead_p, self.attr("pressure_first_stage")) self.pressure_stage2 = self.attr("pressure_second_stage") self.pressure_stage3 = self.attr("pressure_third_stage") @@ -76,7 +75,7 @@ def cache_attributes(self): self.gas_comp = field.gas_comp self.num_of_stages = self.attr("number_stages") - #TODO: move it to smart default + # TODO: move it to smart default if field.wellhead_p.m < 500: self.num_of_stages = 1 @@ -89,7 +88,7 @@ def run(self, analysis): self.print_running_msg() field = self.field - #TODO: Fix this after data pipeline is done + # TODO: Fix this after data pipeline is done water_oil_ratio = field.attr("WOR") # mass rate @@ -108,13 +107,15 @@ def run(self, analysis): # energy rate free_gas_stages, final_GOR = self.get_free_gas_stages(field, input) # (float, list) scf/bbl - gas_compression_volume_stages = [(self.oil_volume_rate * free_gas).to("mmscf/day") for free_gas in - free_gas_stages] - compressor_brake_horsepower_of_stages = self.compressor_brake_horsepower_of_stages(self.field, - gas_after, - gas_compression_volume_stages) - energy_consumption_of_stages = get_energy_consumption_stages(self.prime_mover_type, - compressor_brake_horsepower_of_stages) + gas_compression_volume_stages = [ + (self.oil_volume_rate * free_gas).to("mmscf/day") for free_gas in free_gas_stages + ] + compressor_brake_horsepower_of_stages = self.compressor_brake_horsepower_of_stages( + self.field, gas_after, gas_compression_volume_stages + ) + energy_consumption_of_stages = get_energy_consumption_stages( + self.prime_mover_type, compressor_brake_horsepower_of_stages + ) energy_consumption_sum = sum(energy_consumption_of_stages) energy_use = self.energy @@ -146,7 +147,6 @@ def impute(self): field.save_process_data(wellhead_LHV_rate=gas_LHV_rate + oil_LHV_rate) def get_stages_temperature_and_pressure(self): - temperature_of_stages = [self.temperature_stage1, self.temperature_stage2.to("degF"), self.outlet_tp.T] pressure_of_stages = [self.pressure_stage1, self.pressure_stage2, self.pressure_stage3] @@ -162,13 +162,14 @@ def get_output_streams(self, field): gas_after = self.find_output_stream("gas for partition") last = self.num_of_stages - 1 - stream = Stream("stage_stream", TemperaturePressure(temperature_of_stages[last], - pressure_of_stages[last])) + stream = Stream("stage_stream", TemperaturePressure(temperature_of_stages[last], pressure_of_stages[last])) - density = oil.density(stream, # lb/ft3 - oil.oil_specific_gravity, - oil.gas_specific_gravity, - oil.gas_oil_ratio) + density = oil.density( + stream, # lb/ft3 + oil.oil_specific_gravity, + oil.gas_specific_gravity, + oil.gas_oil_ratio, + ) gas_volume_rate = self.oil_volume_rate * self.gas_oil_ratio * self.gas_comp gas_density = gas.component_gas_rho_STP[self.gas_comp.index] @@ -209,18 +210,17 @@ def get_free_gas_stages(self, field, input_stream): solution_gas_oil_ratio_of_stages = [oil.gas_oil_ratio] oil_SG = oil.specific_gravity(input_stream.API) for stage in range(self.num_of_stages): - stream_stages = Stream("stage_stream", TemperaturePressure(temperature_of_stages[stage], - pressure_of_stages[stage])) - solution_gas_oil_ratio = oil.solution_gas_oil_ratio(stream_stages, - oil_SG, - oil.gas_specific_gravity, - oil.gas_oil_ratio) + stream_stages = Stream( + "stage_stream", TemperaturePressure(temperature_of_stages[stage], pressure_of_stages[stage]) + ) + solution_gas_oil_ratio = oil.solution_gas_oil_ratio( + stream_stages, oil_SG, oil.gas_specific_gravity, oil.gas_oil_ratio + ) solution_gas_oil_ratio_of_stages.append(solution_gas_oil_ratio) free_gas_of_stages = [] for i in range(1, len(solution_gas_oil_ratio_of_stages)): - free_gas_of_stages.append(solution_gas_oil_ratio_of_stages[i - 1] - - solution_gas_oil_ratio_of_stages[i]) + free_gas_of_stages.append(solution_gas_oil_ratio_of_stages[i - 1] - solution_gas_oil_ratio_of_stages[i]) return free_gas_of_stages, solution_gas_oil_ratio_of_stages[-1] @@ -236,19 +236,18 @@ def compressor_brake_horsepower_of_stages(self, field, gas_stream, gas_compressi temperature_of_stages, pressure_of_stages = self.get_stages_temperature_and_pressure() - overall_compression_ratio_stages = [self.pressure_after_boosting / - pressure_of_stages[stage] for stage in range(self.num_of_stages)] + overall_compression_ratio_stages = [ + self.pressure_after_boosting / pressure_of_stages[stage] for stage in range(self.num_of_stages) + ] compression_ratio_per_stages = Compressor.get_compression_ratio_stages(overall_compression_ratio_stages) brake_horsepower_of_stages = [] - for (inlet_temp, inlet_press, (compression_ratio, num_of_compression), - gas_compression_volume) \ - in zip(temperature_of_stages, - pressure_of_stages, - compression_ratio_per_stages, - gas_compression_volume_stages): - work_sum, _, _ = Compressor.get_compressor_work_temp(field, inlet_temp, inlet_press, - gas_stream, compression_ratio, num_of_compression) + for inlet_temp, inlet_press, (compression_ratio, num_of_compression), gas_compression_volume in zip( + temperature_of_stages, pressure_of_stages, compression_ratio_per_stages, gas_compression_volume_stages + ): + work_sum, _, _ = Compressor.get_compressor_work_temp( + field, inlet_temp, inlet_press, gas_stream, compression_ratio, num_of_compression + ) horsepower = work_sum * gas_compression_volume brake_horsepower = horsepower / self.compressor_eff brake_horsepower_of_stages.append(brake_horsepower) diff --git a/opgee/processes/shared.py b/opgee/processes/shared.py index 414dc8ff..9454388c 100644 --- a/opgee/processes/shared.py +++ b/opgee/processes/shared.py @@ -11,16 +11,11 @@ from ..error import OpgeeException from ..stream import Stream, PHASE_GAS -_slope = {"NG_engine": -0.6035, - "NG_turbine": -0.1279} +_slope = {"NG_engine": -0.6035, "NG_turbine": -0.1279} -_intercept = {"NG_engine": 7922.4, - "NG_turbine": 9219.6} +_intercept = {"NG_engine": 7922.4, "NG_turbine": 9219.6} -_maxBHP = {"NG_engine": 2800.0, - "Diesel_engine": 3000.0, - "NG_turbine": 21000.0, - "Electric_motor": 1000.0} +_maxBHP = {"NG_engine": 2800.0, "Diesel_engine": 3000.0, "NG_turbine": 21000.0, "Electric_motor": 1000.0} def get_efficiency(prime_mover_type, brake_horsepower): @@ -36,16 +31,14 @@ def get_efficiency(prime_mover_type, brake_horsepower): if prime_mover_type == "Electric_motor": efficiency = 2967 * brake_horsepower ** (-0.018) if brake_horsepower != 0.0 else 3038 elif prime_mover_type == "Diesel_engine": - efficiency = 0.0004 * brake_horsepower ** 2 - 1.6298 * brake_horsepower + 7955.8 + efficiency = 0.0004 * brake_horsepower**2 - 1.6298 * brake_horsepower + 7955.8 else: efficiency = _slope[prime_mover_type] * brake_horsepower + _intercept[prime_mover_type] return ureg.Quantity(efficiency, "btu/horsepower/hour") -def get_init_lifting_stream(gas, - lifting_gas_stream, - gas_lifting_vol_rate): +def get_init_lifting_stream(gas, lifting_gas_stream, gas_lifting_vol_rate): """ Generate initial gas stream for lifting @@ -57,9 +50,11 @@ def get_init_lifting_stream(gas, lifting_gas_mass_fracs = gas.component_mass_fractions(gas.component_molar_fractions(lifting_gas_stream)) - series = (lifting_gas_mass_fracs * - gas_lifting_vol_rate * - gas.component_gas_rho_STP[lifting_gas_stream.gas_flow_rates().index]) + series = ( + lifting_gas_mass_fracs + * gas_lifting_vol_rate + * gas.component_gas_rho_STP[lifting_gas_stream.gas_flow_rates().index] + ) stream = Stream("gas lifting stream", lifting_gas_stream.tp) stream.set_rates_from_series(series, PHASE_GAS) @@ -70,8 +65,14 @@ def get_init_lifting_stream(gas, # # Helper function shared by acid_gas_removal and demethanizer # -def predict_blower_energy_use(proc, thermal_load, air_cooler_delta_T=None, water_press=None, - air_cooler_fan_eff=None, air_cooler_speed_reducer_eff=None): +def predict_blower_energy_use( + proc, + thermal_load, + air_cooler_delta_T=None, + water_press=None, + air_cooler_fan_eff=None, + air_cooler_speed_reducer_eff=None, +): """ Predict blower energy use per day. Any parameters not explicitly provided are taken from the `proc` object. @@ -85,7 +86,7 @@ def predict_blower_energy_use(proc, thermal_load, air_cooler_delta_T=None, water """ def _value(value, dflt): - return (dflt if value is None else value) + return dflt if value is None else value air_cooler_delta_T = _value(air_cooler_delta_T, proc.air_cooler_delta_T) water_press = _value(water_press, proc.water_press) diff --git a/opgee/processes/sour_gas_compressor.py b/opgee/processes/sour_gas_compressor.py index 9e9f9b5a..7a38f787 100644 --- a/opgee/processes/sour_gas_compressor.py +++ b/opgee/processes/sour_gas_compressor.py @@ -58,13 +58,9 @@ def run(self, analysis): discharge_press = self.res_press + ureg.Quantity(500.0, "psia") overall_compression_ratio = discharge_press / input.tp.P - energy_consumption, output_temp, _ = \ - Compressor.get_compressor_energy_consumption( - self.field, - self.prime_mover_type, - self.eta_compressor, - overall_compression_ratio, - input) + energy_consumption, output_temp, _ = Compressor.get_compressor_energy_consumption( + self.field, self.prime_mover_type, self.eta_compressor, overall_compression_ratio, input + ) gas_to_injection.tp.set(T=output_temp, P=discharge_press) field.save_process_data(sour_gas_reinjection_mass_rate=gas_to_injection.gas_flow_rate("CO2")) @@ -80,6 +76,3 @@ def run(self, analysis): # emissions self.set_combustion_emissions() self.emissions.set_from_stream(EM_FUGITIVES, gas_fugitives) - - - diff --git a/opgee/processes/steam_generation.py b/opgee/processes/steam_generation.py index 62f6f7cd..43066a4f 100644 --- a/opgee/processes/steam_generation.py +++ b/opgee/processes/steam_generation.py @@ -32,10 +32,7 @@ def __init__(self, *args, **kwargs): # In the meantime, it requires special processing below: field = self.field if field.steam_flooding == 1 and field.SOR != 0: - self._required_inputs = [ - "produced water", - "makeup water" - ] + self._required_inputs = ["produced water", "makeup water"] self._required_outputs = [ "water", @@ -79,17 +76,20 @@ def cache_attributes(self): self.steam_quality_after_blowdown = self.attr("steam_quality_after_blowdown") self.fraction_blowdown_recycled = self.attr("fraction_blowdown_recycled") - self.waste_water_reinjection_tp = TemperaturePressure(self.attr("waste_water_reinjection_temp"), - self.attr("waste_water_reinjection_press")) + self.waste_water_reinjection_tp = TemperaturePressure( + self.attr("waste_water_reinjection_temp"), self.attr("waste_water_reinjection_press") + ) self.pressure_loss_choke_wellhead = self.attr("pressure_loss_choke_wellhead") self.friction_loss_steam_distr = field.friction_loss_steam_distr self.water_density = self.water.density() self.res_press = field.res_press self.steam_injection_delta_press = self.attr("steam_injection_delta_press") - self.steam_generator_press_outlet = ((self.res_press + self.steam_injection_delta_press) * - self.friction_loss_steam_distr * - self.pressure_loss_choke_wellhead) + self.steam_generator_press_outlet = ( + (self.res_press + self.steam_injection_delta_press) + * self.friction_loss_steam_distr + * self.pressure_loss_choke_wellhead + ) self.prod_water_inlet_press = self.attr("prod_water_inlet_press") self.makeup_water_inlet_press = self.attr("makeup_water_inlet_press") self.eta_displacement_pump = self.attr("eta_displacement_pump") @@ -132,51 +132,60 @@ def run(self, analysis): steam_injection_volume_rate = water_mass_rate_for_injection / self.water_density steam_quality_diff_between_blowndown_and_outlet = self.steam_quality_after_blowdown - self.steam_quality_outlet - steam_quality_diff_between_blowndown_and_outlet = \ - ureg.Quantity(max(steam_quality_diff_between_blowndown_and_outlet.to("frac").m, 0.0), "frac") + steam_quality_diff_between_blowndown_and_outlet = ureg.Quantity( + max(steam_quality_diff_between_blowndown_and_outlet.to("frac").m, 0.0), "frac" + ) if steam_quality_diff_between_blowndown_and_outlet.m < 0: _logger.warning("steam quality after blowdown is smaller than steam quality at outlet") - blowdown_water_mass_rate = \ + blowdown_water_mass_rate = ( water_mass_rate_for_injection * steam_quality_diff_between_blowndown_and_outlet / self.steam_quality_outlet + ) waste_water_from_blowdown = blowdown_water_mass_rate * (1 - self.fraction_blowdown_recycled) import_product.set_export(self.name, WATER, waste_water_from_blowdown) recycled_blowdown_water = blowdown_water_mass_rate * self.fraction_blowdown_recycled recycled_water_stream = self.find_output_stream("water") - recycled_water_stream.set_liquid_flow_rate("H2O", - recycled_blowdown_water.to("tonne/day"), - tp=self.waste_water_reinjection_tp) + recycled_water_stream.set_liquid_flow_rate( + "H2O", recycled_blowdown_water.to("tonne/day"), tp=self.waste_water_reinjection_tp + ) - fuel_consumption_OTSG = fuel_consumption_HRSG = fuel_consumption_solar = \ - electricity_HRSG = ureg.Quantity(0, "MJ/day") + fuel_consumption_OTSG = fuel_consumption_HRSG = fuel_consumption_solar = electricity_HRSG = ureg.Quantity( + 0, "MJ/day" + ) fraction_OTSG = self.fraction_OTSG if fraction_OTSG.m != 0: - fuel_consumption_OTSG, mass_in_OTSG, mass_out_OTSG, energy_in_OTSG, energy_out_OTSG = \ - self.steam_generator.once_through_SG(prod_water_mass_rate * fraction_OTSG, - makeup_water_mass_rate * fraction_OTSG, - water_mass_rate_for_injection * fraction_OTSG, - blowdown_water_mass_rate * fraction_OTSG) + fuel_consumption_OTSG, mass_in_OTSG, mass_out_OTSG, energy_in_OTSG, energy_out_OTSG = ( + self.steam_generator.once_through_SG( + prod_water_mass_rate * fraction_OTSG, + makeup_water_mass_rate * fraction_OTSG, + water_mass_rate_for_injection * fraction_OTSG, + blowdown_water_mass_rate * fraction_OTSG, + ) + ) self.check_balance(mass_in_OTSG, mass_out_OTSG, "OTSG_mass") self.check_balance(energy_in_OTSG, energy_out_OTSG, "OTSG_energy") fraction_steam_cogen = self.fraction_steam_cogen if self.fraction_steam_cogen != 0: - fuel_consumption_HRSG, electricity_HRSG, mass_in_HRSG, mass_out_HRSG, energy_in_HRSG, energy_out_HRSG = \ - self.steam_generator.heat_recovery_SG(prod_water_mass_rate * fraction_steam_cogen, - makeup_water_mass_rate * fraction_steam_cogen, - water_mass_rate_for_injection * fraction_steam_cogen, - blowdown_water_mass_rate * fraction_steam_cogen) + fuel_consumption_HRSG, electricity_HRSG, mass_in_HRSG, mass_out_HRSG, energy_in_HRSG, energy_out_HRSG = ( + self.steam_generator.heat_recovery_SG( + prod_water_mass_rate * fraction_steam_cogen, + makeup_water_mass_rate * fraction_steam_cogen, + water_mass_rate_for_injection * fraction_steam_cogen, + blowdown_water_mass_rate * fraction_steam_cogen, + ) + ) self.check_balance(mass_in_HRSG, mass_out_HRSG, "HRSG_mass") self.check_balance(energy_in_HRSG, energy_out_HRSG, "HRSG_energy") if self.fraction_steam_solar != 0: - fuel_consumption_solar = \ - self.steam_generator.solar_SG(prod_water_mass_rate * self.fraction_steam_solar, - makeup_water_mass_rate * self.fraction_steam_solar) + fuel_consumption_solar = self.steam_generator.solar_SG( + prod_water_mass_rate * self.fraction_steam_solar, makeup_water_mass_rate * self.fraction_steam_solar + ) # energy use energy_use = self.energy @@ -185,12 +194,11 @@ def run(self, analysis): water_pump_hp = self.get_feedwater_horsepower(prod_water_mass_rate, makeup_water_mass_rate) water_pump_power = get_energy_consumption("Electric_motor", water_pump_hp) - OTSG_air_blower = get_energy_consumption("Electric_motor", - fuel_consumption_OTSG * self.eta_air_blower_OTSG) - HRSG_air_blower = get_energy_consumption("Electric_motor", - fuel_consumption_HRSG * self.eta_air_blower_HRSG) - solar_thermal_pumping = get_energy_consumption("Electric_motor", - fuel_consumption_solar * self.eta_air_blower_solar) + OTSG_air_blower = get_energy_consumption("Electric_motor", fuel_consumption_OTSG * self.eta_air_blower_OTSG) + HRSG_air_blower = get_energy_consumption("Electric_motor", fuel_consumption_HRSG * self.eta_air_blower_HRSG) + solar_thermal_pumping = get_energy_consumption( + "Electric_motor", fuel_consumption_solar * self.eta_air_blower_solar + ) total_power_required = water_pump_power + OTSG_air_blower + HRSG_air_blower + solar_thermal_pumping energy_use.set_rate(EN_ELECTRICITY, total_power_required) @@ -206,8 +214,9 @@ def get_feedwater_horsepower(self, prod_water_mass_rate, makeup_water_mass_rate) prod_water_volume_rate = prod_water_mass_rate / self.water_density makeup_water_mass_rate = makeup_water_mass_rate / self.water_density - result = makeup_water_mass_rate * (self.steam_generator_press_outlet - self.makeup_water_inlet_press) + \ - prod_water_volume_rate * (self.steam_generator_press_outlet - self.prod_water_inlet_press) + result = makeup_water_mass_rate * ( + self.steam_generator_press_outlet - self.makeup_water_inlet_press + ) + prod_water_volume_rate * (self.steam_generator_press_outlet - self.prod_water_inlet_press) result /= self.eta_displacement_pump return result diff --git a/opgee/processes/steam_generator.py b/opgee/processes/steam_generator.py index a34f0610..e195e80b 100644 --- a/opgee/processes/steam_generator.py +++ b/opgee/processes/steam_generator.py @@ -35,8 +35,7 @@ def __init__(self, field): self.makeup_water_inlet_temp = field.attr("makeup_water_inlet_temp") self.makeup_water_inlet_press = field.attr("makeup_water_inlet_press") self.temperature_inlet_air_OTSG = field.attr("temperature_inlet_air_OTSG") - self.OTSG_exhaust_temp_outlet_before_economizer = field.attr( - "OTSG_exhaust_temp_outlet_before_economizer") + self.OTSG_exhaust_temp_outlet_before_economizer = field.attr("OTSG_exhaust_temp_outlet_before_economizer") self.OTSG_exhaust_temp_series = field.attrs_with_prefix("OTSG_exhaust_temp_") self.HRSG_exhaust_temp_series = field.attrs_with_prefix("HRSG_exhaust_temp_") @@ -82,70 +81,84 @@ def __init__(self, field): self.reaction_combustion_coeff = model.reaction_combustion_coeff self.gas_turbine_tlb = model.gas_turbine_tbl - self.steam_generator_press_outlet = ((self.res_press + self.steam_injection_delta_press) * - self.friction_loss_steam_distr * - self.pressure_loss_choke_wellhead) + self.steam_generator_press_outlet = ( + (self.res_press + self.steam_injection_delta_press) + * self.friction_loss_steam_distr + * self.pressure_loss_choke_wellhead + ) self.steam_generator_press_outlet = min(self.steam_generator_press_outlet, ureg.Quantity(220, "bar")) self.O2_excess_HRSG = self.gas_turbine_tlb["Turbine excess air"][self.gas_turbine_type] - self.prod_gas_reactants_comp = self.get_combustion_comp(self.reaction_combustion_coeff, - self.processed_prod_gas_comp) + self.prod_gas_reactants_comp = self.get_combustion_comp( + self.reaction_combustion_coeff, self.processed_prod_gas_comp + ) self.prod_gas_products_comp = self.get_combustion_comp(self.prod_combustion_coeff, self.processed_prod_gas_comp) - self.import_gas_reactants_comp = self.get_combustion_comp(self.reaction_combustion_coeff, - self.imported_fuel_gas_comp) + self.import_gas_reactants_comp = self.get_combustion_comp( + self.reaction_combustion_coeff, self.imported_fuel_gas_comp + ) - self.import_gas_products_comp = self.get_combustion_comp(self.prod_combustion_coeff, - self.imported_fuel_gas_comp) - - def once_through_SG(self, - prod_water_mass_rate, - makeup_water_mass_rate, - water_mass_rate_for_injection, - blowdown_water_mass_rate): + self.import_gas_products_comp = self.get_combustion_comp( + self.prod_combustion_coeff, self.imported_fuel_gas_comp + ) + def once_through_SG( + self, prod_water_mass_rate, makeup_water_mass_rate, water_mass_rate_for_injection, blowdown_water_mass_rate + ): """ - Calculate fuel consumption and mass and energy balance for a once-through steam generator (OTSG). - - :param prod_water_mass_rate: float, produced water mass flow rate (kg/day) - :param makeup_water_mass_rate: float, makeup water mass flow rate (kg/day) - :param water_mass_rate_for_injection: float, water mass flow rate for injection (kg/day) - :param blowdown_water_mass_rate: float, blowdown water mass flow rate (kg/day) - :return: Tuple with fuel consumption for steam generation (MJ/day), mass flow rate in (kg/day), - mass flow rate out (kg/day), energy flow rate in (MJ/day), and energy flow rate out (MJ/day) + Calculate fuel consumption and mass and energy balance for a once-through steam generator (OTSG). + + :param prod_water_mass_rate: float, produced water mass flow rate (kg/day) + :param makeup_water_mass_rate: float, makeup water mass flow rate (kg/day) + :param water_mass_rate_for_injection: float, water mass flow rate for injection (kg/day) + :param blowdown_water_mass_rate: float, blowdown water mass flow rate (kg/day) + :return: Tuple with fuel consumption for steam generation (MJ/day), mass flow rate in (kg/day), + mass flow rate out (kg/day), energy flow rate in (MJ/day), and energy flow rate out (MJ/day) """ - prod_water_enthalpy_rate, makeup_water_enthalpy_rate, blowdown_water_recoverable_enthalpy_rate, steam_out_enthalpy_rate = \ - self.get_water_steam_enthalpy_rate(prod_water_mass_rate, - makeup_water_mass_rate, - water_mass_rate_for_injection, - blowdown_water_mass_rate) + ( + prod_water_enthalpy_rate, + makeup_water_enthalpy_rate, + blowdown_water_recoverable_enthalpy_rate, + steam_out_enthalpy_rate, + ) = self.get_water_steam_enthalpy_rate( + prod_water_mass_rate, makeup_water_mass_rate, water_mass_rate_for_injection, blowdown_water_mass_rate + ) # OTSG combustion - gas_MW_combust, gas_LHV, \ - air_requirement_fuel, air_requirement_LHV_fuel, air_requirement_LHV_stream, \ - exhaust_consump, exhaust_consump_sum, exhaust_consump_MW = self.get_combustion_parameters("OTSG") - - LHV_fuel, LHV_stream = self.get_LHV_fuel_and_steam_series(exhaust_consump, - self.OTSG_exhaust_temp_series, - gas_MW_combust, - exhaust_consump_sum, - exhaust_consump_MW) - input_enthalpy_per_unit_fuel = \ - gas_LHV + air_requirement_LHV_fuel - LHV_fuel["outlet_before_economizer"] - shell_loss_per_unit_fuel = \ - self.loss_shell_OTSG * gas_LHV if self.OTSG_fuel_type == "Gas" else self.loss_shell_OTSG * self.oil.mass_energy_density() - other_loss_per_unit_fuel = \ + ( + gas_MW_combust, + gas_LHV, + air_requirement_fuel, + air_requirement_LHV_fuel, + air_requirement_LHV_stream, + exhaust_consump, + exhaust_consump_sum, + exhaust_consump_MW, + ) = self.get_combustion_parameters("OTSG") + + LHV_fuel, LHV_stream = self.get_LHV_fuel_and_steam_series( + exhaust_consump, self.OTSG_exhaust_temp_series, gas_MW_combust, exhaust_consump_sum, exhaust_consump_MW + ) + input_enthalpy_per_unit_fuel = gas_LHV + air_requirement_LHV_fuel - LHV_fuel["outlet_before_economizer"] + shell_loss_per_unit_fuel = ( + self.loss_shell_OTSG * gas_LHV + if self.OTSG_fuel_type == "Gas" + else self.loss_shell_OTSG * self.oil.mass_energy_density() + ) + other_loss_per_unit_fuel = ( self.loss_gaseous_OTSG / gas_MW_combust if self.OTSG_fuel_type == "Gas" else self.loss_liquid_OTSG - available_enthalpy = max(input_enthalpy_per_unit_fuel - - shell_loss_per_unit_fuel - - other_loss_per_unit_fuel, 0) + ) + available_enthalpy = max(input_enthalpy_per_unit_fuel - shell_loss_per_unit_fuel - other_loss_per_unit_fuel, 0) # calculate recoverable heat - delta_H = \ - steam_out_enthalpy_rate - prod_water_enthalpy_rate - \ - makeup_water_enthalpy_rate - blowdown_water_recoverable_enthalpy_rate + delta_H = ( + steam_out_enthalpy_rate + - prod_water_enthalpy_rate + - makeup_water_enthalpy_rate + - blowdown_water_recoverable_enthalpy_rate + ) temp = exhaust_consump_sum * exhaust_consump_MW / gas_MW_combust / available_enthalpy constant_before_economizer = temp * LHV_stream["outlet_before_economizer"] @@ -162,106 +175,137 @@ def once_through_SG(self, d_heater = temp / (1 + temp) temp = delta_H / (1 - d_eco * d_heater) - recoverable_heat_before_economizer = \ + recoverable_heat_before_economizer = ( ureg.Quantity(0.0, "MJ/day") if not self.economizer_OTSG else (d_eco - d_eco * d_heater) * temp - recoverable_heat_before_preheater = \ + ) + recoverable_heat_before_preheater = ( ureg.Quantity(0.0, "MJ/day") if not self.economizer_OTSG else (d_heater - d_eco * d_heater) * temp + ) - fuel_demand_for_steam_enthalpy_change = \ + fuel_demand_for_steam_enthalpy_change = ( delta_H - recoverable_heat_before_economizer - recoverable_heat_before_preheater + ) fuel_consumption_for_steam_generation_mass = fuel_demand_for_steam_enthalpy_change / available_enthalpy - fuel_LHV = \ - gas_LHV if self.OTSG_fuel_type == "Gas" else self.oil.mass_energy_density(API=self.field.attr("API"), - with_unit=True) + fuel_LHV = ( + gas_LHV + if self.OTSG_fuel_type == "Gas" + else self.oil.mass_energy_density(API=self.field.attr("API"), with_unit=True) + ) fuel_consumption_for_steam_generation_energy = fuel_consumption_for_steam_generation_mass * fuel_LHV # mass and energy balance - air_in_mass_rate = \ - air_requirement_fuel.sum() * \ - self.gas.molar_weight_from_molar_fracs(self.inlet_air_comp) * \ - fuel_consumption_for_steam_generation_mass + air_in_mass_rate = ( + air_requirement_fuel.sum() + * self.gas.molar_weight_from_molar_fracs(self.inlet_air_comp) + * fuel_consumption_for_steam_generation_mass + ) air_in_mass_rate = air_in_mass_rate / gas_MW_combust if self.OTSG_fuel_type == "Gas" else air_in_mass_rate outlet_exhaust_mass = exhaust_consump_sum * exhaust_consump_MW * fuel_consumption_for_steam_generation_mass - outlet_exhaust_mass = \ + outlet_exhaust_mass = ( outlet_exhaust_mass / gas_MW_combust if self.OTSG_fuel_type == "Gas" else outlet_exhaust_mass + ) temp = delta_H - recoverable_heat_before_economizer - recoverable_heat_before_preheater H_eco = constant_before_economizer * temp H_heater = constant_before_preheater * temp H_outlet = constant_outlet * temp - mass_in = \ - fuel_consumption_for_steam_generation_mass + air_in_mass_rate + \ - prod_water_mass_rate + makeup_water_mass_rate + mass_in = ( + fuel_consumption_for_steam_generation_mass + + air_in_mass_rate + + prod_water_mass_rate + + makeup_water_mass_rate + ) mass_out = water_mass_rate_for_injection + blowdown_water_mass_rate + outlet_exhaust_mass - energy_in = \ - fuel_consumption_for_steam_generation_mass * gas_LHV + \ - air_in_mass_rate * air_requirement_LHV_stream + \ - prod_water_enthalpy_rate + makeup_water_enthalpy_rate - energy_out = \ - outlet_exhaust_mass * LHV_stream["outlet"] + \ - (H_heater - H_outlet - recoverable_heat_before_preheater) + \ - (H_eco - H_heater - recoverable_heat_before_economizer) + \ - fuel_consumption_for_steam_generation_mass * other_loss_per_unit_fuel + \ - fuel_consumption_for_steam_generation_mass * shell_loss_per_unit_fuel + \ - steam_out_enthalpy_rate - blowdown_water_recoverable_enthalpy_rate + energy_in = ( + fuel_consumption_for_steam_generation_mass * gas_LHV + + air_in_mass_rate * air_requirement_LHV_stream + + prod_water_enthalpy_rate + + makeup_water_enthalpy_rate + ) + energy_out = ( + outlet_exhaust_mass * LHV_stream["outlet"] + + (H_heater - H_outlet - recoverable_heat_before_preheater) + + (H_eco - H_heater - recoverable_heat_before_economizer) + + fuel_consumption_for_steam_generation_mass * other_loss_per_unit_fuel + + fuel_consumption_for_steam_generation_mass * shell_loss_per_unit_fuel + + steam_out_enthalpy_rate + - blowdown_water_recoverable_enthalpy_rate + ) return fuel_consumption_for_steam_generation_energy, mass_in, mass_out, energy_in, energy_out - def heat_recovery_SG(self, - prod_water_mass_rate, - makeup_water_mass_rate, - water_mass_rate_for_injection, - blowdown_water_mass_rate): - - prod_water_enthalpy_rate, makeup_water_enthalpy_rate, blowdown_water_recoverable_enthalpy_rate, steam_out_enthalpy_rate = \ - self.get_water_steam_enthalpy_rate(prod_water_mass_rate, - makeup_water_mass_rate, - water_mass_rate_for_injection, - blowdown_water_mass_rate) + def heat_recovery_SG( + self, prod_water_mass_rate, makeup_water_mass_rate, water_mass_rate_for_injection, blowdown_water_mass_rate + ): + ( + prod_water_enthalpy_rate, + makeup_water_enthalpy_rate, + blowdown_water_recoverable_enthalpy_rate, + steam_out_enthalpy_rate, + ) = self.get_water_steam_enthalpy_rate( + prod_water_mass_rate, makeup_water_mass_rate, water_mass_rate_for_injection, blowdown_water_mass_rate + ) # GT + HRSG combustion - gas_MW_combust, gas_LHV, \ - air_requirement_fuel, air_requirement_LHV_fuel, air_requirement_LHV_stream, \ - exhaust_consump, exhaust_consump_sum, exhaust_consump_MW = self.get_combustion_parameters("HRSG") - - temp = exhaust_consump * \ - self.gas.combustion_enthalpy(exhaust_consump, - self.gas_turbine_tlb["Turbine exhaust temp."][self.gas_turbine_type], - PHASE_GAS) + ( + gas_MW_combust, + gas_LHV, + air_requirement_fuel, + air_requirement_LHV_fuel, + air_requirement_LHV_stream, + exhaust_consump, + exhaust_consump_sum, + exhaust_consump_MW, + ) = self.get_combustion_parameters("HRSG") + + temp = exhaust_consump * self.gas.combustion_enthalpy( + exhaust_consump, self.gas_turbine_tlb["Turbine exhaust temp."][self.gas_turbine_type], PHASE_GAS + ) exhaust_consump_LHV_fuel = temp.sum() / gas_MW_combust exhaust_consump_LHV_stream = temp.sum() / exhaust_consump_MW / exhaust_consump_sum exhaust_consump = exhaust_consump.drop(labels=["C1"]) - inlet_temp = \ - self.duct_firing_inlet_temp if self.duct_firing else self.gas_turbine_tlb["Turbine exhaust temp."][ - self.gas_turbine_type] + inlet_temp = ( + self.duct_firing_inlet_temp + if self.duct_firing + else self.gas_turbine_tlb["Turbine exhaust temp."][self.gas_turbine_type] + ) if self.duct_firing: - inlet, inlet_sum, inlet_MW, inlet_LHV_fuel, inlet_LHV_stream, duct_additional_fuel = \ - self.get_HRSG_inlet_combustion(inlet_temp, gas_MW_combust, gas_LHV, exhaust_consump, - exhaust_consump_LHV_fuel) + inlet, inlet_sum, inlet_MW, inlet_LHV_fuel, inlet_LHV_stream, duct_additional_fuel = ( + self.get_HRSG_inlet_combustion( + inlet_temp, gas_MW_combust, gas_LHV, exhaust_consump, exhaust_consump_LHV_fuel + ) + ) else: - inlet, inlet_sum, inlet_MW, inlet_LHV_fuel, inlet_LHV_stream, duct_additional_fuel = \ - exhaust_consump, exhaust_consump_sum, exhaust_consump_MW, exhaust_consump_LHV_fuel, \ - exhaust_consump_LHV_stream, ureg.Quantity(0.0, "frac") - - LHV_fuel, LHV_stream = self.get_LHV_fuel_and_steam_series(inlet, - self.HRSG_exhaust_temp_series, - gas_MW_combust, - inlet_sum, - inlet_MW) + inlet, inlet_sum, inlet_MW, inlet_LHV_fuel, inlet_LHV_stream, duct_additional_fuel = ( + exhaust_consump, + exhaust_consump_sum, + exhaust_consump_MW, + exhaust_consump_LHV_fuel, + exhaust_consump_LHV_stream, + ureg.Quantity(0.0, "frac"), + ) + + LHV_fuel, LHV_stream = self.get_LHV_fuel_and_steam_series( + inlet, self.HRSG_exhaust_temp_series, gas_MW_combust, inlet_sum, inlet_MW + ) # recoverable heat in economizer - delta_H = \ - steam_out_enthalpy_rate - prod_water_enthalpy_rate - makeup_water_enthalpy_rate - \ - blowdown_water_recoverable_enthalpy_rate + delta_H = ( + steam_out_enthalpy_rate + - prod_water_enthalpy_rate + - makeup_water_enthalpy_rate + - blowdown_water_recoverable_enthalpy_rate + ) eta_eco = self.eta_economizer_heat_rec_HRSG eta_heater = self.eta_preheater_heat_rec_HRSG - frac_loss = \ + frac_loss = ( self.loss_shell_HRSG * (inlet_LHV_stream - LHV_stream["outlet_before_economizer"]) / inlet_LHV_stream + ) frac_exhaust = LHV_stream["outlet_before_economizer"] / inlet_LHV_stream frac_steam = 1 - frac_loss - frac_exhaust const_eco = LHV_stream["outlet_before_economizer"] / inlet_LHV_stream / frac_steam @@ -269,14 +313,16 @@ def heat_recovery_SG(self, denominator = 1 - eta_eco * const_heater + eta_eco * const_eco H_eco = const_eco * delta_H / denominator H_heater = const_heater * delta_H / denominator - recoverable_heat_before_economizer = ((H_eco - H_heater) * eta_eco if - self.economizer_HRSG else ureg.Quantity(0.0, "MJ/day")) + recoverable_heat_before_economizer = ( + (H_eco - H_heater) * eta_eco if self.economizer_HRSG else ureg.Quantity(0.0, "MJ/day") + ) H_fuel_inlet_HRSG = (delta_H - recoverable_heat_before_economizer) / frac_steam mass_fuel_inlet_HRSG = H_fuel_inlet_HRSG / inlet_LHV_stream - recoverable_heat_before_preheater = (mass_fuel_inlet_HRSG * - (LHV_stream["outlet"] - - LHV_stream["outlet_before_preheater"]) * - eta_heater if self.preheater_HRSG else ureg.Quantity(0.0, "MJ/day")) + recoverable_heat_before_preheater = ( + mass_fuel_inlet_HRSG * (LHV_stream["outlet"] - LHV_stream["outlet_before_preheater"]) * eta_heater + if self.preheater_HRSG + else ureg.Quantity(0.0, "MJ/day") + ) GT_frac_electricity = self.gas_turbine_tlb["Turbine efficiency"][self.gas_turbine_type] GT_frac_loss = self.gas_turbine_tlb["Turbine loss"][self.gas_turbine_type] @@ -294,32 +340,42 @@ def heat_recovery_SG(self, # balance check mass_air_GT = mass_exhaust_GT - mass_fuel_inlet_GT H_air_GT = mass_air_GT * air_requirement_LHV_stream - mass_in = \ + mass_in = ( mass_air_GT + mass_fuel_inlet_GT + mass_fuel_inlet_HRSG + prod_water_mass_rate + makeup_water_mass_rate + ) mass_out = mass_exhaust_GT + mass_fuel_inlet_HRSG + water_mass_rate_for_injection - energy_in = \ - H_air_GT + H_fuel_inlet_GT + recoverable_heat_before_preheater + \ - prod_water_enthalpy_rate + makeup_water_enthalpy_rate + H_fuel_inlet_HRSG - energy_out = \ - H_electricity_GT + H_exhaust_GT + H_loss_GT + H_eco - recoverable_heat_before_economizer + \ - H_fuel_inlet_HRSG * frac_loss + steam_out_enthalpy_rate - blowdown_water_recoverable_enthalpy_rate + energy_in = ( + H_air_GT + + H_fuel_inlet_GT + + recoverable_heat_before_preheater + + prod_water_enthalpy_rate + + makeup_water_enthalpy_rate + + H_fuel_inlet_HRSG + ) + energy_out = ( + H_electricity_GT + + H_exhaust_GT + + H_loss_GT + + H_eco + - recoverable_heat_before_economizer + + H_fuel_inlet_HRSG * frac_loss + + steam_out_enthalpy_rate + - blowdown_water_recoverable_enthalpy_rate + ) return total_fuel_consumption, H_electricity_GT, mass_in, mass_out, energy_in, energy_out - def solar_SG(self, - prod_water_mass_rate, - makeup_water_mass_rate): - - prod_water_enthalpy_rate = self.water.enthalpy_PT(self.prod_water_inlet_press, - self.field.attr("prod_water_inlet_temp"), - prod_water_mass_rate) - makeup_water_enthalpy_rate = self.water.enthalpy_PT(self.makeup_water_inlet_press, - self.makeup_water_inlet_temp, - makeup_water_mass_rate) + def solar_SG(self, prod_water_mass_rate, makeup_water_mass_rate): + prod_water_enthalpy_rate = self.water.enthalpy_PT( + self.prod_water_inlet_press, self.field.attr("prod_water_inlet_temp"), prod_water_mass_rate + ) + makeup_water_enthalpy_rate = self.water.enthalpy_PT( + self.makeup_water_inlet_press, self.makeup_water_inlet_temp, makeup_water_mass_rate + ) desired_steam_mass_rate = prod_water_mass_rate + makeup_water_mass_rate - desired_steam_enthalpy_rate = self.water.steam_enthalpy(self.steam_generator_press_outlet, - self.steam_quality_outlet, - desired_steam_mass_rate) + desired_steam_enthalpy_rate = self.water.steam_enthalpy( + self.steam_generator_press_outlet, self.steam_quality_outlet, desired_steam_mass_rate + ) H_solar_inlet = desired_steam_enthalpy_rate - prod_water_enthalpy_rate - makeup_water_enthalpy_rate return H_solar_inlet @@ -373,27 +429,33 @@ def get_air_requirement(self, gas_MW_combust, SG_type): if SG_type == "OTSG": if self.OTSG_fuel_type == "Gas": - air_requirement_fuel = \ - (self.OTSG_frac_import_gas * self.import_gas_reactants_comp + - self.OTSG_frac_prod_gas * self.prod_gas_reactants_comp) * self.O2_excess_OTSG + air_requirement_fuel = ( + self.OTSG_frac_import_gas * self.import_gas_reactants_comp + + self.OTSG_frac_prod_gas * self.prod_gas_reactants_comp + ) * self.O2_excess_OTSG else: - air_requirement_fuel = \ - (liquid_fuel_comp["C"] + 0.25 * liquid_fuel_comp["H"] + liquid_fuel_comp["S"]) * \ - self.O2_excess_OTSG / self.inlet_air_comp["O2"] * self.inlet_air_comp + air_requirement_fuel = ( + (liquid_fuel_comp["C"] + 0.25 * liquid_fuel_comp["H"] + liquid_fuel_comp["S"]) + * self.O2_excess_OTSG + / self.inlet_air_comp["O2"] + * self.inlet_air_comp + ) else: - air_requirement_fuel = \ - (self.HRSG_frac_import_gas * self.import_gas_reactants_comp + - self.HRSG_frac_prod_gas * self.prod_gas_reactants_comp) * self.O2_excess_HRSG + air_requirement_fuel = ( + self.HRSG_frac_import_gas * self.import_gas_reactants_comp + + self.HRSG_frac_prod_gas * self.prod_gas_reactants_comp + ) * self.O2_excess_HRSG air_requirement_fuel_sum = ureg.Quantity(air_requirement_fuel.sum(), "percent") air_requirement_fuel["C1"] = 100 air_requirement_fuel = pd.Series(air_requirement_fuel, dtype="pint[percent]") - air_requirement_MW = \ - self.gas.molar_weight_from_molar_fracs(air_requirement_fuel.drop(labels=["C1"])) / \ - air_requirement_fuel.drop(labels=["C1"]).sum() - temp = \ - air_requirement_fuel * \ - self.gas.combustion_enthalpy(air_requirement_fuel, self.temperature_inlet_air_OTSG, PHASE_GAS) + air_requirement_MW = ( + self.gas.molar_weight_from_molar_fracs(air_requirement_fuel.drop(labels=["C1"])) + / air_requirement_fuel.drop(labels=["C1"]).sum() + ) + temp = air_requirement_fuel * self.gas.combustion_enthalpy( + air_requirement_fuel, self.temperature_inlet_air_OTSG, PHASE_GAS + ) air_requirement_LHV_fuel = temp.sum() / gas_MW_combust air_requirement_LHV_stream = temp.sum() / air_requirement_MW / air_requirement_fuel_sum air_requirement_fuel = air_requirement_fuel.drop(labels=["C1"]) @@ -401,36 +463,40 @@ def get_air_requirement(self, gas_MW_combust, SG_type): return air_requirement_fuel, air_requirement_LHV_fuel, air_requirement_LHV_stream def get_exhaust_parameters(self, air_requirement_fuel, SG_type): - liquid_fuel_comp = self.oil.liquid_fuel_composition(self.field.attr("API")) if SG_type == "OTSG": if self.OTSG_fuel_type == "Gas": - exhaust_consump = (self.OTSG_frac_import_gas * self.import_gas_products_comp + - self.OTSG_frac_prod_gas * self.prod_gas_products_comp) + ( - self.O2_excess_OTSG - 1) / self.O2_excess_OTSG * air_requirement_fuel + exhaust_consump = ( + self.OTSG_frac_import_gas * self.import_gas_products_comp + + self.OTSG_frac_prod_gas * self.prod_gas_products_comp + ) + (self.O2_excess_OTSG - 1) / self.O2_excess_OTSG * air_requirement_fuel else: exhaust_consump = air_requirement_fuel + 0.5 * liquid_fuel_comp - exhaust_consump["O2"] = air_requirement_fuel["O2"] - liquid_fuel_comp["C"] - 0.25 * \ - liquid_fuel_comp["H"] - liquid_fuel_comp["S"] + exhaust_consump["O2"] = ( + air_requirement_fuel["O2"] + - liquid_fuel_comp["C"] + - 0.25 * liquid_fuel_comp["H"] + - liquid_fuel_comp["S"] + ) exhaust_consump["CO2"] = air_requirement_fuel["CO2"] + liquid_fuel_comp["C"] else: - exhaust_consump = \ - (self.HRSG_frac_import_gas * self.import_gas_products_comp + - self.HRSG_frac_prod_gas * self.prod_gas_products_comp) + \ - (self.O2_excess_HRSG - 1) / self.O2_excess_HRSG * air_requirement_fuel + exhaust_consump = ( + self.HRSG_frac_import_gas * self.import_gas_products_comp + + self.HRSG_frac_prod_gas * self.prod_gas_products_comp + ) + (self.O2_excess_HRSG - 1) / self.O2_excess_HRSG * air_requirement_fuel exhaust_consump_sum = exhaust_consump.sum() exhaust_consump["C1"] = ureg.Quantity(100.0, "percent") exhaust_consump = pd.Series(exhaust_consump, dtype="pint[percent]") - exhaust_consump_MW = \ - self.gas.molar_weight_from_molar_fracs(exhaust_consump.drop(labels=["C1"])) / \ - exhaust_consump.drop(labels=["C1"]).sum() + exhaust_consump_MW = ( + self.gas.molar_weight_from_molar_fracs(exhaust_consump.drop(labels=["C1"])) + / exhaust_consump.drop(labels=["C1"]).sum() + ) return exhaust_consump, exhaust_consump_sum, exhaust_consump_MW def get_HRSG_inlet_combustion(self, inlet_temp, gas_MW_combust, gas_LHV, exhaust_consump, exhaust_consump_LHV_fuel): - - tolerance = 1E-6 + tolerance = 1e-6 duct_additional_fuel = 0.4 delta_error = duct_additional_fuel max_iter = 1000 @@ -438,22 +504,25 @@ def get_HRSG_inlet_combustion(self, inlet_temp, gas_MW_combust, gas_LHV, exhaust while (delta_error > tolerance and counter < max_iter) or counter == 0: O2_excess_duct = (self.O2_excess_HRSG.m - 1) / duct_additional_fuel - HRSG_inlet = \ - (self.HRSG_frac_import_gas * self.import_gas_products_comp + - self.HRSG_frac_prod_gas * self.prod_gas_products_comp) + \ - (O2_excess_duct - 1) / O2_excess_duct * exhaust_consump + HRSG_inlet = ( + self.HRSG_frac_import_gas * self.import_gas_products_comp + + self.HRSG_frac_prod_gas * self.prod_gas_products_comp + ) + (O2_excess_duct - 1) / O2_excess_duct * exhaust_consump HRSG_inlet_sum = HRSG_inlet.sum() HRSG_inlet["C1"] = ureg.Quantity(100.0, "percent") HRSG_inlet = pd.Series(HRSG_inlet, dtype="pint[percent]") - HRSG_inlet_MW = self.gas.molar_weight_from_molar_fracs( - HRSG_inlet.drop(labels=["C1"])) / HRSG_inlet.drop(labels=["C1"]).sum() + HRSG_inlet_MW = ( + self.gas.molar_weight_from_molar_fracs(HRSG_inlet.drop(labels=["C1"])) + / HRSG_inlet.drop(labels=["C1"]).sum() + ) temp = HRSG_inlet * self.gas.combustion_enthalpy(HRSG_inlet, inlet_temp, PHASE_GAS) HRSG_inlet_LHV_fuel = temp.sum() / gas_MW_combust HRSG_inlet_LHV_stream = temp.sum() / HRSG_inlet_MW / HRSG_inlet_sum - duct_additional_fuel_new = \ - (HRSG_inlet_LHV_fuel.to("MJ/kg").m - exhaust_consump_LHV_fuel.to("MJ/kg").m) / gas_LHV.to("MJ/kg").m + duct_additional_fuel_new = ( + HRSG_inlet_LHV_fuel.to("MJ/kg").m - exhaust_consump_LHV_fuel.to("MJ/kg").m + ) / gas_LHV.to("MJ/kg").m delta_error = (duct_additional_fuel_new - duct_additional_fuel) ** 2 duct_additional_fuel = duct_additional_fuel_new @@ -464,13 +533,18 @@ def get_HRSG_inlet_combustion(self, inlet_temp, gas_MW_combust, gas_LHV, exhaust else: HRSG_inlet = HRSG_inlet.drop(labels=["C1"]) - return HRSG_inlet, HRSG_inlet_sum, HRSG_inlet_MW, HRSG_inlet_LHV_fuel, HRSG_inlet_LHV_stream, duct_additional_fuel - - def get_water_steam_enthalpy_rate(self, - prod_water_mass_rate, - makeup_water_mass_rate, - water_mass_rate_for_injection, - blowdown_water_mass_rate): + return ( + HRSG_inlet, + HRSG_inlet_sum, + HRSG_inlet_MW, + HRSG_inlet_LHV_fuel, + HRSG_inlet_LHV_stream, + duct_additional_fuel, + ) + + def get_water_steam_enthalpy_rate( + self, prod_water_mass_rate, makeup_water_mass_rate, water_mass_rate_for_injection, blowdown_water_mass_rate + ): """ Calculate water and steam enthalpy rate given water mass rate @@ -481,36 +555,44 @@ def get_water_steam_enthalpy_rate(self, :return: """ - prod_water_enthalpy_rate = self.water.enthalpy_PT(self.prod_water_inlet_press, - self.field.attr("prod_water_inlet_temp"), - prod_water_mass_rate) - makeup_water_enthalpy_rate = self.water.enthalpy_PT(self.makeup_water_inlet_press, - self.makeup_water_inlet_temp, - makeup_water_mass_rate) + prod_water_enthalpy_rate = self.water.enthalpy_PT( + self.prod_water_inlet_press, self.field.attr("prod_water_inlet_temp"), prod_water_mass_rate + ) + makeup_water_enthalpy_rate = self.water.enthalpy_PT( + self.makeup_water_inlet_press, self.makeup_water_inlet_temp, makeup_water_mass_rate + ) desired_steam_mass_rate = water_mass_rate_for_injection - desired_steam_enthalpy_rate = self.water.steam_enthalpy(self.steam_generator_press_outlet, - self.steam_quality_outlet, - desired_steam_mass_rate) + desired_steam_enthalpy_rate = self.water.steam_enthalpy( + self.steam_generator_press_outlet, self.steam_quality_outlet, desired_steam_mass_rate + ) blowdown_mass_rate = blowdown_water_mass_rate steam_generator_temp_outlet = self.water.saturated_temperature(self.steam_generator_press_outlet) - blowdown_water_recoverable_enthalpy_rate = blowdown_before_heat_recovery_enthalpy_rate = ureg.Quantity(0, "MJ/day") + blowdown_water_recoverable_enthalpy_rate = blowdown_before_heat_recovery_enthalpy_rate = ureg.Quantity( + 0, "MJ/day" + ) if blowdown_mass_rate.m != 0: - blowdown_before_heat_recovery_enthalpy_rate = self.water.enthalpy_PT(self.steam_generator_press_outlet, - steam_generator_temp_outlet, - blowdown_mass_rate) - blowdown_after_heat_recovery_enthalpy_rate = self.water.enthalpy_PT(self.waste_water_reinjection_press, - self.waste_water_reinjection_temp, - blowdown_mass_rate) - blowdown_water_recoverable_enthalpy_rate = self.eta_blowdown_heat_rec_HRSG * \ - (blowdown_before_heat_recovery_enthalpy_rate - - blowdown_after_heat_recovery_enthalpy_rate) \ - if self.blowdown_heat_recovery else ureg.Quantity(0.0, "MJ/day") + blowdown_before_heat_recovery_enthalpy_rate = self.water.enthalpy_PT( + self.steam_generator_press_outlet, steam_generator_temp_outlet, blowdown_mass_rate + ) + blowdown_after_heat_recovery_enthalpy_rate = self.water.enthalpy_PT( + self.waste_water_reinjection_press, self.waste_water_reinjection_temp, blowdown_mass_rate + ) + blowdown_water_recoverable_enthalpy_rate = ( + self.eta_blowdown_heat_rec_HRSG + * (blowdown_before_heat_recovery_enthalpy_rate - blowdown_after_heat_recovery_enthalpy_rate) + if self.blowdown_heat_recovery + else ureg.Quantity(0.0, "MJ/day") + ) steam_out_enthalpy_rate = desired_steam_enthalpy_rate + blowdown_before_heat_recovery_enthalpy_rate - return prod_water_enthalpy_rate, makeup_water_enthalpy_rate, \ - blowdown_water_recoverable_enthalpy_rate, steam_out_enthalpy_rate + return ( + prod_water_enthalpy_rate, + makeup_water_enthalpy_rate, + blowdown_water_recoverable_enthalpy_rate, + steam_out_enthalpy_rate, + ) def get_combustion_parameters(self, SG_type): """ @@ -523,8 +605,9 @@ def get_combustion_parameters(self, SG_type): processed_prod_gas_comp = self.processed_prod_gas_comp exported_gas_stream = self.field.get_process_data("exported_gas") if exported_gas_stream and exported_gas_stream.total_flow_rate().m != 0.0: - exported_gas_comp = self.gas.component_molar_fractions(exported_gas_stream, - self.imported_fuel_gas_comp.index) + exported_gas_comp = self.gas.component_molar_fractions( + exported_gas_stream, self.imported_fuel_gas_comp.index + ) processed_prod_gas_comp = exported_gas_comp frac_import_gas = self.OTSG_frac_import_gas if SG_type == "OTSG" else self.HRSG_frac_import_gas @@ -533,11 +616,20 @@ def get_combustion_parameters(self, SG_type): gas_MW_combust = self.gas.molar_weight_from_molar_fracs(gas_combusted) gas_LHV = self.gas.mass_energy_density_from_molar_fracs(gas_combusted) - air_requirement_fuel, air_requirement_LHV_fuel, air_requirement_LHV_stream = \ - self.get_air_requirement(gas_MW_combust, SG_type) + air_requirement_fuel, air_requirement_LHV_fuel, air_requirement_LHV_stream = self.get_air_requirement( + gas_MW_combust, SG_type + ) exhaust_consump, exhaust_consump_sum, exhaust_consump_MW = self.get_exhaust_parameters( - air_requirement_fuel, SG_type) - - return gas_MW_combust, gas_LHV, \ - air_requirement_fuel, air_requirement_LHV_fuel, air_requirement_LHV_stream, \ - exhaust_consump, exhaust_consump_sum, exhaust_consump_MW + air_requirement_fuel, SG_type + ) + + return ( + gas_MW_combust, + gas_LHV, + air_requirement_fuel, + air_requirement_LHV_fuel, + air_requirement_LHV_stream, + exhaust_consump, + exhaust_consump_sum, + exhaust_consump_MW, + ) diff --git a/opgee/processes/storage_compressor.py b/opgee/processes/storage_compressor.py index 8df4b4c7..a041b37a 100644 --- a/opgee/processes/storage_compressor.py +++ b/opgee/processes/storage_compressor.py @@ -20,6 +20,7 @@ class StorageCompressor(Process): """ Storage compressor calculate emission from compressing gas for long-term (i.e., seasonal) storage. """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -55,13 +56,9 @@ def run(self, analysis): gas_fugitives = self.set_gas_fugitives(input, loss_rate) overall_compression_ratio = self.discharge_press / input.tp.P - energy_consumption, output_temp, output_press = \ - Compressor.get_compressor_energy_consumption( - self.field, - self.prime_mover_type, - self.eta_compressor, - overall_compression_ratio, - input) + energy_consumption, output_temp, output_press = Compressor.get_compressor_energy_consumption( + self.field, self.prime_mover_type, self.eta_compressor, overall_compression_ratio, input + ) # energy-use energy_use = self.energy diff --git a/opgee/processes/storage_separator.py b/opgee/processes/storage_separator.py index 316c463b..52ffa335 100644 --- a/opgee/processes/storage_separator.py +++ b/opgee/processes/storage_separator.py @@ -18,6 +18,7 @@ class StorageSeparator(Process): """ Storage well calculate fugitive emission from storage wells. """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -52,8 +53,4 @@ def run(self, analysis): gas_to_compressor = self.find_output_stream("gas") gas_to_compressor.copy_gas_rates_from(input, tp=self.outlet_tp) - #TODO: Future versions of OPGEE may treat this process in more detail. - - - - + # TODO: Future versions of OPGEE may treat this process in more detail. diff --git a/opgee/processes/storage_well.py b/opgee/processes/storage_well.py index 033b156e..825a5abd 100644 --- a/opgee/processes/storage_well.py +++ b/opgee/processes/storage_well.py @@ -18,6 +18,7 @@ class StorageWell(Process): Storage well calculate fugitive emission from storage wells. """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) diff --git a/opgee/processes/transmission_compressor.py b/opgee/processes/transmission_compressor.py index 4f723a5b..94b3a4db 100644 --- a/opgee/processes/transmission_compressor.py +++ b/opgee/processes/transmission_compressor.py @@ -17,10 +17,12 @@ _logger = getLogger(__name__) + class TransmissionCompressor(Process): """ Transmission compressor calculate compressor emissions after the production site boundary. """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -76,29 +78,24 @@ def run(self, analysis): # initial compressor properties overall_compression_ratio_init = station_outlet_press.to("psi_absolute") / input.tp.P - energy_consumption_init, output_temp_init, output_press_init = \ - Compressor.get_compressor_energy_consumption( - self.field, - self.prime_mover_type, - self.eta_compressor, - overall_compression_ratio_init, - input) + energy_consumption_init, output_temp_init, output_press_init = Compressor.get_compressor_energy_consumption( + self.field, self.prime_mover_type, self.eta_compressor, overall_compression_ratio_init, input + ) # Along-pipeline booster compressor properties overall_compression_ratio_booster = station_outlet_press.to("psi_absolute") / self.transmission_inlet_press - energy_consumption_booster, output_temp_booster, output_press_booster = \ + energy_consumption_booster, output_temp_booster, output_press_booster = ( Compressor.get_compressor_energy_consumption( - self.field, - self.prime_mover_type, - self.eta_compressor, - overall_compression_ratio_booster, - input) + self.field, self.prime_mover_type, self.eta_compressor, overall_compression_ratio_booster, input + ) + ) # energy-use energy_use = self.energy energy_carrier = get_energy_carrier(self.prime_mover_type) - energy_use.set_rate(energy_carrier, energy_consumption_init + - energy_consumption_booster * num_compressor_stations) + energy_use.set_rate( + energy_carrier, energy_consumption_init + energy_consumption_booster * num_compressor_stations + ) # import/export self.set_import_from_energy(energy_use) diff --git a/opgee/processes/transport_energy.py b/opgee/processes/transport_energy.py index e8c160d0..640a521f 100644 --- a/opgee/processes/transport_energy.py +++ b/opgee/processes/transport_energy.py @@ -24,23 +24,19 @@ def __init__(self, field): self.residual_oil_LHV = field.model.const("residual-oil-LHV") self.residual_oil_density = field.model.const("residual-oil-density") - def get_transport_energy_dict(self, - field, - parameter_table, - transport_share_fuel, - transport_by_mode, - LHV_rate, - prod_type): + def get_transport_energy_dict( + self, field, parameter_table, transport_share_fuel, transport_by_mode, LHV_rate, prod_type + ): """ - Calculate transport energy consumption and return fuel consumption for different transport modes. - - :param field: Field object containing process data - :param parameter_table: DataFrame with transport parameters - :param transport_share_fuel: DataFrame with transport fuel share - :param transport_by_mode: DataFrame with transport fractions and distances - :param LHV_rate: LHV rate for the product type - :param prod_type: , can be "diluent", "lng", "crude", or "petrocoke" - :return: Fuel consumption for different transport modes + Calculate transport energy consumption and return fuel consumption for different transport modes. + + :param field: Field object containing process data + :param parameter_table: DataFrame with transport parameters + :param transport_share_fuel: DataFrame with transport fuel share + :param transport_by_mode: DataFrame with transport fractions and distances + :param LHV_rate: LHV rate for the product type + :param prod_type: , can be "diluent", "lng", "crude", or "petrocoke" + :return: Fuel consumption for different transport modes """ known_types = ["diluent", "lng", "crude", "petrocoke"] prod_type = prod_type.lower() @@ -68,55 +64,61 @@ def get_transport_energy_dict(self, transport_distance = transport_by_mode["Distance"] # Calculate transport energy intensity - ocean_tanker_orig_dest_energy_intensity = \ - self.transport_energy_intensity( - ocean_tanker_load_factor_dest, - "tanker", - ocean_tanker_speed=ocean_tanker_speed, - ocean_tanker_size=ocean_tanker_size, - ) - - ocean_tanker_dest_orig_energy_intensity = \ - self.transport_energy_intensity( - ocean_tanker_load_factor_origin, - "tanker", - ocean_tanker_speed=ocean_tanker_speed, - ocean_tanker_size=ocean_tanker_size, - ) - - barge_orig_dest_energy_intensity = \ - self.transport_energy_intensity( - barge_load_factor_dest, - "barge", - barge_capacity=barge_capacity, - barge_speed=barge_speed, - ) - - barge_dest_orig_energy_intensity = \ - self.transport_energy_intensity( - barge_load_factor_origin, - "barge", - barge_capacity=barge_capacity, - barge_speed=barge_speed, - ) - - pipeline_orig_dest_energy_intensity = (energy_intensity_pipeline_turbine * - frac_power_pipeline_turbine + - energy_intensity_pipeline_engine_current * - frac_power_pipeline_engine_current + - energy_intensity_pipeline_engine_future * - frac_power_pipeline_engine_future) - - - transport_orig_dest_energy_consumption = \ - pd.Series([ocean_tanker_orig_dest_energy_intensity, barge_orig_dest_energy_intensity, - pipeline_orig_dest_energy_intensity, energy_intensity_rail_transport, - self.energy_intensity_truck], dtype="pint[btu/tonne/mile]") - - transport_dest_origin_energy_consumption = \ - pd.Series([ocean_tanker_dest_orig_energy_intensity, barge_dest_orig_energy_intensity, - self.pipeline_dest_orig_energy_intensity, self.rail_dest_orig_energy_intensity, - self.truck_dest_orig_energy_intensity], dtype="pint[btu/tonne/mile]") + ocean_tanker_orig_dest_energy_intensity = self.transport_energy_intensity( + ocean_tanker_load_factor_dest, + "tanker", + ocean_tanker_speed=ocean_tanker_speed, + ocean_tanker_size=ocean_tanker_size, + ) + + ocean_tanker_dest_orig_energy_intensity = self.transport_energy_intensity( + ocean_tanker_load_factor_origin, + "tanker", + ocean_tanker_speed=ocean_tanker_speed, + ocean_tanker_size=ocean_tanker_size, + ) + + barge_orig_dest_energy_intensity = self.transport_energy_intensity( + barge_load_factor_dest, + "barge", + barge_capacity=barge_capacity, + barge_speed=barge_speed, + ) + + barge_dest_orig_energy_intensity = self.transport_energy_intensity( + barge_load_factor_origin, + "barge", + barge_capacity=barge_capacity, + barge_speed=barge_speed, + ) + + pipeline_orig_dest_energy_intensity = ( + energy_intensity_pipeline_turbine * frac_power_pipeline_turbine + + energy_intensity_pipeline_engine_current * frac_power_pipeline_engine_current + + energy_intensity_pipeline_engine_future * frac_power_pipeline_engine_future + ) + + transport_orig_dest_energy_consumption = pd.Series( + [ + ocean_tanker_orig_dest_energy_intensity, + barge_orig_dest_energy_intensity, + pipeline_orig_dest_energy_intensity, + energy_intensity_rail_transport, + self.energy_intensity_truck, + ], + dtype="pint[btu/tonne/mile]", + ) + + transport_dest_origin_energy_consumption = pd.Series( + [ + ocean_tanker_dest_orig_energy_intensity, + barge_dest_orig_energy_intensity, + self.pipeline_dest_orig_energy_intensity, + self.rail_dest_orig_energy_intensity, + self.truck_dest_orig_energy_intensity, + ], + dtype="pint[btu/tonne/mile]", + ) if prod_type == "diluent": denominator = field.get_process_data("final_diluent_LHV_mass") @@ -127,26 +129,27 @@ def get_transport_energy_dict(self, else: denominator = field.model.const("petrocoke-heating-value") / 1.10231 - transport_energy_consumption = \ - (transport_orig_dest_energy_consumption + transport_dest_origin_energy_consumption) / denominator + transport_energy_consumption = ( + transport_orig_dest_energy_consumption + transport_dest_origin_energy_consumption + ) / denominator - fuel_consumption = \ - TransportEnergy.fuel_consumption( - fraction_transport, - transport_distance, - transport_share_fuel, - transport_energy_consumption, - feed_loss, - LHV_rate) + fuel_consumption = TransportEnergy.fuel_consumption( + fraction_transport, + transport_distance, + transport_share_fuel, + transport_energy_consumption, + feed_loss, + LHV_rate, + ) return fuel_consumption def get_ocean_tanker_dest_energy_intensity(self, parameter_table): """ - Calculate the energy intensity for ocean tankers from origin to destination. + Calculate the energy intensity for ocean tankers from origin to destination. - :param parameter_table: DataFrame with transport parameters - :return: Energy intensity for ocean tankers from origin to destination (unit: btu/tonne/mile) + :param parameter_table: DataFrame with transport parameters + :return: Energy intensity for ocean tankers from origin to destination (unit: btu/tonne/mile) """ field = self.field @@ -156,17 +159,15 @@ def get_ocean_tanker_dest_energy_intensity(self, parameter_table): ocean_tanker_speed = parameter_dict["speed_tanker"] ocean_tanker_size = field.ocean_tanker_size - ocean_tanker_orig_dest_energy_intensity = \ - self.transport_energy_intensity( - ocean_tanker_load_factor_dest, - "tanker", - ocean_tanker_speed=ocean_tanker_speed, - ocean_tanker_size=ocean_tanker_size, - ) + ocean_tanker_orig_dest_energy_intensity = self.transport_energy_intensity( + ocean_tanker_load_factor_dest, + "tanker", + ocean_tanker_speed=ocean_tanker_speed, + ocean_tanker_size=ocean_tanker_size, + ) return ocean_tanker_orig_dest_energy_intensity - @staticmethod def get_parameter_dict(parameter_table): """ @@ -178,13 +179,15 @@ def get_parameter_dict(parameter_table): parameter_value = parameter_table.iloc[:, 0] parameter_unit = parameter_table["Units"] - parameter_dict = {name : ureg.Quantity(float(value), parameter_unit[name]) - for name, value in parameter_value.items()} + parameter_dict = { + name: ureg.Quantity(float(value), parameter_unit[name]) for name, value in parameter_value.items() + } return parameter_dict - def transport_energy_intensity(self, load_factor, type, ocean_tanker_speed=None, - ocean_tanker_size=None, barge_capacity=None, barge_speed=None): + def transport_energy_intensity( + self, load_factor, type, ocean_tanker_speed=None, ocean_tanker_size=None, barge_capacity=None, barge_speed=None + ): """ Calculate transport energy intensity using load factor and water transport energy consumption. @@ -210,8 +213,11 @@ def transport_energy_intensity(self, load_factor, type, ocean_tanker_speed=None, energy_consumption = (14.42 / load_factor.m + const) * 0.735 * residual_oil_LHV.m / residual_oil_density.m energy_consumption = ureg.Quantity(energy_consumption, "btu/hp/hr") - hp =\ - ureg.Quantity(9070 + 0.101 * ocean_tanker_size.m, "hp") if type == "tanker" else ureg.Quantity(5600 / 22500 * barge_capacity.m, "hp") + hp = ( + ureg.Quantity(9070 + 0.101 * ocean_tanker_size.m, "hp") + if type == "tanker" + else ureg.Quantity(5600 / 22500 * barge_capacity.m, "hp") + ) common = energy_consumption * load_factor * hp if type == "tanker": @@ -221,28 +227,25 @@ def transport_energy_intensity(self, load_factor, type, ocean_tanker_speed=None, return result @staticmethod - def fuel_consumption(fraction_transport, - transport_distance, - transport_share_fuel, - transport_energy_consumption, - feed_loss, - LHV): + def fuel_consumption( + fraction_transport, transport_distance, transport_share_fuel, transport_energy_consumption, feed_loss, LHV + ): """ - Calculate fuel consumption for different transport types. - - :param fraction_transport: Series with fractions of transport types - :param transport_distance: Series with transport distances for each transport type - :param transport_share_fuel: Dictionary with fuel shares for each transport type - :param transport_energy_consumption: Series with transport energy consumption for each transport type - :param feed_loss: float, feed loss during transportation - :param LHV: float, lower heating value of the fuel - :return: Dictionary with fuel consumption for each transport type + Calculate fuel consumption for different transport types. + + :param fraction_transport: Series with fractions of transport types + :param transport_distance: Series with transport distances for each transport type + :param transport_share_fuel: Dictionary with fuel shares for each transport type + :param transport_energy_consumption: Series with transport energy consumption for each transport type + :param feed_loss: float, feed loss during transportation + :param LHV: float, lower heating value of the fuel + :return: Dictionary with fuel consumption for each transport type """ transport_energy_consumption.index = transport_distance.index result = { - type: (transport_energy_consumption * transport_distance * fraction_transport * frac).sum() * LHV + ( - LHV * feed_loss if type == EN_DIESEL else 0) + type: (transport_energy_consumption * transport_distance * fraction_transport * frac).sum() * LHV + + (LHV * feed_loss if type == EN_DIESEL else 0) for type, frac in transport_share_fuel.items() } return result diff --git a/opgee/processes/venting.py b/opgee/processes/venting.py index 31bdaf2a..6ffd3128 100644 --- a/opgee/processes/venting.py +++ b/opgee/processes/venting.py @@ -46,10 +46,9 @@ def __init__(self, name, **kwargs): def cache_attributes(self): field = self.field - #TODO: give warning when frac_venting is not within [0, 1] + # TODO: give warning when frac_venting is not within [0, 1] frac = field.frac_venting - self.frac_venting = min(ureg.Quantity(1., "frac"), - max(frac, ureg.Quantity(0., "frac"))) + self.frac_venting = min(ureg.Quantity(1.0, "frac"), max(frac, ureg.Quantity(0.0, "frac"))) self.pipe_leakage = field.pipe_leakage self.gas_lifting = field.gas_lifting @@ -75,12 +74,16 @@ def run(self, analysis): return methane_to_venting = input.gas_flow_rate("C1") * self.frac_venting - venting_frac = \ - methane_to_venting / input.gas_flow_rate("C1") \ - if input.gas_flow_rate("C1").m != 0 else ureg.Quantity(0, "frac") - fugitive_frac = \ - self.pipe_leakage / input.gas_flow_rate("C1") \ - if input.gas_flow_rate("C1").m != 0 else ureg.Quantity(0, "frac") + venting_frac = ( + methane_to_venting / input.gas_flow_rate("C1") + if input.gas_flow_rate("C1").m != 0 + else ureg.Quantity(0, "frac") + ) + fugitive_frac = ( + self.pipe_leakage / input.gas_flow_rate("C1") + if input.gas_flow_rate("C1").m != 0 + else ureg.Quantity(0, "frac") + ) gas_to_vent = Stream("venting_gas", tp=field.stp) gas_to_vent.copy_flow_rates_from(input, tp=field.stp) diff --git a/opgee/processes/water_injection.py b/opgee/processes/water_injection.py index 65d6ca4e..bbbcb47f 100644 --- a/opgee/processes/water_injection.py +++ b/opgee/processes/water_injection.py @@ -26,14 +26,15 @@ class WaterInjection(Process): """ - TBD + TBD - input streams: - - + input streams: + - - output streams: - - + output streams: + - """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -101,12 +102,18 @@ def run(self, analysis): water_gravitation_head = self.water_density * self.gravitation_acc * self.depth water_flow_velocity = single_well_water_volume / self.xsection_area - friction_loss = (self.friction_factor * self.depth * water_flow_velocity ** 2) / \ - (2 * self.prod_tubing_diam * self.gravitation_const) * self.water_density + friction_loss = ( + (self.friction_factor * self.depth * water_flow_velocity**2) + / (2 * self.prod_tubing_diam * self.gravitation_const) + * self.water_density + ) diff_press = wellbore_flowing_press - water_gravitation_head - pumping_press = diff_press + friction_loss - self.press_pump \ - if diff_press + friction_loss >= 0 else ureg.Quantity(0.0, "psia") + pumping_press = ( + diff_press + friction_loss - self.press_pump + if diff_press + friction_loss >= 0 + else ureg.Quantity(0.0, "psia") + ) pumping_hp_single_well = pumping_press * single_well_water_volume / self.eta_pump # energy-use diff --git a/opgee/processes/water_treatment.py b/opgee/processes/water_treatment.py index 4dfdbcf9..5790c257 100644 --- a/opgee/processes/water_treatment.py +++ b/opgee/processes/water_treatment.py @@ -19,14 +19,15 @@ class WaterTreatment(Process): """ - The water treatment process ... + The water treatment process ... - Inputs: - - A + Inputs: + - A - Outputs: - - B + Outputs: + - B """ + def __init__(self, name, **kwargs): super().__init__(name, **kwargs) @@ -38,10 +39,12 @@ def __init__(self, name, **kwargs): field = self.field if field.steam_flooding: - self._required_outputs.extend([ - "makeup water", - "produced water", - ]) + self._required_outputs.extend( + [ + "makeup water", + "produced water", + ] + ) if field.water_flooding or field.water_reinjection: self._required_outputs.append("water") @@ -88,8 +91,7 @@ def cache_attributes(self): self.makeup_water_treatment = None self.makeup_water_treatment_tbl = self.attr("makeup_water_treatment_table") - self.makeup_water_tp = TemperaturePressure(self.attr("makeup_water_temp"), - self.attr("makeup_water_press")) + self.makeup_water_tp = TemperaturePressure(self.attr("makeup_water_temp"), self.attr("makeup_water_press")) self.num_stages = self.attr("number_of_stages") @@ -131,32 +133,33 @@ def run(self, analysis): makeup_water_mass = max(injected_water_mass_demand - input_water_mass_rate, ureg.Quantity(0.0, "tonne/day")) prod_water_mass = min(injected_water_mass_demand, input_water_mass_rate) - makeup_steam_mass =\ - max(injected_steam_mass_demand - input_water_mass_rate + prod_water_mass, - ureg.Quantity(0.0, "tonne/day")) - prod_steam_mass =\ - min(injected_steam_mass_demand, input_water_mass_rate - prod_water_mass) + makeup_steam_mass = max( + injected_steam_mass_demand - input_water_mass_rate + prod_water_mass, ureg.Quantity(0.0, "tonne/day") + ) + prod_steam_mass = min(injected_steam_mass_demand, input_water_mass_rate - prod_water_mass) if self.steam_flooding: makeup_water_to_steam = self.find_output_stream("makeup water") prod_water_to_steam = self.find_output_stream("produced water") prod_water_to_steam.set_liquid_flow_rate("H2O", prod_steam_mass.to("tonne/day"), tp=input.tp) if makeup_steam_mass.m != 0: - makeup_water_to_steam.set_liquid_flow_rate("H2O", makeup_steam_mass.to("tonne/day"), - tp=self.makeup_water_tp) + makeup_water_to_steam.set_liquid_flow_rate( + "H2O", makeup_steam_mass.to("tonne/day"), tp=self.makeup_water_tp + ) if self.water_flooding or self.water_reinjection: water_to_reinjection = self.find_output_stream("water") water_to_reinjection_rate = prod_water_mass + makeup_water_mass water_to_reinjection.set_liquid_flow_rate("H2O", water_to_reinjection_rate, tp=input.tp) - water_for_disp =\ + water_for_disp = ( input_water_mass_rate - makeup_water_mass - makeup_steam_mass - prod_water_mass - prod_steam_mass + ) self.set_iteration_value(water_for_disp) surface_disp_rate = water_for_disp * self.frac_disp_surface subsurface_disp_rate = water_for_disp * self.frac_disp_subsurface - water_density = field.water.density() # water is under the standard conditions + water_density = field.water.density() # water is under the standard conditions input_water_volume_rate = input_water_mass_rate / water_density makeup_water_vol_downstream = (makeup_water_mass + makeup_steam_mass) / water_density @@ -186,21 +189,19 @@ def run(self, analysis): self.sum_intermediate_results() def get_water_treatment_elec(self, water_treatment_table, water_volume_rate): - electricity = 0 stages = sorted(water_treatment_table.index.unique()) if self.num_stages > len(stages) or self.num_stages < 0: raise OpgeeException( - f"water treatment: number of stages ({self.num_stages}) must be > 0 and <= {len(stages)}") + f"water treatment: number of stages ({self.num_stages}) must be > 0 and <= {len(stages)}" + ) - for stage in stages[:self.num_stages]: + for stage in stages[: self.num_stages]: stage_row = water_treatment_table.loc[stage] - electricity_factor = (stage_row["Apply"].squeeze() * - stage_row["EC"].squeeze()).sum() + electricity_factor = (stage_row["Apply"].squeeze() * stage_row["EC"].squeeze()).sum() electricity += electricity_factor * water_volume_rate - loss_factor = (stage_row["Apply"].squeeze() * - stage_row["Volume loss"].squeeze()).sum() - water_volume_rate *= (1 - loss_factor) + loss_factor = (stage_row["Apply"].squeeze() * stage_row["Volume loss"].squeeze()).sum() + water_volume_rate *= 1 - loss_factor return electricity diff --git a/opgee/smart_defaults.py b/opgee/smart_defaults.py index 0fdca99b..ce2d3c4a 100644 --- a/opgee/smart_defaults.py +++ b/opgee/smart_defaults.py @@ -14,7 +14,8 @@ _logger = getLogger(__name__) -NO_DEP = '_' # dummy dependency so these also show up in graph +NO_DEP = "_" # dummy dependency so these also show up in graph + class ProcessNotFoundError(OpgeeException): """ @@ -22,8 +23,10 @@ class ProcessNotFoundError(OpgeeException): Smart Default processing because the `Process` doesn't exist in the `Field` under evaluation. """ + pass + class SmartDefault(OpgeeObject): """ Create a ``SmartDefault`` instance. Do not use this method directly: use @@ -56,9 +59,10 @@ class SmartDefault(OpgeeObject): :param dependencies: (list of str) names of attributes on which ``attr_name`` depends. """ - registry = {} # Dictionary of ``SmartDefault`` instances keyed by attribute name. - _run_order = None # cached result of run_order() method. + registry = {} # Dictionary of ``SmartDefault`` instances keyed by attribute name. + + _run_order = None # cached result of run_order() method. def __init__(self, attr_name, wrapper, user_func, dependencies): self.attr_name = attr_name @@ -69,13 +73,13 @@ def __init__(self, attr_name, wrapper, user_func, dependencies): # func.__qualname__ is a string of format "func_class.func_name" # for methods and simply the func_name for normal functions qualname = user_func.__qualname__ - items = qualname.split('.') + items = qualname.split(".") self.func_class = items[0] if len(items) == 2 else None self.func_name = qualname self.func_module = user_func.__module__ self.registry[attr_name] = self - _logger.debug(f'Saving dependency for attribute {attr_name} of class {self.func_class}') + _logger.debug(f"Saving dependency for attribute {attr_name} of class {self.func_class}") # TBD: consider using @functools.wraps: # def my_decorator(f): @@ -96,9 +100,12 @@ def register(cls, attr_name, dependencies): These follow the same rules for class or Process specifier defined above. :return: (function) The decorator function. """ + def decorator(user_func): def wrapper(*args): - _logger.debug(f'Calling {user_func.__qualname__} for attribute {attr_name} with dependencies {dependencies}') + _logger.debug( + f"Calling {user_func.__qualname__} for attribute {attr_name} with dependencies {dependencies}" + ) return user_func(*args) cls(attr_name, wrapper, user_func, dependencies) @@ -129,7 +136,7 @@ def run_order(cls): if cycles: raise OpgeeException(f"Smart default dependencies contain cycles: {cycles}") - cls._run_order = list(nx.topological_sort(g)) # expand generator so we can iterate more than once + cls._run_order = list(nx.topological_sort(g)) # expand generator so we can iterate more than once return cls._run_order @@ -153,7 +160,7 @@ def apply_defaults(cls, field, analysis=None): obj, attr_obj = dep.find_attr(attr_name, analysis, field) except ProcessNotFoundError as e: _logger.warning(f"{e} (ignoring)") - continue # skip this smart default + continue # skip this smart default # Don't set smart defaults on explicitly set values if attr_obj.explicit: @@ -165,7 +172,7 @@ def apply_defaults(cls, field, analysis=None): tups = [dep.find_attr(name, analysis, field) for name in dep.dependencies] except ProcessNotFoundError as e: _logger.warning(f"{e} (ignoring)") - continue # skip this smart default + continue # skip this smart default values = [attr_obj.value for _, attr_obj in tups] @@ -181,7 +188,6 @@ def apply_defaults(cls, field, analysis=None): except Exception as e: raise OpgeeException(f"Attempt to set SmartDefault value for attribute '{attr_name}' failed: {e}") - def find_attr(self, attr_name, analysis, field): """ Find an attribute in the Analysis, Field, or in a named Process. @@ -202,18 +208,18 @@ def find_attr(self, attr_name, analysis, field): class_name, attr_name = split_attr_name(attr_name) if class_name is None: - class_name = self.func_class or 'Field' + class_name = self.func_class or "Field" - if class_name == 'Field': + if class_name == "Field": obj = field - elif class_name == 'Analysis': + elif class_name == "Analysis": obj = analysis else: obj = field.find_process(class_name, raiseError=False) if obj is None: - raise ProcessNotFoundError(f"Process not found for '{class_name}.{attr_name}' in {field}") + raise ProcessNotFoundError(f"Process not found for '{class_name}.{attr_name}' in {field}") attr_obj = obj.attr_dict.get(attr_name) if attr_obj is None: diff --git a/opgee/stream.py b/opgee/stream.py index 257bbb2a..c96bf0e4 100644 --- a/opgee/stream.py +++ b/opgee/stream.py @@ -46,9 +46,7 @@ def molecule_to_carbon(molecule): m = _hydrocarbon_prog.match(molecule) if m is None: - raise OpgeeException( - f"Expected hydrocarbon molecule name like CxHy, got {molecule}" - ) + raise OpgeeException(f"Expected hydrocarbon molecule name like CxHy, got {molecule}") c_name = m.group(1) return c_name @@ -99,9 +97,7 @@ class Stream(AttributeMixin, XmlInstantiable): # Verify that the pubchem-cid index includes 1..N where N is the max_carbon number if set(_carbon_number_dict.keys()) != set(idx): - raise ModelValidationError( - f"{table_name} must contain carbon numbers 1..{max_carbon_number}." - ) + raise ModelValidationError(f"{table_name} must contain carbon numbers 1..{max_carbon_number}.") # All hydrocarbon gases other than methane (C1) are considered VOCs. VOCs = _hydrocarbons[1:] @@ -143,7 +139,7 @@ class Stream(AttributeMixin, XmlInstantiable): _extensions = {} _units = ureg.Unit("tonne/day") - + tp: TemperaturePressure def __init__( @@ -162,9 +158,7 @@ def __init__( XmlInstantiable.__init__(self, name, parent=parent) # TBD: rename this self.comp_matrix for clarity - self.components = ( - self.create_component_matrix() if comp_matrix is None else comp_matrix - ) + self.components = self.create_component_matrix() if comp_matrix is None else comp_matrix self.tp = copy(tp) @@ -198,9 +192,7 @@ def to_dataframe(self): :return: (pd.DataFrame) data series """ - df = self.components.reset_index().melt( - id_vars=["index"], var_name="phase", value_name="value" - ) + df = self.components.reset_index().melt(id_vars=["index"], var_name="phase", value_name="value") df.rename(columns={"index": "component"}, inplace=True) df["units"] = "metric_ton / day" @@ -215,20 +207,19 @@ def to_dataframe(self): columns = ["phase", "component", "value", "units"] - items = [('T', self.tp.T), - ('P', self.tp.P), - ('API', self.API)] + items = [("T", self.tp.T), ("P", self.tp.P), ("API", self.API)] - tuples = [(no_phase, name, value.m, str(value.units)) - for name, value in items if value is not None and value.m != 0] + tuples = [ + (no_phase, name, value.m, str(value.units)) for name, value in items if value is not None and value.m != 0 + ] extras = pd.DataFrame(data=tuples, columns=columns) - result = pd.concat([df, extras], axis='rows') + result = pd.concat([df, extras], axis="rows") - result['field'] = self.parent.name - result['stream'] = self.name - result['source'] = self.src_name - result['destination'] = self.dst_name + result["field"] = self.parent.name + result["stream"] = self.name + result["source"] = self.src_name + result["destination"] = self.dst_name col_order = ["field", "stream", "source", "destination"] + columns return result[col_order] @@ -248,9 +239,7 @@ def reset(self): :return: none """ self.initialized = has_xml_data = self.xml_data is not None - self.components = ( - self.xml_data if has_xml_data else self.create_component_matrix() - ) + self.components = self.xml_data if has_xml_data else self.create_component_matrix() self.tp.copy_from(self.initial_tp) @@ -280,9 +269,7 @@ def extend_components(cls, names): # ensure no duplicate names bad = name_set.intersection(set(cls._extensions)) if bad: - raise OpgeeException( - f"extend_components: these proposed extensions are already defined: {bad}" - ) + raise OpgeeException(f"extend_components: these proposed extensions are already defined: {bad}") _logger.info(f"Extended stream components to include {names}") @@ -479,9 +466,9 @@ def set_rates_from_series(self, series, phase, upper_bound_stream=None): self.initialized = True self.components.loc[series.index, phase] = series.clip(lower=0) if upper_bound_stream is not None: - self.components.loc[series.index, phase] = self.components.loc[ - series.index, phase - ].clip(upper=upper_bound_stream.components.loc[series.index, phase]) + self.components.loc[series.index, phase] = self.components.loc[series.index, phase].clip( + upper=upper_bound_stream.components.loc[series.index, phase] + ) def multiply_factor_from_series(self, series, phase): """ @@ -492,9 +479,7 @@ def multiply_factor_from_series(self, series, phase): :return: """ self.initialized = True - self.components.loc[series.index, phase] = ( - series * self.components.loc[series.index, phase] - ) + self.components.loc[series.index, phase] = series * self.components.loc[series.index, phase] def set_tp(self, tp): """ @@ -686,9 +671,7 @@ def from_xml(cls, elt, parent=None): attr_dict = cls.instantiate_attrs(elt) expected = {"temperature", "pressure", "API"} if set(attr_dict.keys()) != expected: - raise OpgeeException( - f"Stream {name}: expected attributes {sorted(expected)}" - ) + raise OpgeeException(f"Stream {name}: expected attributes {sorted(expected)}") temp = attr_dict["temperature"].value pres = attr_dict["pressure"].value @@ -709,18 +692,14 @@ def from_xml(cls, elt, parent=None): a = comp_elt.attrib comp_name = elt_name(comp_elt) rate = coercible(comp_elt.text, float) - phase = a[ - "phase" - ] # required by XML schema to be one of the 3 legal values + phase = a["phase"] # required by XML schema to be one of the 3 legal values # convert hydrocarbon molecule name to carbon number format if is_hydrocarbon(comp_name): comp_name = molecule_to_carbon(comp_name) if comp_name not in matrix.index: - raise OpgeeException( - f"Unrecognized stream component name '{comp_name}'." - ) + raise OpgeeException(f"Unrecognized stream component name '{comp_name}'.") matrix.loc[comp_name, phase] = rate diff --git a/opgee/subcommand.py b/opgee/subcommand.py index 3ae6c00e..4c26d468 100644 --- a/opgee/subcommand.py +++ b/opgee/subcommand.py @@ -1,16 +1,17 @@ -''' +""" .. Created as part of pygcam. Imported into opgee (2021). .. Copyright (c) 2016-2022 Richard Plevin See the https://opensource.org/licenses/MIT for license details. -''' +""" + from abc import ABCMeta, abstractmethod # Fixes help strings to display properly with sphinx-argparse def clean_help(s): lines = s.splitlines() - return ' '.join(map(lambda s: s.strip(), lines)) + return " ".join(map(lambda s: s.strip(), lines)) class SubcommandABC(object): @@ -30,6 +31,7 @@ class SubcommandABC(object): Defaults to the sub-command name, capitalized. :param guiSuppress: (bool) if True, do not display this sub-command in the GUI. """ + __metaclass__ = ABCMeta Instances = {} # SubCommand instances keyed by name @@ -45,8 +47,7 @@ def getInstance(cls, name): # obj = cls.getInstance(name) # return obj.parser - def __init__(self, name, subparsers, kwargs, group=None, label=None, - guiSuppress=False): + def __init__(self, name, subparsers, kwargs, group=None, label=None, guiSuppress=False): self.name = name self.label = label or name.capitalize() # label to display in GUI self.parser = parser = subparsers.add_parser(self.name, **kwargs) @@ -58,7 +59,7 @@ def __init__(self, name, subparsers, kwargs, group=None, label=None, # For grouping commands in the GUI. Set this in subclass' addArgs(). # Set to None if the command should not be presented in the GUI. # Note: 'group' can be set as standalone var or in guiInfo['group']. - self.group = group or 'main' + self.group = group or "main" # self.group = group or (guiInfo.get('group') if guiInfo else 'main') self.addArgs(parser) diff --git a/opgee/table_manager.py b/opgee/table_manager.py index 2303fded..1c34e998 100644 --- a/opgee/table_manager.py +++ b/opgee/table_manager.py @@ -39,31 +39,32 @@ class TableManager(OpgeeObject): Users can add external tables using the ``add_table`` method. """ + table_defs = [ - TableDef('constants', index_col='name'), - TableDef('GWP', index_col=False), - TableDef('bitumen-mining-energy-intensity', index_col=0), + TableDef("constants", index_col="name"), + TableDef("GWP", index_col=False), + TableDef("bitumen-mining-energy-intensity", index_col=0), TableDef("process-specific-EF", index_col=0, has_units=True), TableDef("water-treatment", index_col=0, has_units=True), - TableDef('heavy-oil-upgrading'), + TableDef("heavy-oil-upgrading"), TableDef("transport-parameter", index_col=0), - TableDef("transport-share-fuel", index_col=[0,1], has_units=True), - TableDef("transport-by-mode", index_col=[0,1], has_units=True), + TableDef("transport-share-fuel", index_col=[0, 1], has_units=True), + TableDef("transport-by-mode", index_col=[0, 1], has_units=True), TableDef("reaction-combustion-coeff", index_col=0, has_units=True), TableDef("product-combustion-coeff", index_col=0, has_units=True), TableDef("gas-turbine-specs", index_col=0, has_units=True), TableDef("gas-dehydration", index_col=0), - TableDef("acid-gas-removal", index_col=0, index_row=[0,1]), + TableDef("acid-gas-removal", index_col=0, index_row=[0, 1]), TableDef("ryan-holmes-process", index_col=0, has_units=True), TableDef("imported-gas-comp", index_col=0, has_units=True), TableDef("upstream-CI", index_col=0, has_units=True), - TableDef("vertical-drilling-energy-intensity", index_col=[0,1], has_units=True), - TableDef("horizontal-drilling-energy-intensity", index_col=[0,1], has_units=True), + TableDef("vertical-drilling-energy-intensity", index_col=[0, 1], has_units=True), + TableDef("horizontal-drilling-energy-intensity", index_col=[0, 1], has_units=True), TableDef("fracture-consumption-table", index_col=0), - TableDef("land-use-EF", index_col=[0,1], has_units=True), + TableDef("land-use-EF", index_col=[0, 1], has_units=True), TableDef("pubchem-cid", index_col=0), - TableDef("ASPEN_input_boundary", index_col=[0,1]), - TableDef("demethanizer", index_col=0, index_row=[0,1]), + TableDef("ASPEN_input_boundary", index_col=[0, 1]), + TableDef("demethanizer", index_col=0, index_row=[0, 1]), TableDef("loss-matrix-oil", index_col=0), TableDef("loss-matrix-gas", index_col=0), TableDef("productivity-gas", index_col=0), @@ -71,7 +72,7 @@ class TableManager(OpgeeObject): TableDef("site-fugitive-processing-unit-breakdown", index_col=0, has_units=True), TableDef("well-completion-and-workover-C1-rate", has_units=True), TableDef("grid_mix_EF", index_col=0, has_units=True), - TableDef("grid_mix_feed", index_col=0, has_units=True) + TableDef("grid_mix_feed", index_col=0, has_units=True), ] _table_def_dict = {tbl_def.basename: tbl_def for tbl_def in table_defs} @@ -102,20 +103,23 @@ def get_table(self, name, raiseError=True): return None relpath = f"tables/{name}.csv" - s = resourceStream(relpath, stream_type='text') + s = resourceStream(relpath, stream_type="text") if tbl_def.has_units: df = pd.read_csv(s, index_col=tbl_def.index_col, header=[0, 1]) - unitful_cols = [name for name, unit in df.columns if unit != '_'] + unitful_cols = [name for name, unit in df.columns if unit != "_"] for col in unitful_cols: - df[col] = df[col].astype(float) # force numeric values to float to avoid complaints from pint + df[col] = df[col].astype(float) # force numeric values to float to avoid complaints from pint df_units = df[unitful_cols].pint.quantify(level=-1) df[unitful_cols] = df_units[unitful_cols] - df.columns = df.columns.droplevel(1) # drop the units from the column index + df.columns = df.columns.droplevel(1) # drop the units from the column index else: - df = pd.read_csv(s, index_col=tbl_def.index_col) if tbl_def.index_row is None \ + df = ( + pd.read_csv(s, index_col=tbl_def.index_col) + if tbl_def.index_row is None else pd.read_csv(s, index_col=tbl_def.index_col, header=tbl_def.index_row) + ) if tbl_def.fillna is not None: df.fillna(tbl_def.fillna, inplace=True) diff --git a/opgee/table_update.py b/opgee/table_update.py index a8ffeac5..8ad1ce2f 100644 --- a/opgee/table_update.py +++ b/opgee/table_update.py @@ -13,6 +13,7 @@ class Cell(OpgeeObject): """ Defines a replacement for a value provided in a built-in CSV file. """ + def __init__(self, row, col, value): super().__init__() @@ -32,6 +33,6 @@ def __init__(self, name, cells): @classmethod def from_xml(cls, elt, parent=None): - sub_elts = elt.findall('Cell') - cells = [Cell(e.attrib['row'], e.attrib['col'], e.text) for e in sub_elts] + sub_elts = elt.findall("Cell") + cells = [Cell(e.attrib["row"], e.attrib["col"], e.text) for e in sub_elts] return TableUpdate(elt_name(elt), cells) diff --git a/opgee/thermodynamics.py b/opgee/thermodynamics.py index 1928b8b9..761bee5d 100644 --- a/opgee/thermodynamics.py +++ b/opgee/thermodynamics.py @@ -26,10 +26,11 @@ class ChemicalInfo(OpgeeObject): def __init__(self): dict_non_hydrocarbon = {name: Chemical(name) for name in Stream.non_hydrocarbon_gases} series = Stream.pubchem_cid_df.PubChem - self._chemical_dict = chemical_dict = {name : Chemical(f"PubChem={num}") for name, num in series.items()} + self._chemical_dict = chemical_dict = {name: Chemical(f"PubChem={num}") for name, num in series.items()} chemical_dict.update(dict_non_hydrocarbon) - self._mol_weights = pd.Series({name: chemical.MW for name, chemical in chemical_dict.items()}, - dtype="pint[g/mole]") + self._mol_weights = pd.Series( + {name: chemical.MW for name, chemical in chemical_dict.items()}, dtype="pint[g/mole]" + ) @classmethod def get_instance(cls): @@ -128,7 +129,7 @@ def Cp(component, kelvin, with_units=True): :return: (float) specific heat in standard condition (unit = joule/g/kelvin) """ chemical = ChemicalInfo.chemical(component) - cp = chemical.Cp(phase='g', T=kelvin) + cp = chemical.Cp(phase="g", T=kelvin) if with_units: cp = ureg.Quantity(cp, "joule/g/kelvin") @@ -265,10 +266,7 @@ def __init__(self, field): :param field: """ - composition = [("N2", 0.774396), - ("O2", 0.20531), - ("CO2", 0.000294), - ("H2O", 0.02)] + composition = [("N2", 0.774396), ("O2", 0.20531), ("CO2", 0.000294), ("H2O", 0.02)] super().__init__(field, composition) @@ -283,16 +281,18 @@ def __init__(self, field): :param field: """ - composition = [("Nitrogen", 0.78084), - ("Oxygen", 0.20946), - ("Argon", 0.00934), - ("Carbon dioxide", 0.000412), - ("Neon", 0.00001818), - ("Helium", 0.00000524), - ("Methane", 0.00000179), - ("Krypton", 0.0000010), - ("Hydrogen", 0.0000005), - ("Xenon", 0.00000009)] + composition = [ + ("Nitrogen", 0.78084), + ("Oxygen", 0.20946), + ("Argon", 0.00934), + ("Carbon dioxide", 0.000412), + ("Neon", 0.00001818), + ("Helium", 0.00000524), + ("Methane", 0.00000179), + ("Krypton", 0.0000010), + ("Hydrogen", 0.0000005), + ("Xenon", 0.00000009), + ] super().__init__(field, composition) @@ -313,7 +313,6 @@ def __init__(self, field): self.dry_air = DryAir(field) - # TODO: refactor this. Currently each subclass of AbstractSubstance calls this __init__ # method and stores redundant copies of all the variables below. Make these class vars # instead so they are computed and stored only once. @@ -321,23 +320,24 @@ def __init__(self, field): components = self.component_MW.index self.component_LHV_molar = pd.Series( - {name: heating_value(name, with_units=False) for name in components}, - dtype="pint[joule/mole]") + {name: heating_value(name, with_units=False) for name in components}, dtype="pint[joule/mole]" + ) self.component_LHV_mass = self.component_LHV_molar / self.component_MW # joule/gram self.component_HHV_molar = pd.Series( {name: heating_value(name, use_LHV=False, with_units=False) for name in components}, - dtype="pint[joule/mole]") + dtype="pint[joule/mole]", + ) self.component_HHV_mass = self.component_LHV_molar / self.component_MW # joule/gram - self.component_Cp_STP = pd.Series({name: Cp(name, 288.706, with_units=False) for name in components}, - dtype="pint[joule/g/kelvin]") - self.component_Tc = pd.Series({name: Tc(name, with_units=False) for name in components}, - dtype="pint[kelvin]") - self.component_Pc = pd.Series({name: Pc(name, with_units=False) for name in components}, - dtype="pint[Pa]") - self.component_gas_rho_STP = pd.Series({name: rho(name, field.stp.T, field.stp.P, PHASE_GAS) - for name in components}, dtype="pint[kg/m**3]") + self.component_Cp_STP = pd.Series( + {name: Cp(name, 288.706, with_units=False) for name in components}, dtype="pint[joule/g/kelvin]" + ) + self.component_Tc = pd.Series({name: Tc(name, with_units=False) for name in components}, dtype="pint[kelvin]") + self.component_Pc = pd.Series({name: Pc(name, with_units=False) for name in components}, dtype="pint[Pa]") + self.component_gas_rho_STP = pd.Series( + {name: rho(name, field.stp.T, field.stp.P, PHASE_GAS) for name in components}, dtype="pint[kg/m**3]" + ) self.steam_table = XSteam(XSteam.UNIT_SYSTEM_FLS) @@ -362,9 +362,9 @@ def __init__(self, field): self.API = API = field.attr("API") self.oil_LHV_mass = self.mass_energy_density() - self.component_LHV_mass['oil'] = self.oil_LHV_mass.to("joule/gram") - self.gas_comp = field.attrs_with_prefix('gas_comp_') - self.gas_oil_ratio = field.attr('GOR') + self.component_LHV_mass["oil"] = self.oil_LHV_mass.to("joule/gram") + self.gas_comp = field.attrs_with_prefix("gas_comp_") + self.gas_oil_ratio = field.attr("GOR") self.oil_specific_gravity = ureg.Quantity(141.5 / (131.5 + API.m), "frac") self.total_molar_weight = (self.gas_comp * self.component_MW[self.gas_comp.index]).sum() self.gas_specific_gravity = self._gas_specific_gravity() @@ -437,10 +437,12 @@ def reservoir_solution_GOR(self): gas_SG = self.gas_specific_gravity.to("frac").m gor_bubble = self.bubble_point_solution_GOR(self.gas_oil_ratio).m - empirical_res = (res_P ** (1 / self.pbub_a2) * - oil_SG ** (-self.pbub_a1 / self.pbub_a2) * - math.exp(self.pbub_a3 / self.pbub_a2 * gas_SG * oil_SG) / - (res_T * gas_SG)) + empirical_res = ( + res_P ** (1 / self.pbub_a2) + * oil_SG ** (-self.pbub_a1 / self.pbub_a2) + * math.exp(self.pbub_a3 / self.pbub_a2 * gas_SG * oil_SG) + / (res_T * gas_SG) + ) result = min([empirical_res, gor_bubble]) result = ureg.Quantity(result, "scf/bbl_oil") return result @@ -460,9 +462,11 @@ def bubble_point_pressure(self, oil_specific_gravity, gas_specific_gravity, gas_ gas_SG = gas_specific_gravity.to("frac").m gor_bubble = self.bubble_point_solution_GOR(gas_oil_ratio).m - result = (oil_SG ** self.pbub_a1 * - (gas_SG * gor_bubble * res_temp) ** self.pbub_a2 * - math.exp(-self.pbub_a3 * gas_SG * oil_SG)) + result = ( + oil_SG**self.pbub_a1 + * (gas_SG * gor_bubble * res_temp) ** self.pbub_a2 + * math.exp(-self.pbub_a3 * gas_SG * oil_SG) + ) result = ureg.Quantity(result, "psia") return result @@ -479,11 +483,14 @@ def solution_gas_oil_ratio(self, stream, oil_specific_gravity, gas_specific_grav gas_SG = gas_specific_gravity.to("frac").m gor_bubble = self.bubble_point_solution_GOR(gas_oil_ratio) - result = min(math.pow(stream_P, 1 / self.pbub_a2) * - math.pow(oil_SG, -self.pbub_a1 / self.pbub_a2) * - math.exp(self.pbub_a3 / self.pbub_a2 * gas_SG * oil_SG) * - 1 / (stream_T * gas_SG), - gor_bubble.m) + result = min( + math.pow(stream_P, 1 / self.pbub_a2) + * math.pow(oil_SG, -self.pbub_a1 / self.pbub_a2) + * math.exp(self.pbub_a3 / self.pbub_a2 * gas_SG * oil_SG) + * 1 + / (stream_T * gas_SG), + gor_bubble.m, + ) result = ureg.Quantity(result, "scf/bbl_oil") return result @@ -500,17 +507,17 @@ def saturated_formation_volume_factor(self, stream, oil_specific_gravity, gas_sp gas_SG = gas_specific_gravity.to("frac").m solution_gor = self.solution_gas_oil_ratio(stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio).m - result = (1 + 0.000000525 * solution_gor * (stream_T - 60) + - 0.000181 * solution_gor / oil_SG + 0.000449 * (stream_T - 60) / oil_SG + - 0.000206 * solution_gor * gas_SG / oil_SG) + result = ( + 1 + + 0.000000525 * solution_gor * (stream_T - 60) + + 0.000181 * solution_gor / oil_SG + + 0.000449 * (stream_T - 60) / oil_SG + + 0.000206 * solution_gor * gas_SG / oil_SG + ) result = ureg.Quantity(result, "frac") return result - def unsat_formation_volume_factor(self, - stream, - oil_specific_gravity, - gas_specific_gravity, - gas_oil_ratio): + def unsat_formation_volume_factor(self, stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio): """ The formation volume factor is defined as the ratio of the volume of oil (plus the gas in solution) at the prevailing reservoir temperature and pressure to the volume of oil at standard conditions @@ -518,14 +525,11 @@ def unsat_formation_volume_factor(self, :return: (float) unsaturated formation volume factor (unit = fraction) """ res_stream = Stream("test_stream", self.res_tp) - bubble_oil_FVF = self.saturated_formation_volume_factor(res_stream, - self.oil_specific_gravity, - self.gas_specific_gravity, - self.gas_oil_ratio).m - - p_bubblepoint = self.bubble_point_pressure(oil_specific_gravity, - gas_specific_gravity, - gas_oil_ratio).m + bubble_oil_FVF = self.saturated_formation_volume_factor( + res_stream, self.oil_specific_gravity, self.gas_specific_gravity, self.gas_oil_ratio + ).m + + p_bubblepoint = self.bubble_point_pressure(oil_specific_gravity, gas_specific_gravity, gas_oil_ratio).m isothermal_compressibility = self.isothermal_compressibility(oil_specific_gravity).m stream_press = stream.tp.P.m @@ -547,15 +551,19 @@ def isothermal_compressibility_X(self, stream, oil_specific_gravity, gas_specifi iso_comp_a3 = 0.02408026 iso_comp_a4 = -0.0000000926091 - solution_gor = self.solution_gas_oil_ratio(stream, - oil_specific_gravity, - gas_specific_gravity, - gas_oil_ratio).m + solution_gor = self.solution_gas_oil_ratio(stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio).m gas_SG = gas_specific_gravity.to("frac").m stream_temp = stream.tp.T.to("rankine").m - result = max((iso_comp_a1 * solution_gor + iso_comp_a2 * solution_gor ** 2 + - iso_comp_a3 * gas_SG + iso_comp_a4 * stream_temp ** 2), 0.0) + result = max( + ( + iso_comp_a1 * solution_gor + + iso_comp_a2 * solution_gor**2 + + iso_comp_a3 * gas_SG + + iso_comp_a4 * stream_temp**2 + ), + 0.0, + ) result = ureg.Quantity(result, "pa**-1") return result @@ -571,30 +579,20 @@ def isothermal_compressibility(oil_specific_gravity): result = ureg.Quantity(result, "pa**-1") return result - def formation_volume_factor(self, - stream, - oil_specific_gravity, - gas_specific_gravity, - gas_oil_ratio): + def formation_volume_factor(self, stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio): """ the formation volume factor is defined as the ratio of the volume of oil (plus the gas in solution) at the prevailing reservoir temperature and pressure to the volume of oil at standard conditions :return:(float) final formation volume factor (unit = fraction) """ - p_bubblepoint = self.bubble_point_pressure(oil_specific_gravity, - gas_specific_gravity, - gas_oil_ratio) - - result = (self.saturated_formation_volume_factor(stream, - oil_specific_gravity, - gas_specific_gravity, - gas_oil_ratio) - if stream.tp.P < p_bubblepoint else - self.unsat_formation_volume_factor(stream, - oil_specific_gravity, - gas_specific_gravity, - gas_oil_ratio)) + p_bubblepoint = self.bubble_point_pressure(oil_specific_gravity, gas_specific_gravity, gas_oil_ratio) + + result = ( + self.saturated_formation_volume_factor(stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio) + if stream.tp.P < p_bubblepoint + else self.unsat_formation_volume_factor(stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio) + ) return result def density(self, stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio): @@ -608,19 +606,14 @@ def density(self, stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ra :return: The density of the mixture (unit = lb/ft**3) """ - solution_gor = self.solution_gas_oil_ratio(stream, - oil_specific_gravity, - gas_specific_gravity, - gas_oil_ratio) - volume_factor = self.formation_volume_factor(stream, - oil_specific_gravity, - gas_specific_gravity, - gas_oil_ratio) + solution_gor = self.solution_gas_oil_ratio(stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio) + volume_factor = self.formation_volume_factor(stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio) water_density = self.water.density_STP air_density = self.dry_air.density() - result =\ - (water_density * oil_specific_gravity + air_density * gas_specific_gravity * solution_gor) / volume_factor + result = ( + water_density * oil_specific_gravity + air_density * gas_specific_gravity * solution_gor + ) / volume_factor return result.to("lb/ft**3") def volume_flow_rate(self, stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio): @@ -655,7 +648,7 @@ def mass_energy_density(self, API=None, use_LHV=True, with_unit=True): a1, a2, a3, a4 = (16796, 54.4, 0.217, 0.0019) if use_LHV else (17672, 66.6, 0.316, 0.0014) API = self.API.m if API is None else API.m - result = (a1 + a2 * API - a3 * API ** 2 - a4 * API ** 3) + result = a1 + a2 * API - a3 * API**2 - a4 * API**3 result = ureg.Quantity(result, "british_thermal_unit/lb") if with_unit else result return result @@ -672,10 +665,7 @@ def volume_energy_density(self, stream, oil_specific_gravity, gas_specific_gravi :return:(float) volume energy density (unit = mmBtu/bbl) """ mass_energy_density = self.mass_energy_density(API=stream.API) - density = self.density(stream, - oil_specific_gravity, - gas_specific_gravity, - gas_oil_ratio).to("lb/bbl_oil") + density = self.density(stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio).to("lb/bbl_oil") result = mass_energy_density * density return result.to("mmBtu/bbl_oil") @@ -735,17 +725,19 @@ def liquid_fuel_composition(API): nitrogen_weight_percent = ureg.Quantity(0.2, "percent") sulfur_weight_percent = ureg.Quantity(-0.121 * API.m + 5.4293, "percent") hydrogen_weight_percent = ureg.Quantity(0.111 * API.m + 8.7523, "percent") - carbon_weight_percent = (ureg.Quantity(100., "percent") - - nitrogen_weight_percent - - sulfur_weight_percent - - hydrogen_weight_percent) - nitrogen_mol_percent = nitrogen_weight_percent / ureg.Quantity(14., "g/mol") - sulfur_mol_percent = sulfur_weight_percent / ureg.Quantity(32., "g/mol") - hydrogen_mol_percent = hydrogen_weight_percent / ureg.Quantity(1., "g/mol") - carbon_mol_percent = carbon_weight_percent / ureg.Quantity(12., "g/mol") + carbon_weight_percent = ( + ureg.Quantity(100.0, "percent") - nitrogen_weight_percent - sulfur_weight_percent - hydrogen_weight_percent + ) + nitrogen_mol_percent = nitrogen_weight_percent / ureg.Quantity(14.0, "g/mol") + sulfur_mol_percent = sulfur_weight_percent / ureg.Quantity(32.0, "g/mol") + hydrogen_mol_percent = hydrogen_weight_percent / ureg.Quantity(1.0, "g/mol") + carbon_mol_percent = carbon_weight_percent / ureg.Quantity(12.0, "g/mol") - return pd.Series([carbon_mol_percent, sulfur_mol_percent, hydrogen_mol_percent, nitrogen_mol_percent], - index=["C", "S", "H", "N"], dtype="pint[mol/kg]") + return pd.Series( + [carbon_mol_percent, sulfur_mol_percent, hydrogen_mol_percent, nitrogen_mol_percent], + index=["C", "S", "H", "N"], + dtype="pint[mol/kg]", + ) class Gas(AbstractSubstance): @@ -895,10 +887,12 @@ def heat_capacity(stream): temperature = temperature.to("kelvin").m mass_flow_rate = stream.gas_flow_rates() # pandas.Series if mass_flow_rate.empty: - return ureg.Quantity(0., "btu/degF/day") + return ureg.Quantity(0.0, "btu/degF/day") - specific_heat = pd.Series({name: Cp(name, temperature, with_units=False) for name in mass_flow_rate.index}, - dtype="pint[joule/g/kelvin]") + specific_heat = pd.Series( + {name: Cp(name, temperature, with_units=False) for name in mass_flow_rate.index}, + dtype="pint[joule/g/kelvin]", + ) heat_capacity = (mass_flow_rate * specific_heat).sum() return heat_capacity.to("btu/degF/day") @@ -916,13 +910,13 @@ def uncorrected_pseudocritical_temperature_and_pressure(self, stream): critical_temperature = critical_temperature.pint.m critical_pressure = self.component_Pc[mass_flow_rate.index].pint.to("psia") critical_pressure = critical_pressure.pint.m - temp1 = (molar_fraction * critical_temperature / critical_pressure ** 0.5).sum() + temp1 = (molar_fraction * critical_temperature / critical_pressure**0.5).sum() temp2 = (molar_fraction * critical_temperature / critical_pressure).sum() temp3 = (molar_fraction * (critical_temperature / critical_pressure) ** 0.5).sum() - temp1 = temp1 ** 2 + temp1 = temp1**2 temp2 = 1 / 3 * temp2 - temp3 = 2 / 3 * temp3 ** 2 + temp3 = 2 / 3 * temp3**2 temperature = ureg.Quantity(temp1 / (temp2 + temp3), "rankine") pressure = ureg.Quantity(temp1 / (temp2 + temp3) ** 2, "psia") return pd.Series(data=[temperature, pressure], index=["temperature", "pressure"]) @@ -937,10 +931,11 @@ def corrected_pseudocritical_temperature(self, stream): uncorr_pseudocritical_temp = self.uncorrected_pseudocritical_temperature_and_pressure(stream)["temperature"].m molar_frac_O2 = self.component_molar_fraction("O2", stream).m molar_frac_H2S = self.component_molar_fraction("H2S", stream).m - result = (uncorr_pseudocritical_temp - - 120 * ((molar_frac_O2 + molar_frac_H2S) ** 0.9 - (molar_frac_O2 + molar_frac_H2S) ** 1.6) + - 15 * (molar_frac_H2S ** 0.5 - molar_frac_H2S ** 4) - ) + result = ( + uncorr_pseudocritical_temp + - 120 * ((molar_frac_O2 + molar_frac_H2S) ** 0.9 - (molar_frac_O2 + molar_frac_H2S) ** 1.6) + + 15 * (molar_frac_H2S**0.5 - molar_frac_H2S**4) + ) return ureg.Quantity(result, "rankine") def corrected_pseudocritical_pressure(self, stream): @@ -955,10 +950,10 @@ def corrected_pseudocritical_pressure(self, stream): corr_pseudocritical_temp = self.corrected_pseudocritical_temperature(stream) molar_frac_H2S = self.component_molar_fraction("H2S", stream) - result = ((uncorr_pseudocritical_press * corr_pseudocritical_temp) / - (uncorr_pseudocritical_temp - - molar_frac_H2S * (1 - molar_frac_H2S) * - (uncorr_pseudocritical_temp - corr_pseudocritical_temp))) + result = (uncorr_pseudocritical_press * corr_pseudocritical_temp) / ( + uncorr_pseudocritical_temp + - molar_frac_H2S * (1 - molar_frac_H2S) * (uncorr_pseudocritical_temp - corr_pseudocritical_temp) + ) return result @@ -1003,22 +998,27 @@ def Z_factor(reduced_temperature, reduced_pressure): pint.Quantity: Compressibility factor (Z) as a Pint Quantity object (dimensionless fraction) for the given reduced temperature and reduced pressure. """ - a = 0.42748 * (reduced_pressure / reduced_temperature ** 2.5) + a = 0.42748 * (reduced_pressure / reduced_temperature**2.5) b = 0.08664 * (reduced_pressure / reduced_temperature) - alpha = (1 / 3) * (3 * (a - b - b ** 2) - 1) - beta = (1 / 27) * (-2 + (9 * (a - b - b ** 2)) - (27 * a * b)) - d = (beta ** 2 / 4) + (alpha ** 3 / 27) + alpha = (1 / 3) * (3 * (a - b - b**2) - 1) + beta = (1 / 27) * (-2 + (9 * (a - b - b**2)) - (27 * a * b)) + d = (beta**2 / 4) + (alpha**3 / 27) if d < 0: - theta = math.acos(-np.sign(beta) * (math.sqrt((beta ** 2 / 4) / (-alpha ** 3 / 27)))) - z_roots = [2 * math.sqrt(- alpha / 3) * math.cos((theta / 3) + (i * ((math.pi * 2) / 3))) + (1 / 3) for i in - range(3)] + theta = math.acos(-np.sign(beta) * (math.sqrt((beta**2 / 4) / (-(alpha**3) / 27)))) + z_roots = [ + 2 * math.sqrt(-alpha / 3) * math.cos((theta / 3) + (i * ((math.pi * 2) / 3))) + (1 / 3) + for i in range(3) + ] else: a_star = np.cbrt((-beta / 2) + np.sqrt(d)) b_star = np.cbrt((-beta / 2) - np.sqrt(d)) - z_roots = [a_star + b_star + 1 / 3] if d > 0 else [a_star + b_star + 1 / 3] + [ - -(1 / 2) * (a_star + b_star) + (1 / 3) * i for i in [2, 3]] + z_roots = ( + [a_star + b_star + 1 / 3] + if d > 0 + else [a_star + b_star + 1 / 3] + [-(1 / 2) * (a_star + b_star) + (1 / 3) * i for i in [2, 3]] + ) z = max(z_roots) @@ -1066,7 +1066,7 @@ def viscosity(self, stream): gas_density = self.density(stream).to("lb/ft**3").m temp = stream.tp.T.to("rankine").m - factor_K = (9.4 + 0.02 * gas_stream_molar_weight) * temp ** 1.5 / (209 + 19 * gas_stream_molar_weight + temp) + factor_K = (9.4 + 0.02 * gas_stream_molar_weight) * temp**1.5 / (209 + 19 * gas_stream_molar_weight + temp) factor_X = 3.5 + 986 / temp + 0.01 * gas_stream_molar_weight factor_Y = 2.4 - 0.2 * factor_X @@ -1140,7 +1140,7 @@ def mass_energy_density(self, stream): mass_flow_rate = stream.gas_flow_rates() if len(mass_flow_rate) == 0: - return ureg.Quantity(0., "MJ/kg") + return ureg.Quantity(0.0, "MJ/kg") total_mass_rate = stream.total_gas_rate() @@ -1180,7 +1180,8 @@ def combustion_enthalpy(molar_fracs, temperature, phase): enthalpy = pd.Series( {name: Enthalpy(name, temperature, phase=phase, with_units=False) for name in molar_fracs.index}, - dtype="pint[joule/mole]") + dtype="pint[joule/mole]", + ) if "H2O" in molar_fracs and phase == PHASE_GAS: steam_table = XSteam(XSteam.UNIT_SYSTEM_FLS) @@ -1189,9 +1190,10 @@ def combustion_enthalpy(molar_fracs, temperature, phase): water_T_ref = ureg.Quantity(30, "degC") water_T_ref = water_T_ref.to("degF").m - latent_heat_water = \ - ureg.Quantity( - steam_table.hV_t(water_T_ref) - steam_table.hL_t(water_T_ref), "btu/lb") * ChemicalInfo.mol_weights()["H2O"] + latent_heat_water = ( + ureg.Quantity(steam_table.hV_t(water_T_ref) - steam_table.hL_t(water_T_ref), "btu/lb") + * ChemicalInfo.mol_weights()["H2O"] + ) enthalpy["H2O"] = max(water_vapor_enthalpy - latent_heat_water, ureg.Quantity(0.0, "joule/mole")) return enthalpy @@ -1331,8 +1333,9 @@ def enthalpy_PT(self, pressure, temperature, mass_rate): pressure = pressure.to("psia").m temperature = temperature.to("degF").m - enthalpy = self.steam_table.h_pt(round(pressure, self.steam_tbl_digits), - round(temperature, self.steam_tbl_digits)) + enthalpy = self.steam_table.h_pt( + round(pressure, self.steam_tbl_digits), round(temperature, self.steam_tbl_digits) + ) enthalpy = ureg.Quantity(enthalpy, "btu/lb") result = enthalpy * mass_rate diff --git a/opgee/tool.py b/opgee/tool.py index a91b44e2..a457ed4a 100644 --- a/opgee/tool.py +++ b/opgee/tool.py @@ -1,4 +1,4 @@ -''' +""" .. The "opg" commandline program .. Adapted from tool.py in pygcam. @@ -6,19 +6,20 @@ .. Copyright (c) 2016-2022 Richard Plevin See the https://opensource.org/licenses/MIT for license details. -''' +""" + import argparse import os import sys from glob import glob -from .config import (pathjoin, getParam, getConfig, getParamAsBoolean, setParam, getSection, setSection) +from .config import pathjoin, getParam, getConfig, getParamAsBoolean, setParam, getSection, setSection from .error import OpgeeException, CommandlineError from .log import setLogLevels, configureLogs from .subcommand import clean_help from .version import VERSION -PROGRAM = 'opg' +PROGRAM = "opg" class Opgee(object): @@ -44,19 +45,19 @@ def _loadCachedPlugin(cls, name): @classmethod def _cachePlugins(cls): - ''' + """ Find all plugins via OPGEE.PluginPath and create a dict of plugin pathnames keyed by command name so the plugin can be loaded on-demand. :return: none - ''' + """ pluginDirs = cls._getPluginDirs() - suffix = '_plugin.py' + suffix = "_plugin.py" suffixLen = len(suffix) for d in pluginDirs: - pattern = pathjoin(d, '*' + suffix) + pattern = pathjoin(d, "*" + suffix) for path in glob(pattern): basename = os.path.basename(path) command = basename[:-suffixLen] @@ -100,6 +101,7 @@ def __init__(self, loadPlugins=True, loadBuiltins=True): # load all built-in sub-commands if loadBuiltins: from .built_ins import BuiltinSubcommands + for item in BuiltinSubcommands: self.instantiatePlugin(item) @@ -123,31 +125,41 @@ def addParsers(self): # help=clean_help('''Specify a name for the queued batch job. Default is "gt". # (Linux only)''')) - logLevel = str(getParam('OPGEE.LogLevel')) # so not unicode - parser.add_argument('--logLevel', - default=logLevel, - help=clean_help('''Sets the log level for modules of the program. A default + logLevel = str(getParam("OPGEE.LogLevel")) # so not unicode + parser.add_argument( + "--logLevel", + default=logLevel, + help=clean_help("""Sets the log level for modules of the program. A default log level can be set for the entire program, or individual modules can have levels set using the syntax "module:level, module:level,...", where the level names must be - one of {debug,info,warning,error,fatal} (case insensitive).''')) - - parser.add_argument('--set', dest='configVars', metavar='name=value', action='append', default=[], - help=clean_help('''Assign a value to override a configuration file parameter. For example, + one of {debug,info,warning,error,fatal} (case insensitive)."""), + ) + + parser.add_argument( + "--set", + dest="configVars", + metavar="name=value", + action="append", + default=[], + help=clean_help("""Assign a value to override a configuration file parameter. For example, to temporarily use a different attributes file, use --set "OPGEE.UserAttributesFile=/some/path/attr.xml". Enclose the argument in quotes if it contains spaces or other characters that would confuse the shell. - Use multiple --set flags and arguments to set multiple variables.''')) + Use multiple --set flags and arguments to set multiple variables."""), + ) - parser.add_argument('--verbose', action='store_true', - help=clean_help('''Show diagnostic output''')) + parser.add_argument("--verbose", action="store_true", help=clean_help("""Show diagnostic output""")) - parser.add_argument('--version', action='version', version=VERSION) # goes to stderr, handled by argparse + parser.add_argument("--version", action="version", version=VERSION) # goes to stderr, handled by argparse - parser.add_argument('--VERSION', action='store_true') # goes to stdout, but handled by gt + parser.add_argument("--VERSION", action="store_true") # goes to stdout, but handled by gt - self.subparsers = self.parser.add_subparsers(dest='subcommand', title='Subcommands', - description='''For help on subcommands, use the "-h" flag after the subcommand name''') + self.subparsers = self.parser.add_subparsers( + dest="subcommand", + title="Subcommands", + description="""For help on subcommands, use the "-h" flag after the subcommand name""", + ) def instantiatePlugin(self, pluginClass): plugin = pluginClass(self.subparsers) @@ -155,7 +167,7 @@ def instantiatePlugin(self, pluginClass): @staticmethod def _getPluginDirs(): - pluginPath = getParam('OPGEE.PluginPath') + pluginPath = getParam("OPGEE.PluginPath") if not pluginPath: return [] @@ -178,17 +190,17 @@ def getModObj(mod, name): mod = loadModuleFromPath(path) - pluginClass = getModObj(mod, 'PluginClass') or getModObj(mod, 'Plugin') + pluginClass = getModObj(mod, "PluginClass") or getModObj(mod, "Plugin") if not pluginClass: - raise OpgeeException(f'Neither PluginClass nor class Plugin are defined in {path}') + raise OpgeeException(f"Neither PluginClass nor class Plugin are defined in {path}") self.instantiatePlugin(pluginClass) def _loadRequiredPlugins(self, argv): # Create a dummy subparser to allow us to identify the requested # sub-command so we can load the module if necessary. - parser = argparse.ArgumentParser(prog=PROGRAM, add_help=False, prefix_chars='-+') - parser.add_argument('-h', '--help', action='store_true') + parser = argparse.ArgumentParser(prog=PROGRAM, add_help=False, prefix_chars="-+") + parser.add_argument("-h", "--help", action="store_true") # parser.add_argument('+P', '--projectName', metavar='name') ns, otherArgs = parser.parse_known_args(args=argv) @@ -223,11 +235,11 @@ def run(self, args=None, argList=None): args = self.parser.parse_args(args=argList) else: # top-level call - args.projectName = section = getParam('OPGEE.DefaultProject') + args.projectName = section = getParam("OPGEE.DefaultProject") if section: setSection(section) - logLevel = args.logLevel or getParam('OPGEE.LogLevel') + logLevel = args.logLevel or getParam("OPGEE.LogLevel") if logLevel: setLogLevels(logLevel) @@ -240,10 +252,10 @@ def run(self, args=None, argList=None): def _getMainParser(): - ''' + """ Used only to generate documentation by sphinx' argparse, in which case we don't generate documentation for project-specific plugins. - ''' + """ getConfig(allowMissing=True, systemConfigOnly=True) tool = Opgee.getInstance(loadPlugins=False) return tool.parser @@ -268,10 +280,11 @@ def _getMainParser(): # _logger.info('No symlink permission; setting OPGEE.CopyAllFiles = True') # setParam('OPGEE.CopyAllFiles', 'True') + # This parser handles only --VERSION flag. def _showVersion(argv): - parser = argparse.ArgumentParser(prog=PROGRAM, add_help=False, prefix_chars='-+') - parser.add_argument('--VERSION', action='store_true') + parser = argparse.ArgumentParser(prog=PROGRAM, add_help=False, prefix_chars="-+") + parser.add_argument("--VERSION", action="store_true") ns, otherArgs = parser.parse_known_args(args=argv) @@ -290,18 +303,18 @@ def _main(argv=None): tool._loadRequiredPlugins(argv) # This parser handles only the --set arg to validate it before running subcommands - parser = argparse.ArgumentParser(prog=PROGRAM, add_help=False, prefix_chars='-+') + parser = argparse.ArgumentParser(prog=PROGRAM, add_help=False, prefix_chars="-+") - parser.add_argument('+s', '--set', dest='configVars', action='append', default=[]) + parser.add_argument("+s", "--set", dest="configVars", action="append", default=[]) ns, otherArgs = parser.parse_known_args(args=argv) # Set specified config vars for arg in ns.configVars: - if '=' not in arg: + if "=" not in arg: raise CommandlineError(f'--set requires an argument of the form variable=value, got "{arg}"') - name, value = arg.split('=') + name, value = arg.split("=") setParam(name, value) tool.shellArgs = otherArgs # save for project run method to use in "distribute" mode @@ -322,6 +335,7 @@ def opg(cmdline): argv = shlex.split(cmdline) main(argv) + def main(argv=None, raiseError=False): # load_pint_registry() @@ -338,8 +352,9 @@ def main(argv=None, raiseError=False): print("{PROGRAM} failed: {e}") - if not getSection() or getParamAsBoolean('OPGEE.ShowStackTrace'): + if not getSection() or getParamAsBoolean("OPGEE.ShowStackTrace"): import traceback + traceback.print_exc() finally: diff --git a/opgee/units.py b/opgee/units.py index 6b377def..be17529c 100644 --- a/opgee/units.py +++ b/opgee/units.py @@ -26,6 +26,7 @@ # to avoid redundantly reporting bad units _undefined_units = {} + def validate_unit(unit): """ Return the ``pint.Unit`` associated with the string ``unit``, or ``None`` diff --git a/opgee/utils.py b/opgee/utils.py index 640cfe9c..081b8a1a 100644 --- a/opgee/utils.py +++ b/opgee/utils.py @@ -1,4 +1,4 @@ -''' +""" .. Created as part of pygcam (2015) Imported into opgee (2021) @@ -6,7 +6,8 @@ .. Copyright (c) 2015-2022 Richard Plevin See the https://opensource.org/licenses/MIT for license details. -''' +""" + import argparse import os import sys @@ -18,14 +19,16 @@ _logger = getLogger(__name__) + def ipython_info(): # pragma: no cover ip = False - if 'ipykernel' in sys.modules: - ip = 'notebook' - elif 'IPython' in sys.modules: - ip = 'terminal' + if "ipykernel" in sys.modules: + ip = "notebook" + elif "IPython" in sys.modules: + ip = "terminal" return ip + @contextmanager def pushd(directory): """ @@ -50,13 +53,14 @@ def positive_int(value): try: i = int(value) except: - i = 0 # the effect is to convert a ValueError into an ArgumentTypeError + i = 0 # the effect is to convert a ValueError into an ArgumentTypeError if i <= 0: raise argparse.ArgumentTypeError(f"{value} is not a positive integer") return i + # # Custom argparse "action" to parse comma-delimited strings to lists # @@ -68,16 +72,18 @@ def __init__(self, option_strings, dest, nargs=None, **kwargs): super(ParseCommaList, self).__init__(option_strings, dest, **kwargs) def __call__(self, parser, namespace, values, option_string=None): - setattr(namespace, self.dest, splitAndStrip(values, ',')) + setattr(namespace, self.dest, splitAndStrip(values, ",")) def splitAndStrip(s, delim): items = [item.strip() for item in s.split(delim)] return items + def is_relpath(p): drive, path = os.path.splitdrive(p) - return not (drive or path.startswith('/')) + return not (drive or path.startswith("/")) + def mkdirs(newdir, mode=0o770): """ @@ -97,6 +103,7 @@ def mkdirs(newdir, mode=0o770): def removeTree(path, ignore_errors=True): import shutil + _logger.debug(f"shutil.rmtree('{path}')") if os.path.lexists(path) and os.path.islink(path): os.remove(path) @@ -119,8 +126,9 @@ def removeTree(path, ignore_errors=True): # raise # + def filecopy(src, dst, removeDst=True): - 'Copy src file to dst, optionally removing dst first to avoid writing through symlinks' + "Copy src file to dst, optionally removing dst first to avoid writing through symlinks" from shutil import copy2 # equivalent to "cp -p" _logger.debug(f"copyfile({src}, dst, removeDst)") @@ -129,6 +137,7 @@ def filecopy(src, dst, removeDst=True): copy2(src, dst) + # def copyfiles(files, dstdir, removeDst=True): # ''' # :param files: a list of files to copy @@ -140,6 +149,7 @@ def filecopy(src, dst, removeDst=True): # for f in files: # filecopy(f, dstdir, removeDst=removeDst) + # used only in opgee modules def getBooleanXML(value): """ @@ -159,7 +169,7 @@ def getBooleanXML(value): if val not in valid: raise OpgeeException(f"Can't convert '{value}' to boolean; must be one of {valid} (case sensitive).") - return (val in true) + return val in true # Function to return current function name, or the caller, and so on up @@ -200,13 +210,13 @@ def coercible(value, pytype, raiseError=True): return value if type(pytype) == str: - if pytype == 'float': + if pytype == "float": pytype_func = float - elif pytype == 'int': + elif pytype == "int": pytype_func = lambda s: to_int(s) # allow "24.0" to be truncated to 24 - elif pytype == 'str': + elif pytype == "str": pytype_func = str - elif pytype == 'binary': + elif pytype == "binary": pytype_func = binary else: raise OpgeeException(f"coercible: '{pytype}' is not a recognized type string") @@ -225,7 +235,7 @@ def coercible(value, pytype, raiseError=True): return value -TRIAL_STRING_DELIMITER = ',' +TRIAL_STRING_DELIMITER = "," def parseTrialString(string): @@ -241,11 +251,11 @@ def parseTrialString(string): rangeStrs = string.split(TRIAL_STRING_DELIMITER) res = set() for rangeStr in rangeStrs: - r = [int(x) for x in rangeStr.strip().split('-')] + r = [int(x) for x in rangeStr.strip().split("-")] if len(r) == 2: r = range(r[0], r[1] + 1) elif len(r) != 1: - raise ValueError('Malformed trial string.') + raise ValueError("Malformed trial string.") res = res.union(set(r)) return list(res) @@ -283,7 +293,7 @@ def loadModuleFromPath(module_path, raiseError=True): # Extract the module name from the module path module_path = unixPath(module_path) base = os.path.basename(module_path) - module_name = base.split('.')[0] + module_name = base.split(".")[0] try: module = sys.modules[module_name] @@ -298,7 +308,7 @@ def loadModuleFromPath(module_path, raiseError=True): try: spec = importlib.util.spec_from_file_location(module_name, module_path) module = importlib.util.module_from_spec(spec) # creates a new module - sys.modules[module_name] = module # record it to sys.module + sys.modules[module_name] = module # record it to sys.module spec.loader.exec_module(module) except Exception as e: diff --git a/opgee/version.py b/opgee/version.py index d3821b13..52b62838 100644 --- a/opgee/version.py +++ b/opgee/version.py @@ -1,2 +1,2 @@ -VERSION="4.0a0" +VERSION = "4.0a0" # perhaps should be "4.0.0-alpha.0" diff --git a/opgee/xml_utils.py b/opgee/xml_utils.py index f1175da7..ca6c09af 100644 --- a/opgee/xml_utils.py +++ b/opgee/xml_utils.py @@ -1,10 +1,11 @@ -''' +""" .. Created as part of pygcam (2020) Imported into opgee (2021) .. Copyright (c) 2015-2022 Richard Plevin See the https://opensource.org/licenses/MIT for license details. -''' +""" + from copy import deepcopy from io import StringIO @@ -20,7 +21,7 @@ def str_to_xml(s): elt = ET.XML(s) parser = ET.XMLParser(remove_blank_text=True) xml = ET.tostring(elt) - file_obj = StringIO(xml.decode('utf-8')) + file_obj = StringIO(xml.decode("utf-8")) tree = ET.parse(file_obj, parser) return tree.getroot() @@ -33,47 +34,52 @@ def save_xml(path, root, backup=False, overwrite=False): p = Path(path) if p.exists(): if backup: - backup = path + '~' + backup = path + "~" os.rename(path, backup) elif not overwrite: - raise OpgeeException(f"save_xml: file exists: '{path}'; to overwrite specify backup=True or overwrite=True") + raise OpgeeException( + f"save_xml: file exists: '{path}'; to overwrite specify backup=True or overwrite=True" + ) _logger.info(f"Writing '{path}'") tree = ET.ElementTree(root) - tree.write(path, xml_declaration=True, pretty_print=True, encoding='utf-8') + tree.write(path, xml_declaration=True, pretty_print=True, encoding="utf-8") else: # for debugging only - ET.dump(root, pretty_print=True) # pragma: no cover + ET.dump(root, pretty_print=True) # pragma: no cover + def _load_opgee_template(template): from .config import getParam from .pkg_utils import resourceStream from .model_file import XMLFile - opgee_xml = getParam('OPGEE.ModelFile') - base_stream = resourceStream(opgee_xml, stream_type='bytes', decode=None) - xmlfile = XMLFile(base_stream, schemaPath='etc/opgee.xsd') + opgee_xml = getParam("OPGEE.ModelFile") + base_stream = resourceStream(opgee_xml, stream_type="bytes", decode=None) + xmlfile = XMLFile(base_stream, schemaPath="etc/opgee.xsd") model = xmlfile.getRoot() field = model.find(f"Field[@name='{template}']") return field + def _find_proc_in_agg(process_name, aggs): for agg in aggs: if agg.xpath(f'./Process[@class="{process_name}"]'): - name = agg.attrib['name'] + name = agg.attrib["name"] return name return None -def attr_to_xml(fields, dtypes, xml_path, analysis_name, modifies='default'): + +def attr_to_xml(fields, dtypes, xml_path, analysis_name, modifies="default"): from lxml import etree as ET import numpy as np - known_types = {'int' : int, 'float' : float, 'str' : str} + known_types = {"int": int, "float": float, "str": str} - model = ET.Element('Model') - analysis = ET.SubElement(model, 'Analysis', attrib={'name' : analysis_name}) + model = ET.Element("Model") + analysis = ET.SubElement(model, "Analysis", attrib={"name": analysis_name}) # add all under group = ET.SubElement(analysis, "Group") @@ -85,28 +91,27 @@ def attr_to_xml(fields, dtypes, xml_path, analysis_name, modifies='default'): # Convert fields to xml for field_name, col in fields.items(): - field = ET.SubElement(model, 'Field', - attrib={'name' : field_name, 'modifies' : modifies}) + field = ET.SubElement(model, "Field", attrib={"name": field_name, "modifies": modifies}) # Add Group declaration to as well - group = ET.SubElement(field, 'Group') - group.text = 'all' + group = ET.SubElement(field, "Group") + group.text = "all" proc_dict = {} # remember process elements created for attributes within each field - agg_dict = {} # same for aggregators we create + agg_dict = {} # same for aggregators we create for attr, value in col.items(): # don't include unspecified attributes try: if np.isnan(value): continue - except: + except TypeError: pass # np.isnan() fails for non-numeric types; ignore it - if value == '' or value is None: + if value == "" or value is None: continue - parts = attr.split('.') # see if it's Process.attr_name + parts = attr.split(".") # see if it's Process.attr_name count = len(parts) if count > 2: raise OpgeeException(f"Badly formed attribute name: '{attr}': must be 'attr' or 'Process.attr'") @@ -116,10 +121,9 @@ def attr_to_xml(fields, dtypes, xml_path, analysis_name, modifies='default'): if not (attr_parent := proc_dict.get(process_name)): # If the Process is defined within an , add that # node so XML merging works properly - if (agg_name := _find_proc_in_agg(process_name, aggregators)): - + if agg_name := _find_proc_in_agg(process_name, aggregators): if not (agg := agg_dict.get(agg_name)): - agg = ET.SubElement(field, 'Aggregator', attrib={'name': agg_name}) + agg = ET.SubElement(field, "Aggregator", attrib={"name": agg_name}) agg_dict[agg_name] = agg proc_parent = agg @@ -128,15 +132,17 @@ def attr_to_xml(fields, dtypes, xml_path, analysis_name, modifies='default'): proc_parent = field # Create the element on demand, unless we've found it in the proc_dict - proc_dict[process_name] = attr_parent = ET.SubElement(proc_parent, 'Process', attrib={'class': process_name}) + proc_dict[process_name] = attr_parent = ET.SubElement( + proc_parent, "Process", attrib={"class": process_name} + ) else: attr_parent = field attr_name = attr - a = ET.SubElement(attr_parent, 'A', attrib={'name': attr_name}) + a = ET.SubElement(attr_parent, "A", attrib={"name": attr_name}) - dtype = dtypes[attr] # use original name with "Process." if present + dtype = dtypes[attr] # use original name with "Process." if present type_fn = known_types[dtype] try: a.text = str(type_fn(value)) @@ -145,6 +151,7 @@ def attr_to_xml(fields, dtypes, xml_path, analysis_name, modifies='default'): save_xml(xml_path, model, overwrite=True) + # # TBD: Elements don't match unless *all* attribs are identical. Maybe match only on tag and name attribute?? # @@ -161,18 +168,20 @@ def match_element(elt1, elt2): try: for key, value in attr1.items(): - if key != 'delete' and value != attr2[key]: + if key != "delete" and value != attr2[key]: return False except KeyError: return False return True + def elt2str(elt): - attribs = ' '.join([f'{key}="{value}"' for key, value in elt.attrib.items()]) + attribs = " ".join([f'{key}="{value}"' for key, value in elt.attrib.items()]) s = f"<{elt.tag} {attribs}>" return s + def dump_with_context(elt): """ Print the element along with its parents, to provide context. @@ -181,7 +190,7 @@ def dump_with_context(elt): :return: none """ seq = [elt] - while ((elt := elt.getparent()) is not None): + while (elt := elt.getparent()) is not None: seq.insert(0, elt) indent = 0 @@ -189,6 +198,7 @@ def dump_with_context(elt): print(" " * indent, elt2str(elt)) indent += 1 + def merge_element(parent, new_elt): """ Add an element if none of parent's children has the same tag and attributes @@ -199,7 +209,7 @@ def merge_element(parent, new_elt): # _logger.debug(f"merge_element: new_elt {element_string(new_elt)} to sibling {element_string(sibling)}") if match_element(new_elt, sibling): # _logger.debug(f"matched: {elt2str(new_elt)} and {elt2str(sibling)}") - if new_elt.attrib.get('delete', '0') == '1': + if new_elt.attrib.get("delete", "0") == "1": _logger.debug(f"Deleting {elt2str(sibling)}") parent.remove(sibling) else: @@ -213,6 +223,7 @@ def merge_element(parent, new_elt): _logger.debug(f"Appending {elt2str(new_elt)} to {elt2str(parent)}") parent.append(deepcopy(new_elt)) + def merge_elements(parent, elt_list): """ Add each element in `elt_list` to parent if none of parent's children has the same tag @@ -222,6 +233,7 @@ def merge_elements(parent, elt_list): for elt in elt_list: merge_element(parent, elt) + def merge_siblings(elt1, elt2): """ Merge elt2 into elt1. @@ -229,6 +241,6 @@ def merge_siblings(elt1, elt2): :return: none (elt1 is modified) """ if not match_element(elt1, elt2): - return # fails silently + return # fails silently merge_elements(elt1, elt2[:]) diff --git a/tests/BROKEN_test_smart_defaults.py b/tests/BROKEN_test_smart_defaults.py index 383a9832..b392ec87 100644 --- a/tests/BROKEN_test_smart_defaults.py +++ b/tests/BROKEN_test_smart_defaults.py @@ -5,41 +5,45 @@ from opgee.smart_defaults import SmartDefault from opgee.mcs.simulation import Distribution -@SmartDefault.register('TEST_abcdef', ['TEST_foo', 'TEST_bar']) + +@SmartDefault.register("TEST_abcdef", ["TEST_foo", "TEST_bar"]) def smart_dflt_1(foo, bar): print(f"Called test_smart_dflt(foo:{foo} bar:{bar})") return 10000 + class MyAnalysis(Analysis): def __init__(self, name): - field_name = 'TestField' + field_name = "TestField" super().__init__(name, attr_dict={}, field_names=[field_name], groups=[]) attrs = [ - A('TEST_WOR-MEAN', value=10, pytype=int, unit='fraction', explicit=False), - A('TEST_WOR-SD', value=2, pytype=int, unit='fraction'), - A('Field.age', value=20, pytype=int, unit='year'), - A('TEST_foo', value=3, pytype=int, unit='tonne'), - A('TEST_bar', value=4, pytype=int, unit='meter'), + A("TEST_WOR-MEAN", value=10, pytype=int, unit="fraction", explicit=False), + A("TEST_WOR-SD", value=2, pytype=int, unit="fraction"), + A("Field.age", value=20, pytype=int, unit="year"), + A("TEST_foo", value=3, pytype=int, unit="tonne"), + A("TEST_bar", value=4, pytype=int, unit="meter"), ] attr_dict = {attr.name: attr for attr in attrs} f = Field(field_name, attr_dict=attr_dict, streams=[], procs=[]) self.field_dict = {field_name: f} + def test_simulation(): - analysis_name = 'random_name' - #analysis = MyAnalysis(analysis_name) + analysis_name = "random_name" + # analysis = MyAnalysis(analysis_name) N = 1000 - pathname = '/tmp/test-mcs' + pathname = "/tmp/test-mcs" Simulation.new(pathname, analysis_name=analysis_name, trials=N, overwrite=True) + def test_dependency_decorators(): from opgee.mcs.LHS import getPercentiles - attr_dict = {'TEST_foo': 3, 'TEST_bar': 4, 'TEST_baz': 10, 'TEST_age': 20} + attr_dict = {"TEST_foo": 3, "TEST_bar": 4, "TEST_baz": 10, "TEST_age": 20} - dep_obj = SmartDefault.find('Distribution', 'TEST_WOR-SD') + dep_obj = SmartDefault.find("Distribution", "TEST_WOR-SD") args = [attr_dict[attr_name] for attr_name in dep_obj.dependencies] rv = dep_obj.func(*args) @@ -51,5 +55,5 @@ def test_dependency_decorators(): objs = Distribution.distributions() # look only for our named attributes - attr_names = ['TEST_WOR-SD', 'TEST_WOR'] - assert [obj.attr_name for obj in objs if obj.attr_name.startswith('TEST_')] == attr_names + attr_names = ["TEST_WOR-SD", "TEST_WOR"] + assert [obj.attr_name for obj in objs if obj.attr_name.startswith("TEST_")] == attr_names diff --git a/tests/conftest.py b/tests/conftest.py index 1eb9cb56..ffd75951 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,7 @@ def configure_logging_for_tests(): # Don't display routine diagnostic messages during tests getConfig() - setLogLevels('ERROR') + setLogLevels("ERROR") configureLogs(force=True) return None @@ -23,7 +23,7 @@ def opgee_model(configure_logging_for_tests): @pytest.fixture(scope="module") def test_model(configure_logging_for_tests): - return load_test_model('test_model.xml') + return load_test_model("test_model.xml") @pytest.fixture(scope="function") @@ -33,9 +33,9 @@ def test_model_with_change(configure_logging_for_tests): tests that alter the model, to avoid creating state changes that confuse tests. """ - return load_test_model('test_model.xml') + return load_test_model("test_model.xml") -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def opgee_main(): return Opgee() diff --git a/tests/files/broken_post_proc_plugin.py b/tests/files/broken_post_proc_plugin.py index eb4439f5..275f0fd3 100644 --- a/tests/files/broken_post_proc_plugin.py +++ b/tests/files/broken_post_proc_plugin.py @@ -1,3 +1,3 @@ # Test case for class missing a subclass of PostProcessor -class NotAPlugin(): +class NotAPlugin: pass diff --git a/tests/files/post-proc-plugins/1_post_processor.py b/tests/files/post-proc-plugins/1_post_processor.py index 1a20869c..61431ee2 100644 --- a/tests/files/post-proc-plugins/1_post_processor.py +++ b/tests/files/post-proc-plugins/1_post_processor.py @@ -2,6 +2,7 @@ from opgee.post_processor import PostProcessor + class PostProcessor_1(PostProcessor): results = [] @@ -13,10 +14,10 @@ def clear(cls): cls.results.clear() def run(self, analysis, field, result): - self.results.append(('plugin-1-data', result)) + self.results.append(("plugin-1-data", result)) def save(self, output_dir): - path = os.path.join(output_dir, 'auto_loaded_post_proc_1.csv') - with open(path, 'w') as f: + path = os.path.join(output_dir, "auto_loaded_post_proc_1.csv") + with open(path, "w") as f: for tag, value in self.results: f.write(f"{tag}, {value}\n") diff --git a/tests/files/post-proc-plugins/2_post_processor.py b/tests/files/post-proc-plugins/2_post_processor.py index 6450af25..8f6974c8 100644 --- a/tests/files/post-proc-plugins/2_post_processor.py +++ b/tests/files/post-proc-plugins/2_post_processor.py @@ -2,6 +2,7 @@ from opgee.post_processor import PostProcessor + class PostProcessor_2(PostProcessor): results = [] @@ -13,10 +14,10 @@ def clear(cls): cls.results.clear() def run(self, analysis, field, result): - self.results.append(('plugin-2-data', result)) + self.results.append(("plugin-2-data", result)) def save(self, output_dir): - path = os.path.join(output_dir, 'auto_loaded_post_proc_2.csv') - with open(path, 'w') as f: + path = os.path.join(output_dir, "auto_loaded_post_proc_2.csv") + with open(path, "w") as f: for tag, value in self.results: f.write(f"{tag}, {value}\n") diff --git a/tests/files/simple_post_processor.py b/tests/files/simple_post_processor.py index b95dc1dc..1c551c17 100644 --- a/tests/files/simple_post_processor.py +++ b/tests/files/simple_post_processor.py @@ -2,6 +2,7 @@ from opgee.post_processor import PostProcessor + class SimplePostProcessor(PostProcessor): results = [] @@ -16,13 +17,13 @@ def run(self, analysis, field, result): """ Append results to the class variable ``results`` """ - self.results.append(('dummy-data', result)) + self.results.append(("dummy-data", result)) def save(self, output_dir): """ Write results to a CSV file """ - path = os.path.join(output_dir, 'simple_post_processor.csv') - with open(path, 'w') as f: + path = os.path.join(output_dir, "simple_post_processor.csv") + with open(path, "w") as f: for tag, value in self.results: f.write(f"{tag}, {value}\n") diff --git a/tests/files/user_processes.py b/tests/files/user_processes.py index f2e1faec..fae38331 100644 --- a/tests/files/user_processes.py +++ b/tests/files/user_processes.py @@ -1,6 +1,7 @@ from opgee.error import ModelValidationError from opgee.process import Process + class UserProcess1(Process): def run(self, analysis): # copy inputs to outputs @@ -11,7 +12,8 @@ def run(self, analysis): contents = in_stream.contents if len(contents) != 1: raise ModelValidationError( - f"UserProcess1 test streams must have only a single Content declaration; {self} inputs are {contents}") + f"UserProcess1 test streams must have only a single Content declaration; {self} inputs are {contents}" + ) # If not exactly one stream that declares the same contents, raises error out_stream = self.find_output_stream(contents[0], raiseError=True) @@ -19,6 +21,7 @@ def run(self, analysis): if out_stream: out_stream.copy_flow_rates_from(in_stream) + class UserProcess2(Process): def run(self, analysis): pass diff --git a/tests/processes/test_compressor.py b/tests/processes/test_compressor.py index d4aa4f3a..f586cffc 100644 --- a/tests/processes/test_compressor.py +++ b/tests/processes/test_compressor.py @@ -8,15 +8,16 @@ Q_ = ureg.Quantity + def test_get_compression_ratio(): - overall_comp_ratio = Q_(25., 'kilopascal') / Q_(0.5, 'psia') + overall_comp_ratio = Q_(25.0, "kilopascal") / Q_(0.5, "psia") result = Compressor.get_compression_ratio_and_stage(overall_comp_ratio) assert result is not None _approx = functools.partial(pytest.approx, rel=10e-3, abs=10e-6) comp_ratio, num_stages = result - assert comp_ratio.m == _approx(2.6929, rel=10E-3, abs=10E-6) + assert comp_ratio.m == _approx(2.6929, rel=10e-3, abs=10e-6) assert num_stages == 2 - + # works with pure floats as well result = Compressor.get_compression_ratio_and_stage(3 / 2) assert result is not None @@ -24,16 +25,17 @@ def test_get_compression_ratio(): assert isinstance(comp_ratio, Quantity) assert comp_ratio.m == 1.5 assert num_stages == 1 - + + def test_get_compression_ratio_not_dimensionless(): - overall_compression_ratio = Q_(1., 'kPa') / Q_(1., 'sec') - + overall_compression_ratio = Q_(1.0, "kPa") / Q_(1.0, "sec") + with pytest.raises(DimensionalityError): _ = Compressor.get_compression_ratio_and_stage(overall_compression_ratio) - + + def test_get_compression_ratio_stages_qtys(): - ratios = [1., 1000., 0.558, 100] + ratios = [1.0, 1000.0, 0.558, 100] comp_ratios = [Q_(r, "frac") for r in ratios] result = Compressor.get_compression_ratio_stages(comp_ratios) assert len(result) > 0 - \ No newline at end of file diff --git a/tests/test_attr_constraints.py b/tests/test_attr_constraints.py index 8d068624..d2bbb6cb 100644 --- a/tests/test_attr_constraints.py +++ b/tests/test_attr_constraints.py @@ -2,10 +2,12 @@ from opgee.error import OpgeeException from .utils_for_tests import load_test_model + @pytest.fixture(scope="module") def constraint_model(configure_logging_for_tests): return + # N.B. The previously mutually exclusive attributes tested here no longer are exclusive # def test_excludes(): # with pytest.raises(OpgeeException, match=r".*Exclusive attribute.*"): @@ -14,11 +16,9 @@ def constraint_model(configure_logging_for_tests): def test_syncs(): with pytest.raises(OpgeeException, match=r".*Attributes in synchronized.*"): - load_test_model('test_attr_constraints_2.xml') + load_test_model("test_attr_constraints_2.xml") + def test_constraints(): with pytest.raises(OpgeeException, match=r".*Attribute '.*': constraint failed.*"): - load_test_model('test_attr_constraints_3.xml') - - - + load_test_model("test_attr_constraints_3.xml") diff --git a/tests/test_attributes.py b/tests/test_attributes.py index a99dd409..ede450b1 100644 --- a/tests/test_attributes.py +++ b/tests/test_attributes.py @@ -70,19 +70,23 @@ def attr_dict_2(attr_classes): @pytest.mark.parametrize( - "attr_name, value", [("maximum_iterations", 20), # test numerical value override - ("maximum_change", 0.001), # test numerical default adopted - ] + "attr_name, value", + [ + ("maximum_iterations", 20), # test numerical value override + ("maximum_change", 0.001), # test numerical default adopted + ], ) def test_model_defaults(attr_classes, attr_dict_1, attr_name, value): assert attr_dict_1[attr_name].value == value @pytest.mark.parametrize( - "attr_name, value", [("GWP_horizon", ureg.Quantity(20.0, 'year')), # test units and numerical override - ("GWP_version", "AR4"), # test character value override - ("functional_unit", "oil"), # test character default adopted - ] + "attr_name, value", + [ + ("GWP_horizon", ureg.Quantity(20.0, "year")), # test units and numerical override + ("GWP_version", "AR4"), # test character value override + ("functional_unit", "oil"), # test character default adopted + ], ) def test_analysis_defaults(attr_classes, attr_dict_2, attr_name, value): assert attr_dict_2[attr_name].value == value @@ -95,19 +99,19 @@ def __init__(self, attr_dict): def test_exceptions(attr_classes, attr_dict_1): obj = AttributeHolder(attr_dict_1) - name = 'unknown' + name = "unknown" with pytest.raises(OpgeeException, match=f".*Attribute '{name}' not found in*"): obj.attr(name) def test_string_rep(attr_classes): - name = 'functional_unit' - adef = attr_classes['Analysis'].attribute(name) + name = "functional_unit" + adef = attr_classes["Analysis"].attribute(name) s = str(adef) assert s == f"" def test_unknown_attribute(attr_classes): - name = 'unknown-attribute' + name = "unknown-attribute" with pytest.raises(AttributeError, match=f"Attribute definition for '{name}' was not found"): - attr_classes['Model'].attribute(name) + attr_classes["Model"].attribute(name) diff --git a/tests/test_audit.py b/tests/test_audit.py index 74eca40f..91313bc9 100644 --- a/tests/test_audit.py +++ b/tests/test_audit.py @@ -35,9 +35,7 @@ def test_audit_source_input(audit_model_file: ModelFile): field = model.get_field("audit-field") original_xml_root = audit_model_file.root - original_field_elem_list = original_xml_root.xpath( - f".//Field[@name='{field.name}']" - ) + original_field_elem_list = original_xml_root.xpath(f".//Field[@name='{field.name}']") assert len(original_field_elem_list) == 1 original_field_elem = original_field_elem_list[0] @@ -91,7 +89,7 @@ def test_audit_field(audit_model_file: ModelFile, tmp_path): def audit_setup_and_run(tmp_path: Path, opgee, audit_level: str | None = None): - _ = getConfig(createDefault=True,reload=True) + _ = getConfig(createDefault=True, reload=True) results_dir = Path(os.path.join(tmp_path, "audit_results")) results_dir.mkdir(exist_ok=True, parents=True) @@ -102,41 +100,40 @@ def audit_setup_and_run(tmp_path: Path, opgee, audit_level: str | None = None): audit_xml_path = path_to_test_file("audit_model.xml") mf = ModelFile.from_xml_string(open(audit_xml_path).read()) field = mf.model.get_field("audit-field") - cmd = [ - "run", - "-m", str(audit_xml_path), - "-a", "audit-analysis", - "-r", "detailed", - "-o", str(results_dir) - ] + cmd = ["run", "-m", str(audit_xml_path), "-a", "audit-analysis", "-r", "detailed", "-o", str(results_dir)] opgee.run(argList=cmd) return results_dir / "field_audit.csv", results_dir / "process_graphs" / f"{field.name}_process_graph.png" + def test_audit_save_results(tmp_path: Path, audit_model_file: ModelFile, opgee_main): audit_path, proc_graph_path = audit_setup_and_run(tmp_path, opgee_main, "Field") assert not proc_graph_path.exists() assert audit_path.exists() audit_df = pd.read_csv(audit_path) - inputs = audit_df[audit_df['source'] == 'input'] - vals = inputs['value'].values + inputs = audit_df[audit_df["source"] == "input"] + vals = inputs["value"].values assert len(vals == 2) - assert vals[0] == '25.5' - assert vals[1] == '1500.0' + assert vals[0] == "25.5" + assert vals[1] == "1500.0" + def test_audit_save_procs(tmp_path: Path, opgee_main): audit_path, proc_graph_path = audit_setup_and_run(tmp_path, opgee_main, "Processes") assert proc_graph_path.exists() assert not audit_path.exists() + def test_audit_save_all(tmp_path: Path, opgee_main): audit_path, proc_graph_path = audit_setup_and_run(tmp_path, opgee_main, "All") assert proc_graph_path.exists() and audit_path.exists() + def test_audit_save_none(tmp_path: Path, opgee_main): audit_path, proc_graph_path = audit_setup_and_run(tmp_path, opgee_main) assert not (audit_path.exists() or proc_graph_path.exists()) + # it should audit if Field.run fails def test_audit_on_run_failure(tmp_path: Path, opgee_main): def mocked_field_run_processes(self, analysis): @@ -145,4 +142,3 @@ def mocked_field_run_processes(self, analysis): with patch("opgee.field.Field.run_processes", mocked_field_run_processes): audit_path, proc_graph_path = audit_setup_and_run(tmp_path, opgee_main, "Field") assert audit_path.exists() - diff --git a/tests/test_boundary.py b/tests/test_boundary.py index 3d029840..01a0b274 100644 --- a/tests/test_boundary.py +++ b/tests/test_boundary.py @@ -13,10 +13,10 @@ def impute(self): @pytest.fixture(scope="module") def test_boundary(configure_logging_for_tests): - return load_test_model('test_boundary.xml') + return load_test_model("test_boundary.xml") def test_gas_trans_boundary(test_boundary): - analysis = test_boundary.get_analysis('test_boundary') - field = analysis.get_field('test_gas_transmission_boundary') + analysis = test_boundary.get_analysis("test_boundary") + field = analysis.get_field("test_gas_transmission_boundary") field.run(analysis) diff --git a/tests/test_boundary_procs.py b/tests/test_boundary_procs.py index 4b759402..3ebd1935 100644 --- a/tests/test_boundary_procs.py +++ b/tests/test_boundary_procs.py @@ -17,22 +17,22 @@ class BoundaryStreamsProcC(Process): @pytest.fixture(scope="module") def boundary_model(configure_logging_for_tests): - return load_test_model('test_boundary_procs.xml') + return load_test_model("test_boundary_procs.xml") def test_boundary_streams(boundary_model): - analysis = boundary_model.get_analysis('test_boundary_procs') - field = analysis.get_field('field1') + analysis = boundary_model.get_analysis("test_boundary_procs") + field = analysis.get_field("field1") boundaries = field.defined_boundaries() - assert set(boundaries) == {'Production', 'Distribution', 'Transportation'} + assert set(boundaries) == {"Production", "Distribution", "Transportation"} - proc = field.boundary_dict['Production'] - assert proc.name == 'ProductionBoundary' + proc = field.boundary_dict["Production"] + assert proc.name == "ProductionBoundary" - proc = field.boundary_dict['Distribution'] - assert proc.name == 'DistributionBoundary' + proc = field.boundary_dict["Distribution"] + assert proc.name == "DistributionBoundary" # no need to run the field # field.run(analysis) diff --git a/tests/test_coeff.py b/tests/test_coeff.py index a3ebff12..99433a65 100644 --- a/tests/test_coeff.py +++ b/tests/test_coeff.py @@ -1,17 +1,80 @@ import pandas as pd from opgee.process import run_corr_eqns + def test_run_corr_eqns(): - df = pd.DataFrame(index=["intercept", 'x1', 'x2', 'x3', 'x4', 'x5', 'x1*x2', 'x1*x3', - 'x1*x4', 'x1*x5', 'x2*x3', 'x2*x4', 'x2*x5', 'x3*x4', - 'x3*x5', 'x4*x5', 'x1**2', 'x2**2', 'x3**2', 'x4**2', 'x5**2'], - # columns=["a", "b"], - data={"a": [-0.27681, 0.0021697, -0.051166, 3098.8, -0.011716, 0.0098877, -0.000049723, 0.054335, - -0.00043215, 0.00000023389, 2.8942, 0.011162, -0.000044607, 69.394, -7.8131, - -0.00023773, - 0.00000098932, 0.00062898, -41372, -0.037419, -0.000011238], - "b": [-0.0048379, -0.0000018578, 0.000017634, 36.007, -0.000048814, - 0.00000045508, 0.000000010229, -0.00093692, 0.0000000087422, 0.0000000056977, - 0.016479, 0.00000027865, -0.00000011193, -0.00016548, -0.015255, 0.0000000076148, - -0.00000000010435, -0.000000030978, 4.185, 0.0000028812, 0.0000000091332]}) + df = pd.DataFrame( + index=[ + "intercept", + "x1", + "x2", + "x3", + "x4", + "x5", + "x1*x2", + "x1*x3", + "x1*x4", + "x1*x5", + "x2*x3", + "x2*x4", + "x2*x5", + "x3*x4", + "x3*x5", + "x4*x5", + "x1**2", + "x2**2", + "x3**2", + "x4**2", + "x5**2", + ], + # columns=["a", "b"], + data={ + "a": [ + -0.27681, + 0.0021697, + -0.051166, + 3098.8, + -0.011716, + 0.0098877, + -0.000049723, + 0.054335, + -0.00043215, + 0.00000023389, + 2.8942, + 0.011162, + -0.000044607, + 69.394, + -7.8131, + -0.00023773, + 0.00000098932, + 0.00062898, + -41372, + -0.037419, + -0.000011238, + ], + "b": [ + -0.0048379, + -0.0000018578, + 0.000017634, + 36.007, + -0.000048814, + 0.00000045508, + 0.000000010229, + -0.00093692, + 0.0000000087422, + 0.0000000056977, + 0.016479, + 0.00000027865, + -0.00000011193, + -0.00016548, + -0.015255, + 0.0000000076148, + -0.00000000010435, + -0.000000030978, + 4.185, + 0.0000028812, + 0.0000000091332, + ], + }, + ) result = run_corr_eqns(500, 90, 0.00181, 2, 200, df) diff --git a/tests/test_comparison.py b/tests/test_comparison.py index c7eb761b..2bb3b2e4 100644 --- a/tests/test_comparison.py +++ b/tests/test_comparison.py @@ -61,23 +61,26 @@ def test_comparison1(): status = compare(StringIO(results1), StringIO(results2)) assert status == ComparisonStatus.PROCESS_MISMATCH + def test_comparison2(): # Test matching files status = compare(StringIO(results1), StringIO(results3)) assert status == ComparisonStatus.GOOD + def test_comparison3(): # Test divergent values status = compare(StringIO(results1), StringIO(results4)) assert status == ComparisonStatus.VALUE_MISMATCH + def test_comparison4(): # Test NA in one file not in the other status = compare(StringIO(results1), StringIO(results5)) assert status == ComparisonStatus.VALUE_MISMATCH + def test_comparison5(): # Test mismatched fields status = compare(StringIO(results1), StringIO(results6)) assert status == ComparisonStatus.FIELD_MISMATCH - diff --git a/tests/test_config.py b/tests/test_config.py index be7b1740..114a0694 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,44 +1,59 @@ import os import pytest -from opgee.config import (unixPath, getHomeDir, pathjoin, getConfig, getParam, - getParamAsBoolean, getParamAsInt, getParamAsFloat, - setSection, USR_CONFIG_FILE, stringTrue) +from opgee.config import ( + unixPath, + getHomeDir, + pathjoin, + getConfig, + getParam, + getParamAsBoolean, + getParamAsInt, + getParamAsFloat, + setSection, + USR_CONFIG_FILE, + stringTrue, +) from opgee.error import OpgeeException, ConfigFileError from .utils_for_tests import load_config_from_string + def test_unixpath(): assert unixPath(r"\Users\foo\bar") == "/Users/foo/bar" + def test_expanduser(): - if os.environ.get('CI') == 'true': - home = os.environ['HOME'] # don't use OPGEE_HOME for this test + if os.environ.get("CI") == "true": + home = os.environ["HOME"] # don't use OPGEE_HOME for this test else: home = getHomeDir() assert pathjoin("~", "foo", expanduser=True) == unixPath(f"{home}/foo") + def test_abspath(): home = getHomeDir() os.chdir(home) assert pathjoin("foo", "bar", abspath=True) == unixPath(f"{home}/foo/bar") -def test_realpath(tmp_path): # built-in fixture returns a temporary directory - foo = tmp_path / 'foo.txt' - with open(foo, 'w'): # create an empty file + +def test_realpath(tmp_path): # built-in fixture returns a temporary directory + foo = tmp_path / "foo.txt" + with open(foo, "w"): # create an empty file pass - bar = tmp_path / 'bar.txt' + bar = tmp_path / "bar.txt" os.symlink(foo, bar) assert pathjoin(bar, realpath=True) == unixPath(str(foo)) def test_reload(): getConfig() - getConfig(reload=True) # just making sure it runs + getConfig(reload=True) # just making sure it runs + @pytest.fixture(scope="function") def dummy_config(): - project_name = 'dummy' + project_name = "dummy" # Create a config section cfg_text = f""" @@ -64,59 +79,68 @@ def dummy_config(): return project_name + def test_config_exact(opgee_main, dummy_config, capsys): # capsys is a file like object that captures output to stdout / stderr project_name = dummy_config - opgee_main.run(None, argList=['config', '--exact', 'OPGEE.ProjectName']) + opgee_main.run(None, argList=["config", "--exact", "OPGEE.ProjectName"]) captured = capsys.readouterr() assert captured.out == f"{project_name}\n" + def test_config_cmd(opgee_main, dummy_config, capsys): - opgee_main.run(None, argList=['config', 'X\\.']) + opgee_main.run(None, argList=["config", "X\\."]) captured = capsys.readouterr() - lines = [line.strip() for line in captured.out.split('\n') if line] - assert lines == ['[dummy]', 'X.bar = two', 'X.baz = three', 'X.foo = one'] + lines = [line.strip() for line in captured.out.split("\n") if line] + assert lines == ["[dummy]", "X.bar = two", "X.baz = three", "X.foo = one"] + def test_config_edit(opgee_main, dummy_config, capsys): - opgee_main.run(None, argList=['config', '--edit']) + opgee_main.run(None, argList=["config", "--edit"]) captured = capsys.readouterr() home = getHomeDir() assert captured.out == f"echo {home}/{USR_CONFIG_FILE}\n" + def test_unknown_section(dummy_config): with pytest.raises(OpgeeException, match=r"getParam: unknown section .*"): - getParam('unknown_param', 'unknown_section') + getParam("unknown_param", "unknown_section") + def test_unknown_param(dummy_config): with pytest.raises(OpgeeException, match=r"getParam: unknown variable .*"): - getParam('unknown_param') + getParam("unknown_param") + def test_string_value(): - assert stringTrue('yes', raiseError=False) - assert not stringTrue('no') + assert stringTrue("yes", raiseError=False) + assert not stringTrue("no") - assert stringTrue('unknown', raiseError=False) is None + assert stringTrue("unknown", raiseError=False) is None with pytest.raises(ConfigFileError, match=r"Unrecognized boolean value: .*"): - stringTrue('unknown', raiseError=True) + stringTrue("unknown", raiseError=True) + def test_getParamAsBoolean(dummy_config): with pytest.raises(ConfigFileError, match=r"The value of variable .* could not converted to boolean."): - getParamAsBoolean('X.foo') + getParamAsBoolean("X.foo") + + assert getParamAsBoolean("boolean_param") is True - assert getParamAsBoolean('boolean_param') is True def test_getParamAsInt(dummy_config): with pytest.raises(ValueError): - getParamAsInt('X.foo') + getParamAsInt("X.foo") + + assert getParamAsInt("int_param") == 123 - assert getParamAsInt('int_param') == 123 def test_getParamAsFloat(dummy_config): with pytest.raises(ValueError): - getParamAsFloat('X.foo') + getParamAsFloat("X.foo") - assert getParamAsFloat('int_param') == 123.0 - assert getParamAsFloat('float_param') == 4.5 + assert getParamAsFloat("int_param") == 123.0 + assert getParamAsFloat("float_param") == 4.5 diff --git a/tests/test_core.py b/tests/test_core.py index a2f4fde8..ec0080c0 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3,11 +3,13 @@ from opgee.core import dict_from_list, XmlInstantiable, A from opgee.error import OpgeeException, AbstractMethodError + def test_magnitude_error(): q = ureg.Quantity(10.0, "tonnes/day") with pytest.raises(OpgeeException, match=r"magnitude: value .* units are not .*"): magnitude(q, ureg.Unit("tonnes/year")) + def test_dict_from_list(): foo = XmlInstantiable("foo") bar = XmlInstantiable("bar") @@ -15,39 +17,46 @@ def test_dict_from_list(): items = [foo, bar, baz] d = dict_from_list(items) - assert len(d) == 3 and d['foo'] == foo and d['bar'] == bar and d['baz'] == baz + assert len(d) == 3 and d["foo"] == foo and d["bar"] == bar and d["baz"] == baz + def test_dict_from_list_error(): items = [XmlInstantiable("foo"), XmlInstantiable("bar"), XmlInstantiable("foo")] with pytest.raises(OpgeeException, match="XmlInstantiable instances must have unique names: foo is not unique."): dict_from_list(items) + def test_from_xml_error(): with pytest.raises(AbstractMethodError, match="Abstract method XmlInstantiable.from_xml was called.*"): XmlInstantiable("foo").from_xml(None) + def test_find_parent_error(): - assert XmlInstantiable("foo").find_container('Model') is None + assert XmlInstantiable("foo").find_container("Model") is None + def test_validate_unit_error(configure_logging_for_tests): - unit = 'not_a_unit' + unit = "not_a_unit" assert unit not in _undefined_units assert validate_unit(unit) is None and unit in _undefined_units + def test_A_set_value_None(): - a = A('foo', value=None) + a = A("foo", value=None) assert a.value is None + def test_A_str_rep(): value = 10 - unit = 'mmbtu/day' - a = A('foo', value=value, pytype='float', unit=unit) + unit = "mmbtu/day" + a = A("foo", value=value, pytype="float", unit=unit) assert str(a).startswith("' + pat = f"" assert re.match(pat, s) is not None assert res.energy is None and res.emissions is None diff --git a/tests/test_emissions.py b/tests/test_emissions.py index 0a712e15..0cd7e573 100644 --- a/tests/test_emissions.py +++ b/tests/test_emissions.py @@ -7,9 +7,9 @@ def test_set_rate(): e = Emissions() - rate = ureg.Quantity(123.45, 'tonne/day') - e.set_rate(EM_FUGITIVES, 'CO2', rate) - assert e.data.loc['CO2', EM_FUGITIVES] == rate + rate = ureg.Quantity(123.45, "tonne/day") + e.set_rate(EM_FUGITIVES, "CO2", rate) + assert e.data.loc["CO2", EM_FUGITIVES] == rate def test_set_rate_error(): @@ -17,7 +17,7 @@ def test_set_rate_error(): e = Emissions() with pytest.raises(EmissionsError, match=r".*Unrecognized gas*"): - e.set_rate(EM_FUGITIVES, 'H2O', 43) + e.set_rate(EM_FUGITIVES, "H2O", 43) @pytest.fixture @@ -27,11 +27,9 @@ def emissions_of_two_gases(): return e.data -@pytest.mark.parametrize( - "gas,rate", [('CO2', 123.45), ('N2O', 45.6), ('CO', 0.0), ('CH4', 0.0), ('VOC', 0.0)] -) +@pytest.mark.parametrize("gas,rate", [("CO2", 123.45), ("N2O", 45.6), ("CO", 0.0), ("CH4", 0.0), ("VOC", 0.0)]) def test_set_rates(emissions_of_two_gases, gas, rate): - assert emissions_of_two_gases.loc[gas, EM_LAND_USE] == ureg.Quantity(rate, 'tonne/day') + assert emissions_of_two_gases.loc[gas, EM_LAND_USE] == ureg.Quantity(rate, "tonne/day") def test_set_rates_error1(): @@ -47,7 +45,7 @@ def test_set_rates_error2(): e = Emissions() with pytest.raises(EmissionsError, match=r".*Unrecognized category*"): - e.set_rates('Not-a-category', CO2=123.45) + e.set_rates("Not-a-category", CO2=123.45) @pytest.fixture @@ -59,16 +57,17 @@ def emissions_for_gwp(): @pytest.mark.parametrize( "gwp_horizon, gwp_version, expected", - [(20, 'AR4', 1000 + 10 * 289 + 2 * 72 + 7.65 + 14), - (20, 'AR5', 1000 + 10 * 264 + 2 * 84 + 7.65 + 14), - (20, 'AR5_CCF', 1000 + 10 * 264 + 2 * 86 + 18.6 + 14), - (100, 'AR4', 1000 + 10 * 298 + 2 * 25 + 1.6 + 3.1), - (100, 'AR5', 1000 + 10 * 265 + 2 * 30 + 2.7 + 4.5), - ] + [ + (20, "AR4", 1000 + 10 * 289 + 2 * 72 + 7.65 + 14), + (20, "AR5", 1000 + 10 * 264 + 2 * 84 + 7.65 + 14), + (20, "AR5_CCF", 1000 + 10 * 264 + 2 * 86 + 18.6 + 14), + (100, "AR4", 1000 + 10 * 298 + 2 * 25 + 1.6 + 3.1), + (100, "AR5", 1000 + 10 * 265 + 2 * 30 + 2.7 + 4.5), + ], ) def test_gwp(test_model, emissions_for_gwp, gwp_horizon, gwp_version, expected): original_rates = emissions_for_gwp.data.copy() - analysis = test_model.get_analysis('test') + analysis = test_model.get_analysis("test") analysis.use_GWP(gwp_horizon, gwp_version) rates = emissions_for_gwp.rates(gwp=analysis.gwp) @@ -77,13 +76,13 @@ def test_gwp(test_model, emissions_for_gwp, gwp_horizon, gwp_version, expected): assert all(rates == original_rates) # print(f"GHG for ({gwp_horizon}, {gwp_version} => {ghg}") - assert rates.loc['GHG', EM_FLARING] == ureg.Quantity(pytest.approx(expected), 'tonne/day') + assert rates.loc["GHG", EM_FLARING] == ureg.Quantity(pytest.approx(expected), "tonne/day") def test_use_GWP_error(test_model): with pytest.raises(OpgeeException, match=r".*GWP version must be one of*"): - analysis = test_model.get_analysis('test') - analysis.use_GWP(20, 'AR4_CCF') + analysis = test_model.get_analysis("test") + analysis.use_GWP(20, "AR4_CCF") def test_units(): @@ -92,8 +91,8 @@ def test_units(): def test_add_from_series(): em = Emissions() - d = {'CO': 1.0, 'C1': 2.0, 'CO2': 3.0} + d = {"CO": 1.0, "C1": 2.0, "CO2": 3.0} s = pd.Series(d, dtype="pint[tonne/day]") em.add_from_series(EM_FLARING, s) - for series_name, em_name in (('CO', 'CO'), ('C1', 'CH4'), ('CO2', 'CO2')): - assert em.data.loc[em_name, EM_FLARING] == ureg.Quantity(d[series_name], 'tonne/day') + for series_name, em_name in (("CO", "CO"), ("C1", "CH4"), ("CO2", "CO2")): + assert em.data.loc[em_name, EM_FLARING] == ureg.Quantity(d[series_name], "tonne/day") diff --git a/tests/test_energy.py b/tests/test_energy.py index dc074f0d..62b46642 100644 --- a/tests/test_energy.py +++ b/tests/test_energy.py @@ -1,18 +1,19 @@ from opgee.units import ureg -from opgee.energy import (Energy, EN_DIESEL, EN_NATURAL_GAS, EN_RESID, - EN_PETCOKE, EN_CRUDE_OIL, EN_ELECTRICITY) +from opgee.energy import Energy, EN_DIESEL, EN_NATURAL_GAS, EN_RESID, EN_PETCOKE, EN_CRUDE_OIL, EN_ELECTRICITY from opgee.error import OpgeeException import pytest + def test_set_rate(): e = Energy() - rate = ureg.Quantity(123.45, 'mmbtu/day') + rate = ureg.Quantity(123.45, "mmbtu/day") e.set_rate(EN_DIESEL, rate) assert e.data[EN_DIESEL] == rate + def test_set_electricity(): e = Energy() - rate = ureg.Quantity(123.45, 'kWh/day') + rate = ureg.Quantity(123.45, "kWh/day") e.set_rate(EN_ELECTRICITY, rate) # Though set as "kWh/day", value is stored as "mmBtu/day", after conversion @@ -21,26 +22,30 @@ def test_set_electricity(): assert e.data[EN_ELECTRICITY] == rate + def test_set_rates_error(): """Test that an unknown carrier name throws an OpgeeException""" e = Energy() with pytest.raises(OpgeeException, match=r".*Unrecognized carrier*"): - e.set_rate('Uranium', 4321) + e.set_rate("Uranium", 4321) + def test_add_rate(): e = Energy() - rate = ureg.Quantity(40.0, 'mmbtu/day') + rate = ureg.Quantity(40.0, "mmbtu/day") e.set_rate(EN_DIESEL, rate) e.add_rate(EN_DIESEL, rate) - assert e.data[EN_DIESEL] == ureg.Quantity(80.0, 'mmbtu/day') + assert e.data[EN_DIESEL] == ureg.Quantity(80.0, "mmbtu/day") + def test_add_rate_error(): """Test that an unknown carrier name throws an OpgeeException""" e = Energy() with pytest.raises(OpgeeException, match=r".*Unrecognized carrier*"): - e.add_rates({'Uranium': 4321}) + e.add_rates({"Uranium": 4321}) + @pytest.fixture def two_carriers(): @@ -48,12 +53,13 @@ def two_carriers(): e.set_rates({EN_NATURAL_GAS: 123.45, EN_DIESEL: 45.6}) return e.data + @pytest.mark.parametrize( - "carrier, rate", [(EN_NATURAL_GAS, 123.45), (EN_DIESEL, 45.6), (EN_CRUDE_OIL, 0.0), - (EN_PETCOKE, 0.0), (EN_RESID, 0.0)] + "carrier, rate", + [(EN_NATURAL_GAS, 123.45), (EN_DIESEL, 45.6), (EN_CRUDE_OIL, 0.0), (EN_PETCOKE, 0.0), (EN_RESID, 0.0)], ) def test_set_rates(two_carriers, carrier, rate): - assert two_carriers[carrier] == ureg.Quantity(rate, 'mmbtu/day') + assert two_carriers[carrier] == ureg.Quantity(rate, "mmbtu/day") def test_set_rates_error(): @@ -61,4 +67,4 @@ def test_set_rates_error(): e = Energy() with pytest.raises(OpgeeException, match=r".*Unrecognized carrier*"): - e.set_rates({EN_NATURAL_GAS: 123.45, 'Random': 4321}) + e.set_rates({EN_NATURAL_GAS: 123.45, "Random": 4321}) diff --git a/tests/test_field.py b/tests/test_field.py index c64f44b3..7d29b65c 100644 --- a/tests/test_field.py +++ b/tests/test_field.py @@ -57,25 +57,26 @@ @pytest.fixture(scope="module") def test_field(configure_logging_for_tests): - return load_test_model('test_fields.xml', - use_default_model=True) # this is required since test_fields references "template" field + return load_test_model( + "test_fields.xml", use_default_model=True + ) # this is required since test_fields references "template" field def test_component_fugitive(test_field): - analysis = test_field.get_analysis('test_fugitive') - oilfield = analysis.get_field('test_component_fugitive_oilfield') + analysis = test_field.get_analysis("test_fugitive") + oilfield = analysis.get_field("test_component_fugitive_oilfield") oilfield_component_fugitive_df = oilfield.component_fugitive_table - assert approx_equal(oilfield_component_fugitive_df['Separation'], ureg.Quantity(3.053545e-05, "frac")) - assert approx_equal(oilfield_component_fugitive_df['CrudeOilStorage'], ureg.Quantity(0.931951, "frac")) - assert approx_equal(oilfield_component_fugitive_df['DownholePump'], ureg.Quantity(0.000410132, "frac")) + assert approx_equal(oilfield_component_fugitive_df["Separation"], ureg.Quantity(3.053545e-05, "frac")) + assert approx_equal(oilfield_component_fugitive_df["CrudeOilStorage"], ureg.Quantity(0.931951, "frac")) + assert approx_equal(oilfield_component_fugitive_df["DownholePump"], ureg.Quantity(0.000410132, "frac")) - gasfield = analysis.get_field('test_component_fugitive_gasfield') + gasfield = analysis.get_field("test_component_fugitive_gasfield") gasfield_component_fugitive_df = gasfield.component_fugitive_table - assert approx_equal(gasfield_component_fugitive_df['Separation'], ureg.Quantity(3.77813e-5, "frac")) - assert approx_equal(gasfield_component_fugitive_df['CrudeOilStorage'], ureg.Quantity(0.4323671, "frac")) - assert approx_equal(gasfield_component_fugitive_df['DownholePump'], ureg.Quantity(7.220268674108583e-05, "frac")) + assert approx_equal(gasfield_component_fugitive_df["Separation"], ureg.Quantity(3.77813e-5, "frac")) + assert approx_equal(gasfield_component_fugitive_df["CrudeOilStorage"], ureg.Quantity(0.4323671, "frac")) + assert approx_equal(gasfield_component_fugitive_df["DownholePump"], ureg.Quantity(7.220268674108583e-05, "frac")) def test_bad_boundary(): diff --git a/tests/test_field_groups.py b/tests/test_field_groups.py index fc7f37d9..3f27efa4 100644 --- a/tests/test_field_groups.py +++ b/tests/test_field_groups.py @@ -47,12 +47,15 @@ """ + def test_field_groups(): field_groups_model = load_model_from_str(field_groups_model_xml) field_groups_model.validate() - analysis = field_groups_model.get_analysis('test') + analysis = field_groups_model.get_analysis("test") # Pattern should match only fields 'test1' and 'test2', and not 'test3' - assert analysis.get_field('test1') and analysis.get_field('test2') and not analysis.get_field('test3', raiseError=False) - - + assert ( + analysis.get_field("test1") + and analysis.get_field("test2") + and not analysis.get_field("test3", raiseError=False) + ) diff --git a/tests/test_graph.py b/tests/test_graph.py index 838e3722..a156115b 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -6,18 +6,20 @@ from opgee.error import CommandlineError from tests.utils_for_tests import path_to_test_file -DEVNULL = 'nul' if IsWindows else '/dev/null' +DEVNULL = "nul" if IsWindows else "/dev/null" + +is_sherlock = os.environ.get("LMOD_SYSHOST") == "sherlock" +xml_path = path_to_test_file("gas_lifting_field.xml") -is_sherlock = os.environ.get('LMOD_SYSHOST') == 'sherlock' -xml_path = path_to_test_file('gas_lifting_field.xml') @pytest.mark.skipif(is_sherlock, reason="requires the graphviz/dot which isn't working on sherlock") @pytest.mark.parametrize( - "args", [ - ['graph', '--classes', 'core', '--classes-output', DEVNULL], - ['graph', '--field', 'gas_lifting_field', '-x', xml_path, '--field-output', DEVNULL], - ['graph', '--hierarchy-output', DEVNULL], - ] + "args", + [ + ["graph", "--classes", "core", "--classes-output", DEVNULL], + ["graph", "--field", "gas_lifting_field", "-x", xml_path, "--field-output", DEVNULL], + ["graph", "--hierarchy-output", DEVNULL], + ], ) def test_graphing(opgee_main, args): try: @@ -29,7 +31,8 @@ def test_graphing(opgee_main, args): assert good + @pytest.mark.skipif(is_sherlock, reason="requires the graphviz/dot which isn't working on sherlock") def test_unknown_field(opgee_main): with pytest.raises(CommandlineError, match=r"Field name .* was not found in model"): - opgee_main.run(None, ['graph', '--field', 'unknown-field']) + opgee_main.run(None, ["graph", "--field", "unknown-field"]) diff --git a/tests/test_import_export.py b/tests/test_import_export.py index e1bb032c..46e54d7d 100644 --- a/tests/test_import_export.py +++ b/tests/test_import_export.py @@ -1,15 +1,16 @@ from opgee.units import ureg from opgee.import_export import ImportExport, ELECTRICITY, NATURAL_GAS + def test_import(): obj = ImportExport() - obj.set_import("proc1", ELECTRICITY, ureg.Quantity(100., "kWh/day")) + obj.set_import("proc1", ELECTRICITY, ureg.Quantity(100.0, "kWh/day")) - obj.set_import("proc2", ELECTRICITY, ureg.Quantity(200., "kWh/day")) - obj.set_import("proc2", NATURAL_GAS, ureg.Quantity(2000., "mmbtu/day")) + obj.set_import("proc2", ELECTRICITY, ureg.Quantity(200.0, "kWh/day")) + obj.set_import("proc2", NATURAL_GAS, ureg.Quantity(2000.0, "mmbtu/day")) - obj.set_export("proc2", NATURAL_GAS, ureg.Quantity(450., "mmbtu/day")) + obj.set_export("proc2", NATURAL_GAS, ureg.Quantity(450.0, "mmbtu/day")) df = obj.imports_exports() @@ -17,10 +18,10 @@ def test_import(): exports = df[ImportExport.EXPORT] net_imp = df[ImportExport.NET_IMPORTS] - assert imports[NATURAL_GAS] == ureg.Quantity(2000., "mmbtu/day") - assert imports[ELECTRICITY] == ureg.Quantity(300., "kWh/day") + assert imports[NATURAL_GAS] == ureg.Quantity(2000.0, "mmbtu/day") + assert imports[ELECTRICITY] == ureg.Quantity(300.0, "kWh/day") - assert exports[NATURAL_GAS] == ureg.Quantity(450., "mmbtu/day") + assert exports[NATURAL_GAS] == ureg.Quantity(450.0, "mmbtu/day") assert net_imp[NATURAL_GAS] == (ureg.Quantity(2000, "mmbtu/day") - ureg.Quantity(450, "mmbtu/day")) @@ -28,7 +29,7 @@ def test_import(): proc2_imp = obj.proc_imports("proc2") proc2_exp = obj.proc_exports("proc2") - proc1_imp[ELECTRICITY] == ureg.Quantity(100., "kWh/day") + proc1_imp[ELECTRICITY] == ureg.Quantity(100.0, "kWh/day") - proc2_imp[ELECTRICITY] == ureg.Quantity(200., "kWh/day") - proc2_exp[NATURAL_GAS] == ureg.Quantity(450., "mmbtu/day") + proc2_imp[ELECTRICITY] == ureg.Quantity(200.0, "kWh/day") + proc2_exp[NATURAL_GAS] == ureg.Quantity(450.0, "mmbtu/day") diff --git a/tests/test_impute.py b/tests/test_impute.py index 59b49184..e26a05ae 100644 --- a/tests/test_impute.py +++ b/tests/test_impute.py @@ -3,6 +3,7 @@ from .utils_for_tests import load_test_model from opgee.units import ureg + class CopyingProcess(Process): def run(self, analysis): pass @@ -15,27 +16,40 @@ def impute(self): input.tp.T = output.tp.T input.tp.P = output.tp.P -class Impute1(CopyingProcess): pass -class Impute2(CopyingProcess): pass -class Impute3(CopyingProcess): pass -class Impute4(CopyingProcess): pass + +class Impute1(CopyingProcess): + pass + + +class Impute2(CopyingProcess): + pass + + +class Impute3(CopyingProcess): + pass + + +class Impute4(CopyingProcess): + pass + @pytest.fixture(scope="module") def good_model(configure_logging_for_tests): - return load_test_model('test_impute_model_good.xml') + return load_test_model("test_impute_model_good.xml") + @pytest.fixture(scope="module") def bad_model(configure_logging_for_tests): - return load_test_model('test_impute_model_bad.xml') + return load_test_model("test_impute_model_bad.xml") def test_impute_cycle_good(good_model): model = good_model model.validate() - analysis = model.get_analysis('test') - field = analysis.get_field('test') + analysis = model.get_analysis("test") + field = analysis.get_field("test") field._impute() - stream = field.find_stream('Impute1 => Impute2') + stream = field.find_stream("Impute1 => Impute2") t, p = stream.tp.get() - assert p == ureg.Quantity(150.0, 'psia') and t == ureg.Quantity(90.0, "degF") + assert p == ureg.Quantity(150.0, "psia") and t == ureg.Quantity(90.0, "degF") diff --git a/tests/test_intermediate_boundary.py b/tests/test_intermediate_boundary.py index 1acf3fec..9408838e 100644 --- a/tests/test_intermediate_boundary.py +++ b/tests/test_intermediate_boundary.py @@ -92,14 +92,14 @@ def run(self, analysis): def test_intermediate_boundary(): model_file = ModelFile.from_xml_string(xml_string, use_default_model=False) model = model_file.model - analysis = model.get_analysis('test') + analysis = model.get_analysis("test") analysis.run(compute_ci=False) - field = model.get_field('test') + field = model.get_field("test") boundary_proc = field.boundary_process(analysis) combined_stream = combine_streams(boundary_proc.inputs) - gas_rate = combined_stream.gas_flow_rate('C1') - oil_rate = combined_stream.liquid_flow_rate('oil') - assert gas_rate == ureg.Quantity(987.0, 't/d') and oil_rate == ureg.Quantity(100.0, 't/d') + gas_rate = combined_stream.gas_flow_rate("C1") + oil_rate = combined_stream.liquid_flow_rate("oil") + assert gas_rate == ureg.Quantity(987.0, "t/d") and oil_rate == ureg.Quantity(100.0, "t/d") energy_flow_rate = field.boundary_energy_flow_rate(analysis) assert energy_flow_rate == ureg.Quantity(pytest.approx(50503.544263), "mmbtu/day") diff --git a/tests/test_mcs.py b/tests/test_mcs.py index acbe72af..47698b2d 100644 --- a/tests/test_mcs.py +++ b/tests/test_mcs.py @@ -14,6 +14,7 @@ def test_distro_xml(): param_list = ParameterList.load() assert param_list + def test_good_distros_xml(): xml_string = """ @@ -125,32 +127,36 @@ def test_merge_siblings(): """) -in1 = path_to_test_file('test_merge_1.xml') -in2 = path_to_test_file('test_merge_2.xml') -in3 = path_to_test_file('test_merge_3.xml') +in1 = path_to_test_file("test_merge_1.xml") +in2 = path_to_test_file("test_merge_2.xml") +in3 = path_to_test_file("test_merge_3.xml") def tmpdir(): - dirname = mkdtemp(prefix='merged_xml_') + dirname = mkdtemp(prefix="merged_xml_") return dirname @pytest.mark.parametrize( - "cmdline,expected", [(f'merge -n "{in1}" "{in2}" -o "merged.xml" --overwrite --no-default-model', merged_1), - (f'merge -n "{in1}" "{in2}" "{in3}" -o "merged.xml" --overwrite --no-default-model', merged_2)]) + "cmdline,expected", + [ + (f'merge -n "{in1}" "{in2}" -o "merged.xml" --overwrite --no-default-model', merged_1), + (f'merge -n "{in1}" "{in2}" "{in3}" -o "merged.xml" --overwrite --no-default-model', merged_2), + ], +) def test_merge(cmdline, expected): out_dir = tmpdir() os.chdir(out_dir) opg(cmdline) - with open(f"{out_dir}/merged.xml", 'rb') as f: + with open(f"{out_dir}/merged.xml", "rb") as f: merged_xml = f.read() merged_root = str_to_xml(merged_xml) # Delete the for comparison - attr_defs = merged_root.find('AttrDefs') + attr_defs = merged_root.find("AttrDefs") attr_defs.getparent().remove(attr_defs) assert_same_xml(merged_root, expected) diff --git a/tests/test_model.py b/tests/test_model.py index 4d3e0bd0..f90c26f7 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -3,51 +3,53 @@ from opgee.stream import Stream from .utils_for_tests import load_test_model, path_to_test_file + @pytest.fixture(scope="function") def test_model2(configure_logging_for_tests): # This fixture also serves to test user classpath - model = load_test_model('test_model2.xml', class_path=path_to_test_file('user_processes.py')) + model = load_test_model("test_model2.xml", class_path=path_to_test_file("user_processes.py")) return model + def test_stream_components(configure_logging_for_tests): - load_test_model('test_model.xml', stream_components='Foo, Bar') + load_test_model("test_model.xml", stream_components="Foo, Bar") comps = Stream.component_names - assert 'Foo' in comps and 'Bar' in comps + assert "Foo" in comps and "Bar" in comps def test_unknown_analysis(test_model2): with pytest.raises(OpgeeException, match=r"Analysis named '.*' is not defined"): - test_model2.get_analysis('non-existent-analysis') + test_model2.get_analysis("non-existent-analysis") def test_unknown_field(test_model2): with pytest.raises(OpgeeException, match=r"Field '.*' is not defined in Model"): - test_model2.get_field('non-existent-field') + test_model2.get_field("non-existent-field") def test_unknown_const(test_model2): with pytest.raises(OpgeeException, match=r"No known constant with name .*"): - test_model2.const('non-existent-const') + test_model2.const("non-existent-const") def test_get_analysis(test_model2): - assert test_model2.get_analysis('Analysis1') + assert test_model2.get_analysis("Analysis1") def test_get_analysis_group_regex(test_model): - assert test_model.get_analysis('test2') + assert test_model.get_analysis("test2") def test_get_analysis_group(test_model): - assert test_model.get_analysis('test3') + assert test_model.get_analysis("test3") def test_get_field(test_model2): - assert test_model2.get_field('Field1') + assert test_model2.get_field("Field1") def test_const(test_model2): - test_model2.const('std-temperature') + test_model2.const("std-temperature") def test_model_children(test_model2): diff --git a/tests/test_model_file.py b/tests/test_model_file.py index aa898a57..eb8ccb96 100644 --- a/tests/test_model_file.py +++ b/tests/test_model_file.py @@ -11,13 +11,15 @@ def test_no_file(): with pytest.raises(OpgeeException, match="ModelFile: no model XML file or string specified"): ModelFile(pathnames, use_default_model=False) # instantiate_model=True, save_to_path=None) + def test_modifies(): - xml_path = path_to_test_file('test_fields.xml') + xml_path = path_to_test_file("test_fields.xml") mf = ModelFile(xml_path, use_default_model=True) + def test_many_fields(): - analysis_name = 'test-fields' - model_xml = path_to_test_file('test-fields-9000.xml') + analysis_name = "test-fields" + model_xml = path_to_test_file("test-fields-9000.xml") field_names = fields_for_analysis(model_xml, analysis_name) assert len(field_names) == 8966 @@ -26,8 +28,8 @@ def test_many_fields(): def test_many_field_comparison(): from opgee.manager import run_serial - analysis_name = 'test-fields' - model_xml_file = path_to_test_file('test-fields-9000.xml') + analysis_name = "test-fields" + model_xml_file = path_to_test_file("test-fields-9000.xml") field_names = fields_for_analysis(model_xml_file, analysis_name) @@ -37,27 +39,34 @@ def test_many_field_comparison(): assert len(results) == N + def test_extract_model(): analysis_name = "test_boundary" model_xml = path_to_test_file("test_boundary.xml") for field_name, xml_str in extract_model(model_xml=model_xml, analysis_name=analysis_name, field_names=[]): - mf = ModelFile.from_xml_string(xml_str, add_stream_components=False, - use_class_path=False, - use_default_model=True, - analysis_names=[analysis_name], - field_names=[field_name]) + mf = ModelFile.from_xml_string( + xml_str, + add_stream_components=False, + use_class_path=False, + use_default_model=True, + analysis_names=[analysis_name], + field_names=[field_name], + ) analysis = mf.model.get_analysis(analysis_name) assert analysis.boundary == "Distribution" assert analysis.fn_unit == "gas" - + analysis_name = "example" model_xml = path_to_test_file("gas_lifting_field.xml") for fn, xs in extract_model(model_xml, analysis_name, []): - mf = ModelFile.from_xml_string(xs, add_stream_components=False, - use_class_path=False, - use_default_model=True, - analysis_names=[analysis_name], - field_names=[fn]) + mf = ModelFile.from_xml_string( + xs, + add_stream_components=False, + use_class_path=False, + use_default_model=True, + analysis_names=[analysis_name], + field_names=[fn], + ) analysis = mf.model.get_analysis(analysis_name) assert analysis.boundary == "Production" - assert analysis.fn_unit == "oil" \ No newline at end of file + assert analysis.fn_unit == "oil" diff --git a/tests/test_molecule_names.py b/tests/test_molecule_names.py index 750e97d8..11b0b719 100644 --- a/tests/test_molecule_names.py +++ b/tests/test_molecule_names.py @@ -2,20 +2,24 @@ from opgee.error import OpgeeException from opgee.stream import molecule_to_carbon, carbon_to_molecule -parameterize_args = ["c_name, m_name", [('C1', 'CH4'), ('C2', 'C2H6'), ('C5', 'C5H12')]] +parameterize_args = ["c_name, m_name", [("C1", "CH4"), ("C2", "C2H6"), ("C5", "C5H12")]] + @pytest.mark.parametrize(*parameterize_args) def test_m_to_c(c_name, m_name): assert carbon_to_molecule(c_name) == m_name + @pytest.mark.parametrize(*parameterize_args) def test_c_to_m(c_name, m_name): assert c_name == molecule_to_carbon(m_name) + def test_bad_molecule(): with pytest.raises(OpgeeException, match=r".*Expected hydrocarbon molecule name*"): - molecule_to_carbon('H2O') + molecule_to_carbon("H2O") + def test_bad_carbon(): with pytest.raises(OpgeeException, match=r".*Expected carbon number name*"): - carbon_to_molecule('CH4') + carbon_to_molecule("CH4") diff --git a/tests/test_opgee_xml.py b/tests/test_opgee_xml.py index f5455772..a5f5f2fa 100644 --- a/tests/test_opgee_xml.py +++ b/tests/test_opgee_xml.py @@ -10,9 +10,10 @@ def opgee_model(): mf = ModelFile(glf_xml_path, use_default_model=True) return mf.model + def test_gas_lifting_field(opgee_model): - analysis = opgee_model.get_analysis('example') - field = analysis.get_field('gas_lifting_field') + analysis = opgee_model.get_analysis("example") + field = analysis.get_field("gas_lifting_field") # Just testing that we can run the fields without error - field.run(analysis) \ No newline at end of file + field.run(analysis) diff --git a/tests/test_packet.py b/tests/test_packet.py index 45b803b8..f365ce09 100644 --- a/tests/test_packet.py +++ b/tests/test_packet.py @@ -1,14 +1,16 @@ import pytest from opgee.manager import FieldPacket, TrialPacket, _batched + def test_batched(): with pytest.raises(ValueError, match="_batched: length must be > 0"): list(_batched([1, 2, 3], 0)) + def test_trial_packet(): trial_nums = [1, 2, 4, 5, 10] - field_name = 'field-1' - sim_dir = '/foo/bar/baz' + field_name = "field-1" + sim_dir = "/foo/bar/baz" pkt = TrialPacket(sim_dir, field_name, trial_nums) assert pkt.sim_dir == sim_dir @@ -16,10 +18,11 @@ def test_trial_packet(): assert pkt.field_name == field_name assert list(n for n in pkt) == trial_nums + def test_field_packet(): - model_xml_file = '/foo/bar/baz.xml' - field_names = ['a', 'b', 'c', 'd'] - analysis_name = 'a3' + model_xml_file = "/foo/bar/baz.xml" + field_names = ["a", "b", "c", "d"] + analysis_name = "a3" pkt = FieldPacket(model_xml_file, analysis_name, field_names) assert pkt.model_xml_file == model_xml_file diff --git a/tests/test_post_proc_plugin.py b/tests/test_post_proc_plugin.py index 890a608d..e9dd678a 100644 --- a/tests/test_post_proc_plugin.py +++ b/tests/test_post_proc_plugin.py @@ -8,26 +8,29 @@ from .utils_for_tests import load_test_model, path_to_test_file, tempdir + @pytest.fixture(scope="function") def test_model2(configure_logging_for_tests): # This fixture also serves to test user classpath - model = load_test_model('test_model2.xml', class_path=path_to_test_file('user_processes.py')) + model = load_test_model("test_model2.xml", class_path=path_to_test_file("user_processes.py")) return model + @pytest.fixture(autouse=True) def decach_around_tests(): PostProcessor.decache() yield PostProcessor.decache() + def test_simple_post_processor(test_model2): - analysis = test_model2.get_analysis('Analysis1') - field = test_model2.get_field('Field1') + analysis = test_model2.get_analysis("Analysis1") + field = test_model2.get_field("Field1") result = FieldResult(analysis.name, field.name, DETAILED_RESULT) PostProcessor.decache() - path = path_to_test_file('simple_post_processor.py') + path = path_to_test_file("simple_post_processor.py") plugin = PostProcessor.load_plugin(path) PostProcessor.run_post_processors(analysis, field, result) @@ -35,12 +38,12 @@ def test_simple_post_processor(test_model2): assert len(plugin.results) == 1 pair = plugin.results[0] - assert pair[0] == 'dummy-data' and pair[1] == result + assert pair[0] == "dummy-data" and pair[1] == result output_dir = os.path.dirname(path) PostProcessor.save_post_processor_results(output_dir) - csv_file = os.path.join(output_dir, 'simple_post_processor.csv') + csv_file = os.path.join(output_dir, "simple_post_processor.csv") assert os.path.exists(csv_file) os.remove(csv_file) @@ -52,9 +55,10 @@ class InvalidPostProcessor(PostProcessor): # Doesn't define required run() method pass + def test_invalid_post_processor(test_model2): - analysis = test_model2.get_analysis('Analysis1') - field = test_model2.get_field('Field1') + analysis = test_model2.get_analysis("Analysis1") + field = test_model2.get_field("Field1") result = FieldResult(analysis.name, field.name, DETAILED_RESULT) # class is missing run() method @@ -63,95 +67,112 @@ def test_invalid_post_processor(test_model2): with pytest.raises(AbstractMethodError): instance.run(analysis, field, result) + def test_missing_plugin(): - path = path_to_test_file('MISSING-FILE.py') + path = path_to_test_file("MISSING-FILE.py") with pytest.raises(McsUserError, match=r"Path to plugin '.*' does not exist"): PostProcessor.load_plugin(path) + def test_missing_subclass(): - path = path_to_test_file('broken_post_proc_plugin.py') + path = path_to_test_file("broken_post_proc_plugin.py") - with pytest.raises(McsUserError, match=r'No subclass of PostProcessor .*'): + with pytest.raises(McsUserError, match=r"No subclass of PostProcessor .*"): PostProcessor.load_plugin(path) + @pytest.mark.skip() def test_cmd_line_post_proc(opgee_main): PostProcessor.decache() - plugin_path = path_to_test_file('simple_post_processor.py') - xml_path = path_to_test_file('test_run_subcmd.xml') + plugin_path = path_to_test_file("simple_post_processor.py") + xml_path = path_to_test_file("test_run_subcmd.xml") with tempdir() as output_dir: args = [ - 'run', - '-m', xml_path, - '-a', 'test', - '--no-default-model', - '--cluster-type=serial', - '--output-dir', output_dir, - '--post-proc-plugin', plugin_path, + "run", + "-m", + xml_path, + "-a", + "test", + "--no-default-model", + "--cluster-type=serial", + "--output-dir", + output_dir, + "--post-proc-plugin", + plugin_path, ] - print("opg ", ' '.join(args)) + print("opg ", " ".join(args)) opgee_main.run(None, args) inst = PostProcessor.instances assert len(inst) == 1 - assert inst[0].__class__.__name__ == 'SimplePostProcessor' + assert inst[0].__class__.__name__ == "SimplePostProcessor" - csv_file = os.path.join(output_dir, 'simple_post_processor.csv') + csv_file = os.path.join(output_dir, "simple_post_processor.csv") assert os.path.exists(csv_file) + def test_auto_loading(opgee_main): from opgee.config import setParam + PostProcessor.decache() - setParam('OPGEE.PostProcPluginPath', path_to_test_file('post-proc-plugins')) + setParam("OPGEE.PostProcPluginPath", path_to_test_file("post-proc-plugins")) - xml_path = path_to_test_file('test_run_subcmd.xml') + xml_path = path_to_test_file("test_run_subcmd.xml") with tempdir() as output_dir: args = [ - 'run', - '-m', xml_path, - '-a', 'test', - '--no-default-model', - '--cluster-type=serial', - '--output-dir', output_dir, + "run", + "-m", + xml_path, + "-a", + "test", + "--no-default-model", + "--cluster-type=serial", + "--output-dir", + output_dir, ] - print("opg ", ' '.join(args)) + print("opg ", " ".join(args)) opgee_main.run(None, args) inst = PostProcessor.instances assert len(inst) == 2 - assert inst[0].__class__.__name__ == 'PostProcessor_1' - assert inst[1].__class__.__name__ == 'PostProcessor_2' + assert inst[0].__class__.__name__ == "PostProcessor_1" + assert inst[1].__class__.__name__ == "PostProcessor_2" for i in (1, 2): - csv_file = os.path.join(output_dir, f'auto_loaded_post_proc_{i}.csv') + csv_file = os.path.join(output_dir, f"auto_loaded_post_proc_{i}.csv") assert os.path.exists(csv_file) + @pytest.mark.skip() def test_no_auto_loading(opgee_main): from opgee.config import setParam + PostProcessor.decache() - setParam('OPGEE.PostProcPluginPath', path_to_test_file('post-proc-plugins')) + setParam("OPGEE.PostProcPluginPath", path_to_test_file("post-proc-plugins")) - xml_path = path_to_test_file('test_run_subcmd.xml') + xml_path = path_to_test_file("test_run_subcmd.xml") with tempdir() as output_dir: args = [ - 'run', - '-m', xml_path, - '-a', 'test', - '--no-default-model', - '--cluster-type=serial', - '--output-dir', output_dir, - '--no-post-proc-plugin-path' + "run", + "-m", + xml_path, + "-a", + "test", + "--no-default-model", + "--cluster-type=serial", + "--output-dir", + output_dir, + "--no-post-proc-plugin-path", ] - print("opg ", ' '.join(args)) + print("opg ", " ".join(args)) opgee_main.run(None, args) diff --git a/tests/test_process_groups.py b/tests/test_process_groups.py index 63cb929b..d26022e7 100644 --- a/tests/test_process_groups.py +++ b/tests/test_process_groups.py @@ -5,13 +5,13 @@ @pytest.fixture(scope="module") def process_groups_model(configure_logging_for_tests): - return load_test_model('test_process_groups.xml') + return load_test_model("test_process_groups.xml") @pytest.fixture(scope="module") def test_field(process_groups_model): - analysis = process_groups_model.get_analysis('test') - field = analysis.get_field('test') + analysis = process_groups_model.get_analysis("test") + field = analysis.get_field("test") return field @@ -23,24 +23,26 @@ def gas_paths(test_field): # TODO: Add a test to procs and streams that are not chosen def test_parsing(gas_paths): groups_names = gas_paths.group_names() - assert gas_paths.name == 'oil_sands_mine' and len(groups_names) == 2 + assert gas_paths.name == "oil_sands_mine" and len(groups_names) == 2 - non_oil_sands_group = gas_paths.groups_dict['none'] - sub_group = non_oil_sands_group.process_choice_dict['gas_processing_path'] + non_oil_sands_group = gas_paths.groups_dict["none"] + sub_group = non_oil_sands_group.process_choice_dict["gas_processing_path"] groups_names = sub_group.group_names() - assert sub_group.name == 'gas_processing_path' and len(groups_names) == 8 + assert sub_group.name == "gas_processing_path" and len(groups_names) == 8 - acid_gas = sub_group.get_group('Acid Gas') + acid_gas = sub_group.get_group("Acid Gas") proc_names, stream_names = acid_gas.process_and_stream_refs() - assert set(stream_names) == {'AcidGasRemoval => GasPartition', - 'GasDehydration => AcidGasRemoval', - 'GasGathering => GasDehydration'} + assert set(stream_names) == { + "AcidGasRemoval => GasPartition", + "GasDehydration => AcidGasRemoval", + "GasGathering => GasDehydration", + } - assert set(proc_names) == {'GasDehydration', 'GasGathering', 'AcidGasRemoval'} + assert set(proc_names) == {"GasDehydration", "GasGathering", "AcidGasRemoval"} def test_missing_group(gas_paths): - assert gas_paths.get_group('non-existent-group', raiseError=False) is None + assert gas_paths.get_group("non-existent-group", raiseError=False) is None with pytest.raises(OpgeeException, match="Process choice '.*' not found in field '.*'"): - gas_paths.get_group('non-existent-group') + gas_paths.get_group("non-existent-group") diff --git a/tests/test_process_loop.py b/tests/test_process_loop.py index 34968c2e..b124ca5d 100644 --- a/tests/test_process_loop.py +++ b/tests/test_process_loop.py @@ -9,13 +9,13 @@ def run(self, analysis): # find appropriate streams by checking connected processes' capabilities oil_flow_rate = ureg.Quantity(100.0, Stream.units()) - out_stream = self.find_output_stream('oil') - out_stream.set_liquid_flow_rate('oil', oil_flow_rate) + out_stream = self.find_output_stream("oil") + out_stream.set_liquid_flow_rate("oil", oil_flow_rate) class LoopProc2(Process): def run(self, analysis): - crude_oil = self.find_input_stream('oil') + crude_oil = self.find_input_stream("oil") recycled_water = self.find_input_stream("water") if crude_oil.is_uninitialized() and recycled_water.is_uninitialized(): @@ -66,16 +66,17 @@ def run(self, analysis): def test_process_single_loop(): - process_loop_model = load_test_model('test_process_loop_model.xml') - analysis = process_loop_model.get_analysis('test') - field = analysis.get_field('test_single_loop') + process_loop_model = load_test_model("test_process_loop_model.xml") + analysis = process_loop_model.get_analysis("test") + field = analysis.get_field("test_single_loop") field.run(analysis, compute_ci=False) def test_process_double_loops(): - process_loop_model = load_test_model('test_process_loop_model.xml') - analysis = process_loop_model.get_analysis('test') - field = analysis.get_field('test_double_loops') + process_loop_model = load_test_model("test_process_loop_model.xml") + analysis = process_loop_model.get_analysis("test") + field = analysis.get_field("test_double_loops") field.run(analysis, compute_ci=False) + # TBD: add tests that no elements of cycle are tagged run-after=True in XML diff --git a/tests/test_processes.py b/tests/test_processes.py index 5d3b4d0e..65c92b8d 100644 --- a/tests/test_processes.py +++ b/tests/test_processes.py @@ -7,45 +7,46 @@ from opgee.process import Process, _get_subclass, Reservoir -class NotProcess(): pass +class NotProcess: + pass def test_subclass_lookup_good(test_model): - assert _get_subclass(Process, 'ProcA') + assert _get_subclass(Process, "ProcA") def test_subclass_lookup_bad_subclass(test_model): - with pytest.raises(OpgeeException, match=r'Class .* is not a known subclass of .*'): - _get_subclass(Process, 'NonExistentProcess') + with pytest.raises(OpgeeException, match=r"Class .* is not a known subclass of .*"): + _get_subclass(Process, "NonExistentProcess") def test_subclass_lookup_bad_parent(test_model): - with pytest.raises(OpgeeException, match=r'_get_subclass: cls .* must be one of .*'): - _get_subclass(NotProcess, 'NonExistentProcess') + with pytest.raises(OpgeeException, match=r"_get_subclass: cls .* must be one of .*"): + _get_subclass(NotProcess, "NonExistentProcess") def test_set_emission_rates(test_model_with_change): - analysis = test_model_with_change.get_analysis('test') - field = analysis.get_field('test') - procA = field.find_process('ProcA') + analysis = test_model_with_change.get_analysis("test") + field = analysis.get_field("test") + procA = field.find_process("ProcA") - rate_co2 = ureg.Quantity(100.0, 'tonne/day') - rate_ch4 = ureg.Quantity(30.0, 'tonne/day') - rate_n2o = ureg.Quantity(6.0, 'tonne/day') + rate_co2 = ureg.Quantity(100.0, "tonne/day") + rate_ch4 = ureg.Quantity(30.0, "tonne/day") + rate_n2o = ureg.Quantity(6.0, "tonne/day") procA.add_emission_rates(EM_FLARING, CO2=rate_co2, CH4=rate_ch4, N2O=rate_n2o) df = procA.get_emission_rates(analysis) rates = df[EM_FLARING] - assert (rates.N2O == rate_n2o and rates.CH4 == rate_ch4 and rates.CO2 == rate_co2) + assert rates.N2O == rate_n2o and rates.CH4 == rate_ch4 and rates.CO2 == rate_co2 def test_add_energy_rates(test_model_with_change): - analysis = test_model_with_change.get_analysis('test') - field = analysis.get_field('test') - procA = field.find_process('ProcA') + analysis = test_model_with_change.get_analysis("test") + field = analysis.get_field("test") + procA = field.find_process("ProcA") - unit = ureg.Unit('mmbtu/day') + unit = ureg.Unit("mmbtu/day") ng_rate = ureg.Quantity(123.45, unit) oil_rate = ureg.Quantity(4321.0, unit) @@ -53,14 +54,14 @@ def test_add_energy_rates(test_model_with_change): rates = procA.get_energy_rates() - assert (rates[EN_NATURAL_GAS] == ng_rate and rates[EN_CRUDE_OIL] == oil_rate) + assert rates[EN_NATURAL_GAS] == ng_rate and rates[EN_CRUDE_OIL] == oil_rate -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def process(test_model): - analysis = test_model.get_analysis('test') - field = analysis.get_field('test') - proc = field.find_process('ProcA') + analysis = test_model.get_analysis("test") + field = analysis.get_field("test") + proc = field.find_process("ProcA") return proc @@ -68,11 +69,11 @@ def test_get_reservoir(process): assert isinstance(process.get_reservoir(), Reservoir) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def procB(test_model): - analysis = test_model.get_analysis('test') - field = analysis.get_field('test') - proc = field.find_process('ProcB') + analysis = test_model.get_analysis("test") + field = analysis.get_field("test") + proc = field.find_process("ProcB") return proc @@ -95,15 +96,15 @@ def test_find_output_stream(process): def test_find_input_stream_error(procB): - stream_type = 'unknown_stream_type' + stream_type = "unknown_stream_type" with pytest.raises(OpgeeException, match=f".* no input streams contain '{stream_type}'"): procB.find_input_stream(stream_type) def test_venting_fugitive_rate(test_model): - analysis = test_model.get_analysis('test') - field = analysis.get_field('test') - procA = field.find_process('ProcA') + analysis = test_model.get_analysis("test") + field = analysis.get_field("test") + procA = field.find_process("ProcA") rate = procA.venting_fugitive_rate() # mean of 1000 random draws from uniform(0.001, .003) should be ~0.002 @@ -133,8 +134,7 @@ def test_venting_fugitive_rate(test_model): baz = "a string" -@pytest.mark.parametrize( - "name, value", [('foo', foo), ('bar', bar), ('baz', baz)]) +@pytest.mark.parametrize("name, value", [("foo", foo), ("bar", bar), ("baz", baz)]) def test_process_data(procB, name, value): field = procB.field field.save_process_data(foo=foo, bar=bar, baz=baz) @@ -143,21 +143,21 @@ def test_process_data(procB, name, value): def test_bad_process_data(procB): - with pytest.raises(OpgeeException, match='Process data dictionary does not include .*'): + with pytest.raises(OpgeeException, match="Process data dictionary does not include .*"): procB.field.get_process_data("nonexistent-data-key", raiseError=True) -def approx_equal(a, b, abs=10E-6, rel=None): +def approx_equal(a, b, abs=10e-6, rel=None): "Check that two Quantities are approximately equal" return a.m == pytest.approx(b.m, abs=abs, rel=rel) # Test gas processing units def test_VRUCompressor(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_VRUCompressor') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_VRUCompressor") field.run(analysis) - proc = field.find_process('VRUCompressor') + proc = field.find_process("VRUCompressor") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(0.348120851, "mmbtu/day") @@ -165,10 +165,10 @@ def test_VRUCompressor(test_model): def test_VFPartition(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_VFPartition') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_VFPartition") field.run(analysis) - proc = field.find_process('VFPartition') + proc = field.find_process("VFPartition") # ensure total energy flow rates total = proc.find_output_stream("methane slip").gas_flow_rates().sum() expected = ureg.Quantity(71.2518871, "tonne/day") @@ -176,10 +176,10 @@ def test_VFPartition(test_model): def test_Flaring(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_Flaring') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_Flaring") field.run(analysis) - proc = field.find_process('Flaring') + proc = field.find_process("Flaring") # ensure total energy flow rates total = proc.emissions.rates(analysis.gwp).loc["GHG"].sum() expected = ureg.Quantity(1406.20784, "tonne/day") @@ -187,10 +187,10 @@ def test_Flaring(test_model): def test_Venting(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_Venting') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_Venting") field.run(analysis) - proc = field.find_process('Venting') + proc = field.find_process("Venting") # ensure total energy flow rates total = proc.emissions.rates(analysis.gwp).loc["GHG"].sum() expected = ureg.Quantity(1403.28708, "tonne/day") @@ -198,13 +198,14 @@ def test_Venting(test_model): def test_AcidGasRemoval_Aspen(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_AcidGasRemoval_Aspen') - processing_unit_loss_rate_df = pd.DataFrame(data=[0.00041373], index=['AcidGasRemoval'], columns=['loss_rate'], - dtype="pint[frac]") + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_AcidGasRemoval_Aspen") + processing_unit_loss_rate_df = pd.DataFrame( + data=[0.00041373], index=["AcidGasRemoval"], columns=["loss_rate"], dtype="pint[frac]" + ) field.save_process_data(processing_unit_loss_rate_df=processing_unit_loss_rate_df) field.run(analysis) - proc = field.find_process('AcidGasRemoval') + proc = field.find_process("AcidGasRemoval") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(162.067848, "mmbtu/day") @@ -212,13 +213,14 @@ def test_AcidGasRemoval_Aspen(test_model): def test_AcidGasRemoval_testbook(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_AcidGasRemoval_testbook') - processing_unit_loss_rate_df = pd.DataFrame(data=[0.000259240], index=['AcidGasRemoval'], columns=['loss_rate'], - dtype="pint[frac]") + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_AcidGasRemoval_testbook") + processing_unit_loss_rate_df = pd.DataFrame( + data=[0.000259240], index=["AcidGasRemoval"], columns=["loss_rate"], dtype="pint[frac]" + ) field.save_process_data(processing_unit_loss_rate_df=processing_unit_loss_rate_df) field.run(analysis) - proc = field.find_process('AcidGasRemoval') + proc = field.find_process("AcidGasRemoval") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(782593.6552049, "mmbtu/day") @@ -226,13 +228,14 @@ def test_AcidGasRemoval_testbook(test_model): def test_GasDehydration(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_GasDehydration') - processing_unit_loss_rate_df = pd.DataFrame(data=[0.00037981], index=['GasDehydration'], columns=['loss_rate'], - dtype="pint[frac]") + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_GasDehydration") + processing_unit_loss_rate_df = pd.DataFrame( + data=[0.00037981], index=["GasDehydration"], columns=["loss_rate"], dtype="pint[frac]" + ) field.save_process_data(processing_unit_loss_rate_df=processing_unit_loss_rate_df) field.run(analysis) - proc = field.find_process('GasDehydration') + proc = field.find_process("GasDehydration") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(680.520234, "mmbtu/day") @@ -240,13 +243,14 @@ def test_GasDehydration(test_model): def test_Demethanizer(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_Demethanizer') - processing_unit_loss_rate_df = pd.DataFrame(data=[0.0], index=['Demethanizer'], columns=['loss_rate'], - dtype="pint[frac]") + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_Demethanizer") + processing_unit_loss_rate_df = pd.DataFrame( + data=[0.0], index=["Demethanizer"], columns=["loss_rate"], dtype="pint[frac]" + ) field.save_process_data(processing_unit_loss_rate_df=processing_unit_loss_rate_df) field.run(analysis) - proc = field.find_process('Demethanizer') + proc = field.find_process("Demethanizer") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(37.8508077, "mmbtu/day") @@ -254,10 +258,10 @@ def test_Demethanizer(test_model): def test_PreMembraneChiller(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_PreMembraneChiller') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_PreMembraneChiller") field.run(analysis) - proc = field.find_process('PreMembraneChiller') + proc = field.find_process("PreMembraneChiller") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(907.0197708571166, "mmbtu/day") @@ -265,19 +269,19 @@ def test_PreMembraneChiller(test_model): def test_PreMembraneCompressor(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_PreMembraneCompressor') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_PreMembraneCompressor") field.run(analysis) - proc = field.find_process('PreMembraneCompressor') + proc = field.find_process("PreMembraneCompressor") # ensure total energy flow rates assert proc.energy.data.sum() == ureg.Quantity(0.0, "mmbtu/day") def test_CO2Membrane(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_CO2Membrane') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_CO2Membrane") field.run(analysis) - proc = field.find_process('CO2Membrane') + proc = field.find_process("CO2Membrane") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(4028.59019, "mmbtu/day") @@ -285,10 +289,10 @@ def test_CO2Membrane(test_model): def test_CO2ReinjectionCompressor(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_CO2ReinjectionCompressor') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_CO2ReinjectionCompressor") field.run(analysis) - proc = field.find_process('CO2ReinjectionCompressor') + proc = field.find_process("CO2ReinjectionCompressor") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(5373.74484, "mmbtu/day") @@ -296,10 +300,10 @@ def test_CO2ReinjectionCompressor(test_model): def test_CO2InjectionWell(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_CO2InjectionWell') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_CO2InjectionWell") field.run(analysis) - proc = field.find_process('CO2InjectionWell') + proc = field.find_process("CO2InjectionWell") # ensure total energy flow rates total = proc.emissions.rates(analysis.gwp).loc["GHG"].sum() expected = ureg.Quantity(0.287365, "tonne/day") @@ -307,12 +311,14 @@ def test_CO2InjectionWell(test_model): def test_RyanHolmes(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_RyanHolmes') - processing_unit_loss_rate_df = pd.DataFrame(data=[0], index=['RyanHolmes'], columns=['loss_rate'], dtype="pint[frac]") + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_RyanHolmes") + processing_unit_loss_rate_df = pd.DataFrame( + data=[0], index=["RyanHolmes"], columns=["loss_rate"], dtype="pint[frac]" + ) field.save_process_data(processing_unit_loss_rate_df=processing_unit_loss_rate_df) field.run(analysis) - proc = field.find_process('RyanHolmes') + proc = field.find_process("RyanHolmes") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(415.443372, "mmbtu/day") @@ -320,10 +326,10 @@ def test_RyanHolmes(test_model): def test_SourGasCompressor(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_SourGasCompressor') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_SourGasCompressor") field.run(analysis) - proc = field.find_process('SourGasCompressor') + proc = field.find_process("SourGasCompressor") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(253.621191, "mmbtu/day") @@ -331,10 +337,10 @@ def test_SourGasCompressor(test_model): def test_SourGasInjection(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_SourGasInjection') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_SourGasInjection") field.run(analysis) - proc = field.find_process('SourGasInjection') + proc = field.find_process("SourGasInjection") # ensure total energy flow rates total = proc.emissions.rates(analysis.gwp).loc["GHG"].sum() expected = ureg.Quantity(0.449629401, "tonne/day") @@ -342,18 +348,19 @@ def test_SourGasInjection(test_model): def test_GasReinjectionCompressor(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_GasReinjectionCompressor') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_GasReinjectionCompressor") field.run(analysis) - proc = field.find_process('GasReinjectionCompressor') + proc = field.find_process("GasReinjectionCompressor") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(64913.96925930, "mmbtu/day") assert approx_equal(total, expected, rel=10e-3) - + + def test_TransmissionCompressor(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_TransmissionCompressor') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_TransmissionCompressor") field.run(analysis) proc = field.find_process("TransmissionCompressor") total = proc.energy.data.sum() @@ -362,10 +369,10 @@ def test_TransmissionCompressor(test_model): def test_N2Flooding(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_N2Flooding') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_N2Flooding") field.run(analysis) - proc = field.find_process('GasPartition') + proc = field.find_process("GasPartition") # ensure total energy flow rates total = proc.find_output_stream("gas").gas_flow_rates().sum() expected = ureg.Quantity(25086.65151856, "tonne/day") @@ -377,10 +384,10 @@ def test_N2Flooding(test_model): def test_CO2Flooding_CO2_reinjection(test_model_with_change): - analysis = test_model_with_change.get_analysis('test_gas_processes') - field = analysis.get_field('test_CO2Flooding') + analysis = test_model_with_change.get_analysis("test_gas_processes") + field = analysis.get_field("test_CO2Flooding") field.run(analysis) - proc = field.find_process('GasPartition') + proc = field.find_process("GasPartition") # ensure total energy flow rates total = proc.find_output_stream("gas").gas_flow_rates().sum() expected = ureg.Quantity(9976.19977172, "tonne/day") @@ -392,10 +399,10 @@ def test_CO2Flooding_CO2_reinjection(test_model_with_change): def test_CO2Flooding_non_zero(test_model_with_change): - analysis = test_model_with_change.get_analysis('test_gas_processes') - field = analysis.get_field('test_CO2Flooding') + analysis = test_model_with_change.get_analysis("test_gas_processes") + field = analysis.get_field("test_CO2Flooding") field.run(analysis) - proc = field.find_process('GasPartition') + proc = field.find_process("GasPartition") # ensure total energy flow rates total = proc.find_output_stream("gas").gas_flow_rates().sum() expected = ureg.Quantity(9976.199771722018, "tonne/day") @@ -407,10 +414,10 @@ def test_CO2Flooding_non_zero(test_model_with_change): def test_NGFlooding_onsite(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_NGFlooding_onsite_gas') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_NGFlooding_onsite_gas") field.run(analysis) - proc = field.find_process('GasPartition') + proc = field.find_process("GasPartition") # ensure total energy flow rates total = proc.find_output_stream("gas").gas_flow_rates().sum() expected = ureg.Quantity(3825.799, "tonne/day") @@ -422,10 +429,10 @@ def test_NGFlooding_onsite(test_model): def test_CO2Flooding_sour_gas_reinjection(test_model_with_change): - analysis = test_model_with_change.get_analysis('test_gas_processes') - field = analysis.get_field('test_CO2Flooding') + analysis = test_model_with_change.get_analysis("test_gas_processes") + field = analysis.get_field("test_CO2Flooding") field.run(analysis) - proc = field.find_process('GasPartition') + proc = field.find_process("GasPartition") # ensure total energy flow rates s = proc.find_output_stream("gas") @@ -439,10 +446,10 @@ def test_CO2Flooding_sour_gas_reinjection(test_model_with_change): def test_NGFlooding_offset(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_NGFlooding_offset_gas') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_NGFlooding_offset_gas") field.run(analysis) - proc = field.find_process('GasPartition') + proc = field.find_process("GasPartition") # ensure total energy flow rates total = proc.find_output_stream("gas").gas_flow_rates().sum() expected = ureg.Quantity(428933.382, "tonne/day") @@ -454,10 +461,10 @@ def test_NGFlooding_offset(test_model): def test_GasLifting_low_GLIR(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_GasLifting_low_GLIR') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_GasLifting_low_GLIR") field.run(analysis) - proc = field.find_process('GasPartition') + proc = field.find_process("GasPartition") # ensure total energy flow rates total = proc.find_output_stream("lifting gas").gas_flow_rates().sum() expected = ureg.Quantity(176.061318, "tonne/day") @@ -469,10 +476,10 @@ def test_GasLifting_low_GLIR(test_model): def test_GasLifting_high_GLIR(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_GasLifting_high_GLIR') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_GasLifting_high_GLIR") field.run(analysis) - proc = field.find_process('GasPartition') + proc = field.find_process("GasPartition") # ensure total energy flow rates total = proc.find_output_stream("lifting gas").gas_flow_rates().sum() expected = ureg.Quantity(1662.48754, "tonne/day") @@ -485,10 +492,10 @@ def test_GasLifting_high_GLIR(test_model): # Test common processing units def test_ReservoirWellInterface(test_model): - analysis = test_model.get_analysis('test_common_processes') - field = analysis.get_field('test_ReservoirWellInterface') + analysis = test_model.get_analysis("test_common_processes") + field = analysis.get_field("test_ReservoirWellInterface") field.run(analysis) - proc = field.find_process('ReservoirWellInterface') + proc = field.find_process("ReservoirWellInterface") # ensure output stream pressure pressure = proc.find_output_stream("oil").tp.P expected = ureg.Quantity(1324.23673, "mmbtu/day") @@ -496,11 +503,11 @@ def test_ReservoirWellInterface(test_model): def test_ReservoirWellInterface_CO2_flood(test_model): - analysis = test_model.get_analysis('test_common_processes') - field = analysis.get_field('test_ReservoirWellInterface_CO2_flood') + analysis = test_model.get_analysis("test_common_processes") + field = analysis.get_field("test_ReservoirWellInterface_CO2_flood") field.save_process_data(CO2_mass_rate=ureg.Quantity(827.74208, "tonne/day")) field.run(analysis) - proc = field.find_process('ReservoirWellInterface') + proc = field.find_process("ReservoirWellInterface") # ensure output stream pressure pressure = proc.find_output_stream("oil").tp.P # some dependency update slightly changed the value @@ -510,9 +517,9 @@ def test_ReservoirWellInterface_CO2_flood(test_model): def test_DownholePump(test_model): - analysis = test_model.get_analysis('test_common_processes') - field = analysis.get_field('test_DownholePump') - proc = field.find_process('DownholePump') + analysis = test_model.get_analysis("test_common_processes") + field = analysis.get_field("test_DownholePump") + proc = field.find_process("DownholePump") field.run(analysis) # ensure total energy flow rates total = proc.energy.data.sum() @@ -521,9 +528,9 @@ def test_DownholePump(test_model): def test_DownholePump_with_GasLifting(test_model): - analysis = test_model.get_analysis('test_common_processes') - field = analysis.get_field('test_DownholePump_with_GasLifting') - proc = field.find_process('DownholePump') + analysis = test_model.get_analysis("test_common_processes") + field = analysis.get_field("test_DownholePump_with_GasLifting") + proc = field.find_process("DownholePump") field.run(analysis) # ensure total energy flow rates total = proc.energy.data.sum() @@ -537,9 +544,9 @@ def test_DownholePump_with_GasLifting(test_model): def test_Separation(test_model): - analysis = test_model.get_analysis('test_gas_processes') - field = analysis.get_field('test_Separation') - proc = field.find_process('Separation') + analysis = test_model.get_analysis("test_gas_processes") + field = analysis.get_field("test_Separation") + proc = field.find_process("Separation") field.run(analysis) # ensure total energy flow rates total = proc.energy.data.sum() @@ -548,11 +555,11 @@ def test_Separation(test_model): def test_CrudeOilDewatering(test_model): - analysis = test_model.get_analysis('test_common_processes') + analysis = test_model.get_analysis("test_common_processes") field_storage = analysis.get_field("test_CrudeOilDewatering_Storage") field_storage.run(analysis) - proc = field_storage.find_process('CrudeOilDewatering') + proc = field_storage.find_process("CrudeOilDewatering") output = proc.find_output_stream("oil for storage") # ensure total energy flow rates total = proc.energy.data.sum() @@ -566,10 +573,10 @@ def test_CrudeOilDewatering(test_model): def test_CrudeOilStabilization(test_model): - analysis = test_model.get_analysis('test_common_processes') - field = analysis.get_field('test_CrudeOilStabilization') + analysis = test_model.get_analysis("test_common_processes") + field = analysis.get_field("test_CrudeOilStabilization") field.run(analysis) - proc = field.find_process('CrudeOilStabilization') + proc = field.find_process("CrudeOilStabilization") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(16387.72427865671, "mmbtu/day") @@ -577,10 +584,10 @@ def test_CrudeOilStabilization(test_model): def test_HeavyOilUpgrading(test_model): - analysis = test_model.get_analysis('test_oil_processes') - field = analysis.get_field('test_HeavyOilUpgrading') + analysis = test_model.get_analysis("test_oil_processes") + field = analysis.get_field("test_HeavyOilUpgrading") field.run(analysis) - proc = field.find_process('HeavyOilUpgrading') + proc = field.find_process("HeavyOilUpgrading") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(2881.60814, "mmbtu/day") @@ -588,10 +595,10 @@ def test_HeavyOilUpgrading(test_model): def test_HeavyOilDilution(test_model): - analysis = test_model.get_analysis('test_oil_processes') - field = analysis.get_field('test_HeavyOilDilution') + analysis = test_model.get_analysis("test_oil_processes") + field = analysis.get_field("test_HeavyOilDilution") field.run(analysis) - proc = field.find_process('HeavyOilDilution') + proc = field.find_process("HeavyOilDilution") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(2609.1316115952463, "mmbtu/day") @@ -599,10 +606,10 @@ def test_HeavyOilDilution(test_model): def test_CrudeOilStorage(test_model): - analysis = test_model.get_analysis('test_oil_processes') - field = analysis.get_field('test_CrudeOilStorage') + analysis = test_model.get_analysis("test_oil_processes") + field = analysis.get_field("test_CrudeOilStorage") field.run(analysis) - proc = field.find_process('CrudeOilStorage') + proc = field.find_process("CrudeOilStorage") # ensure total emission flow rates total = proc.emissions.data.loc["GHG"].sum() expected = ureg.Quantity(0.0, "tonne/day") @@ -615,10 +622,10 @@ def test_CrudeOilStorage(test_model): def test_BitumenMining(test_model): - analysis = test_model.get_analysis('test_oil_processes') - field = analysis.get_field('test_BitumenMining') + analysis = test_model.get_analysis("test_oil_processes") + field = analysis.get_field("test_BitumenMining") field.run(analysis) - proc = field.find_process('BitumenMining') + proc = field.find_process("BitumenMining") # ensure total energy flow rates total = proc.energy.data.sum() expected = ureg.Quantity(60717.00400130419, "mmbtu/day") @@ -626,8 +633,8 @@ def test_BitumenMining(test_model): def test_SteamGeneration_OTSG(test_model): - analysis = test_model.get_analysis('test_water_processes') - field = analysis.get_field('test_SteamGeneration_OTSG') + analysis = test_model.get_analysis("test_water_processes") + field = analysis.get_field("test_SteamGeneration_OTSG") try: field.run(analysis) @@ -635,7 +642,7 @@ def test_SteamGeneration_OTSG(test_model): # we expect zero energy flow at boundary on this test pass - proc = field.find_process('SteamGeneration') + proc = field.find_process("SteamGeneration") # ensure total emission flow rates total = proc.energy.data.sum() expected = ureg.Quantity(68056.171381, "mmBtu/day") @@ -643,8 +650,8 @@ def test_SteamGeneration_OTSG(test_model): def test_SteamGeneration_Cogen(test_model): - analysis = test_model.get_analysis('test_water_processes') - field = analysis.get_field('test_SteamGeneration_Cogen') + analysis = test_model.get_analysis("test_water_processes") + field = analysis.get_field("test_SteamGeneration_Cogen") try: field.run(analysis) @@ -652,7 +659,7 @@ def test_SteamGeneration_Cogen(test_model): # we expect zero energy flow at boundary on this test pass - proc = field.find_process('SteamGeneration') + proc = field.find_process("SteamGeneration") # ensure total emission flow rates total = proc.energy.data.sum() expected = ureg.Quantity(105140.98234, "mmBtu/day") @@ -660,8 +667,8 @@ def test_SteamGeneration_Cogen(test_model): def test_SteamGeneration_Solar(test_model): - analysis = test_model.get_analysis('test_water_processes') - field = analysis.get_field('test_SteamGeneration_Solar') + analysis = test_model.get_analysis("test_water_processes") + field = analysis.get_field("test_SteamGeneration_Solar") try: field.run(analysis) @@ -669,7 +676,7 @@ def test_SteamGeneration_Solar(test_model): # we expect zero energy flow at boundary on this test pass - proc = field.find_process('SteamGeneration') + proc = field.find_process("SteamGeneration") # ensure total emission flow rates total = proc.energy.data.sum() expected = ureg.Quantity(1217.86742, "mmBtu/day") @@ -677,8 +684,8 @@ def test_SteamGeneration_Solar(test_model): def test_WaterTreatment(test_model): - analysis = test_model.get_analysis('test_water_processes') - field = analysis.get_field('test_WaterTreatment') + analysis = test_model.get_analysis("test_water_processes") + field = analysis.get_field("test_WaterTreatment") try: field.run(analysis) @@ -686,7 +693,7 @@ def test_WaterTreatment(test_model): # we expect zero energy flow at boundary on this test pass - proc = field.find_process('WaterTreatment') + proc = field.find_process("WaterTreatment") # ensure total emission flow rates total = proc.energy.data.sum() expected = ureg.Quantity(50.774462, "mmBtu/day") @@ -697,15 +704,15 @@ def test_WaterTreatment(test_model): def test_CrudeOilTransport(): from .utils_for_tests import path_to_test_file, load_test_model - model2 = load_test_model('test_model2.xml', class_path=path_to_test_file('user_processes.py')) + model2 = load_test_model("test_model2.xml", class_path=path_to_test_file("user_processes.py")) - analysis = model2.get_analysis('Analysis1') - analysis.boundary = 'Transportation' + analysis = model2.get_analysis("Analysis1") + analysis.boundary = "Transportation" - field = analysis.get_field('Field1') + field = analysis.get_field("Field1") field.run(analysis) - proc = field.find_process('CrudeOilTransport') + proc = field.find_process("CrudeOilTransport") total = proc.find_output_stream("oil").liquid_flow_rate("oil") expected = ureg.Quantity(100.0, "tonne/day") assert approx_equal(total, expected) diff --git a/tests/test_run_subcmd.py b/tests/test_run_subcmd.py index abb116b3..f2a525be 100644 --- a/tests/test_run_subcmd.py +++ b/tests/test_run_subcmd.py @@ -7,115 +7,139 @@ from opgee.tool import opg from .utils_for_tests import path_to_test_file, tempdir + @pytest.fixture(autouse=True) def decache_post_plugins(): PostProcessor.decache() yield + def test_missing_output_dir(opgee_main): - name = 'test' + name = "test" with pytest.raises(CommandlineError, match="Non-MCS runs must specify -o/--output-dir"): - opgee_main.run(None, ['run', '-a', name]) + opgee_main.run(None, ["run", "-a", name]) + def DEPRECATED_test_unknown_analysis(opgee_main): - xml_path = path_to_test_file('test_run_subcmd.xml') - name = 'unknown-analysis' + xml_path = path_to_test_file("test_run_subcmd.xml") + name = "unknown-analysis" with tempdir() as output_dir: - args = ['run', - '-m', xml_path, - '-a', name, - '--no-default-model', - '--cluster-type=serial', - '--output-dir', output_dir] + args = [ + "run", + "-m", + xml_path, + "-a", + name, + "--no-default-model", + "--cluster-type=serial", + "--output-dir", + output_dir, + ] with pytest.raises(CommandlineError, match=r"Specified analyses .* not found in model"): opgee_main.run(None, args) + def test_run_one_field(opgee_main): import pandas as pd - xml_path = path_to_test_file('test_run_subcmd.xml') + xml_path = path_to_test_file("test_run_subcmd.xml") with tempdir() as output_dir: - args = ['run', - '-m', xml_path, - '-a', 'test', - '--no-default-model', - '--cluster-type=serial', - '--output-dir', output_dir] - print("opg ", ' '.join(args)) + args = [ + "run", + "-m", + xml_path, + "-a", + "test", + "--no-default-model", + "--cluster-type=serial", + "--output-dir", + output_dir, + ] + print("opg ", " ".join(args)) opgee_main.run(None, args) - df = pd.read_csv(pathjoin(output_dir, 'carbon_intensity.csv'), index_col='node') + df = pd.read_csv(pathjoin(output_dir, "carbon_intensity.csv"), index_col="node") assert df is not None and len(df) == 4 - assert df.loc['TOTAL', 'CI'] == 0.0 + assert df.loc["TOTAL", "CI"] == 0.0 def test_unknown_field(opgee_main): - name = 'unknown-field' - xml_path = path_to_test_file('test_run_subcmd.xml') + name = "unknown-field" + xml_path = path_to_test_file("test_run_subcmd.xml") with tempdir() as output_dir: - args = ['run', - '-m', xml_path, - '-f', name, - '--no-default-model', - '--cluster-type=serial', - '--output-dir', output_dir] - print("opg ", ' '.join(args)) + args = [ + "run", + "-m", + xml_path, + "-f", + name, + "--no-default-model", + "--cluster-type=serial", + "--output-dir", + output_dir, + ] + print("opg ", " ".join(args)) with pytest.raises(CommandlineError, match="Fields not found in .*"): opgee_main.run(None, args) def test_field_or_analysis(opgee_main): - xml_path = path_to_test_file('test_run_subcmd.xml') + xml_path = path_to_test_file("test_run_subcmd.xml") with tempdir() as output_dir: - args = ['run', - '-m', xml_path, - '--output-dir', output_dir] - print("opg ", ' '.join(args)) + args = ["run", "-m", xml_path, "--output-dir", output_dir] + print("opg ", " ".join(args)) # CommandlineError("Must indicate one or more fields or analyses to run") with pytest.raises(CommandlineError, match="Must indicate one or more fields or analyses to run"): opgee_main.run(None, args) + def test_missing_model_file(opgee_main): with tempdir() as output_dir: - args = ["run", "--output-dir", output_dir, '--no-default-model', '-a', 'test'] + args = ["run", "--output-dir", output_dir, "--no-default-model", "-a", "test"] print("opg ", " ".join(args)) # CommandlineError("No model to run: the --model-file option was not used and --no-default-model was specified.") with pytest.raises(CommandlineError, match="No model to run.*"): opgee_main.run(None, args) + def test_packetization(opgee_main): import pandas as pd - xml_path = path_to_test_file('test-fields-10.xml') + xml_path = path_to_test_file("test-fields-10.xml") fields = 7 - batch_start = 2 # arbitrary start number for result batches + batch_start = 2 # arbitrary start number for result batches packet_size = 3 - cluster_type = 'serial' + cluster_type = "serial" with tempdir() as output_dir: - args = ['run', - '-m', xml_path, - '--output-dir', output_dir, - '-a', 'test-fields', - '--no-default-model', - '--cluster-type', cluster_type, - '--num-tasks=2', - '--batch-size=1', # save each packet in a separate CSV - f'--num-fields={fields}', - f'--packet-size={packet_size}', - f'--batch-start={batch_start}', - ] - print("opg ", ' '.join(args)) + args = [ + "run", + "-m", + xml_path, + "--output-dir", + output_dir, + "-a", + "test-fields", + "--no-default-model", + "--cluster-type", + cluster_type, + "--num-tasks=2", + "--batch-size=1", # save each packet in a separate CSV + f"--num-fields={fields}", + f"--packet-size={packet_size}", + f"--batch-start={batch_start}", + ] + print("opg ", " ".join(args)) opgee_main.run(None, args) @@ -135,22 +159,22 @@ def test_packetization(opgee_main): # Should find 3 result files; 2 with 3 results each, and one with 1 result. num_files = fields // packet_size + (1 if fields % packet_size else 0) assert len(d) == num_files - expected = (f"carbon_intensity_{n}.csv" for n in - range(batch_start, batch_start + num_files)) + expected = (f"carbon_intensity_{n}.csv" for n in range(batch_start, batch_start + num_files)) assert set(expected) == set(d.keys()) last_batch = batch_start + num_files - 1 - last_csv = f'carbon_intensity_{last_batch}.csv' + last_csv = f"carbon_intensity_{last_batch}.csv" last_df = d[last_csv] assert len(last_df.field.unique()) == 1 del d[last_csv] for name, df in d.items(): assert len(df.field.unique()) == packet_size + def test_run_test_model(opgee_main): - setParam('OPGEE.ClassPath', path_to_test_file('user_processes.py')) + setParam("OPGEE.ClassPath", path_to_test_file("user_processes.py")) - xml_path = path_to_test_file('test_model2.xml') + xml_path = path_to_test_file("test_model2.xml") cmd = f"run -f Field1 -m {xml_path} --no-default-model" print(f"Running 'opg {cmd}'") @@ -162,5 +186,5 @@ def test_run_test_model(opgee_main): print(f"ERROR: test_run_test_model: {e}") good = False - setParam('OPGEE.ClassPath', '') # avoid reloading user_processes.py + setParam("OPGEE.ClassPath", "") # avoid reloading user_processes.py assert good diff --git a/tests/test_smart_defaults.py b/tests/test_smart_defaults.py index da1e5c90..ab468155 100644 --- a/tests/test_smart_defaults.py +++ b/tests/test_smart_defaults.py @@ -29,20 +29,22 @@ """ + def model_for_country(country): xml = template.format(country=country) - model = load_model_from_str(xml, use_default_model=True) + model = load_model_from_str(xml, use_default_model=True) return model + def test_model_1(configure_logging_for_tests): - model = model_for_country('USA') - field = model.get_field('test') - T = field.attr('prod_water_inlet_temp') - assert T == ureg.Quantity(140, 'degF') + model = model_for_country("USA") + field = model.get_field("test") + T = field.attr("prod_water_inlet_temp") + assert T == ureg.Quantity(140, "degF") def test_model_2(configure_logging_for_tests): - model = model_for_country('Canada') - field = model.get_field('test') - T = field.attr('prod_water_inlet_temp') - assert T == ureg.Quantity(340, 'degF') + model = model_for_country("Canada") + field = model.get_field("test") + T = field.attr("prod_water_inlet_temp") + assert T == ureg.Quantity(340, "degF") diff --git a/tests/test_stream.py b/tests/test_stream.py index 69c59252..fe30c26f 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -21,7 +21,7 @@ def __init__(self, name, **kwargs): super().__init__(name, **kwargs) self._required_inputs = [ - self.valdict(r'.*gas.*', min=2, max=2), + self.valdict(r".*gas.*", min=2, max=2), ] self._required_outputs = [ "CO2", @@ -38,64 +38,66 @@ def run(self, analysis): @pytest.fixture(scope="module") def stream_model(configure_logging_for_tests): - return load_test_model('test_stream.xml') + return load_test_model("test_stream.xml") def test_carbon_number(): from opgee.stream import is_carbon_number + assert is_carbon_number("C2") and is_carbon_number("C200") assert not is_carbon_number("foo") def test_find_stream(stream_model): - analysis = stream_model.get_analysis('test') - field = analysis.get_field('test') + analysis = stream_model.get_analysis("test") + field = analysis.get_field("test") - name = 'stream1' + name = "stream1" s = field.find_stream(name) assert s.name == name - bad_name = 'unknown_stream' + bad_name = "unknown_stream" with pytest.raises(OpgeeException, match=f"Stream named '{bad_name}' was not found .*"): field.find_stream(bad_name) - proc3 = field.find_process('Proc3') + proc3 = field.find_process("Proc3") - stream = proc3.find_output_stream('CO2') - assert stream and 'CO2' in stream.contents + stream = proc3.find_output_stream("CO2") + assert stream and "CO2" in stream.contents - contents = 'hydrogen' + contents = "hydrogen" with pytest.raises(OpgeeException, match=f"Expected one output stream with '{contents}'.*"): proc3.find_output_stream(contents) - streams = proc3.find_output_streams('hydrogen', as_list=False) + streams = proc3.find_output_streams("hydrogen", as_list=False) assert streams and type(streams) == dict and len(streams) == 2 - streams = proc3.find_output_streams('hydrogen', as_list=True) + streams = proc3.find_output_streams("hydrogen", as_list=True) assert streams and type(streams) == list and len(streams) == 2 with pytest.raises(OpgeeException, match=".*both 'combine' and 'as_list' cannot be True"): - proc3.find_output_streams('hydrogen', as_list=True, combine=True) + proc3.find_output_streams("hydrogen", as_list=True, combine=True) - streams = proc3.find_input_streams('gas.*', as_list=False, regex=True) + streams = proc3.find_input_streams("gas.*", as_list=False, regex=True) assert streams and type(streams) == dict - streams = proc3.find_input_streams('gas.*', as_list=True, regex=True) + streams = proc3.find_input_streams("gas.*", as_list=True, regex=True) assert streams and type(streams) == list with pytest.raises(OpgeeException, match=f".*no input streams contain '{bad_name}'"): proc3.find_input_streams(bad_name, combine=False, as_list=False, raiseError=True) - bad_regex = r'foo.*' + bad_regex = r"foo.*" with pytest.raises(OpgeeException, match=f".*no input streams contain '{bad_regex}'"): proc3.find_input_streams(bad_regex, combine=False, as_list=False, regex=True, raiseError=True) + def test_initialization(stream_model): - analysis = stream_model.get_analysis('test') - field = analysis.get_field('test') + analysis = stream_model.get_analysis("test") + field = analysis.get_field("test") - stream1 = field.find_stream('initialized') + stream1 = field.find_stream("initialized") assert stream1.is_initialized() and not stream1.has_zero_flow() stream2 = field.find_stream("Proc3-to-Proc4") @@ -107,27 +109,29 @@ def test_initialization(stream_model): stream2.set_gas_flow_rate("CO2", 10) assert stream2.is_initialized() and not stream2.has_zero_flow() - assert stream2.solid_flow_rate('PC').m == 0 + assert stream2.solid_flow_rate("PC").m == 0 rates = stream1.non_zero_flow_rates() - assert len(rates) == 1 and rates.index[0] == 'oil' - oil = rates.loc['oil'] + assert len(rates) == 1 and rates.index[0] == "oil" + oil = rates.loc["oil"] assert oil.solid.m == 0.0 and oil.gas.m == 0.0 and oil.liquid.m == 100.0 def test_combustion_stream(stream_model): - analysis = stream_model.get_analysis('test') - field = analysis.get_field('test') + analysis = stream_model.get_analysis("test") + field = analysis.get_field("test") stream1 = field.find_stream("combustion stream") CO2_stream = field.find_stream("combusted final stream") CO2_stream.add_combustion_CO2_from(stream1) assert CO2_stream.gas_flow_rate("CO2") == ureg.Quantity(pytest.approx(8.950127703143934), "t/d") + def test_stream_utils(stream_model): from opgee.core import TemperaturePressure from opgee.stream import Stream + tp = None - s = Stream('stream1', tp) + s = Stream("stream1", tp) assert s.tp == None tp = TemperaturePressure(100, 200) @@ -136,4 +140,3 @@ def test_stream_utils(stream_model): # check that T & P are unchanged s.tp.T.m == 100.0 s.tp.P.m == 200.0 - diff --git a/tests/test_table_manager.py b/tests/test_table_manager.py index 7ba4b7b9..434e6223 100644 --- a/tests/test_table_manager.py +++ b/tests/test_table_manager.py @@ -4,20 +4,23 @@ from opgee.table_manager import TableManager from .utils_for_tests import path_to_test_file + def test_updates(test_model): df = test_model.upstream_CI - assert df.loc[EN_NGL, 'EF'].m == 1234.5 and df.loc[EN_NATURAL_GAS, 'EF'].m == 12345.67 + assert df.loc[EN_NGL, "EF"].m == 1234.5 and df.loc[EN_NATURAL_GAS, "EF"].m == 12345.67 + def test_add_table(): - table_name = 'test_table' - csv_path = path_to_test_file(f'{table_name}.csv') + table_name = "test_table" + csv_path = path_to_test_file(f"{table_name}.csv") mgr = TableManager() mgr.add_table(csv_path, index_col=0, skiprows=1) df = mgr.get_table(table_name) - assert (df.shape == (3, 2) and df.loc['foo', 'value2'] == 20.6) + assert df.shape == (3, 2) and df.loc["foo", "value2"] == 20.6 + def test_bad_table_name(): mgr = TableManager() - name = 'non-existent-table' + name = "non-existent-table" with pytest.raises(OpgeeException, match=f"Unknown table '{name}'"): mgr.get_table(name) diff --git a/tests/test_thermofunction.py b/tests/test_thermofunction.py index 01f76332..173130ed 100644 --- a/tests/test_thermofunction.py +++ b/tests/test_thermofunction.py @@ -194,8 +194,7 @@ def test_component_molar_fraction_C1(gas_instance, stream): def test_component_mass_fraction(gas_instance, stream): - molar_fracs = pd.Series([0.004, 0.9666, 0.02, 0.01], - index=["N2", "C1", "C2", "C3"], dtype="pint[mol/mol]") + molar_fracs = pd.Series([0.004, 0.9666, 0.02, 0.01], index=["N2", "C1", "C2", "C3"], dtype="pint[mol/mol]") mass_fracs = gas_instance.component_mass_fractions(molar_fracs) assert mass_fracs["C1"] == ureg.Quantity(pytest.approx(0.9307131413113588)) @@ -273,8 +272,7 @@ def test_molar_weight(gas_instance, stream): def test_molar_weight_from_molar_fracs(gas_instance, stream): - molar_fracs = pd.Series([0.004, 0.9666, 0.02, 0.01], - index=["N2", "C1", "C2", "C3"], dtype="pint[mol/mol]") + molar_fracs = pd.Series([0.004, 0.9666, 0.02, 0.01], index=["N2", "C1", "C2", "C3"], dtype="pint[mol/mol]") mol_weight = gas_instance.molar_weight_from_molar_fracs(molar_fracs) assert mol_weight == ureg.Quantity(pytest.approx(16.6610324), "g/mol") @@ -310,15 +308,13 @@ def test_gas_mass_energy_density(gas_instance, stream): def test_gas_mass_energy_density_from_molar_fracs(gas_instance, stream): - molar_fracs = pd.Series([0.004, 0.9666, 0.02, 0.01], - index=["N2", "C1", "C2", "C3"], dtype="pint[mol/mol]") + molar_fracs = pd.Series([0.004, 0.9666, 0.02, 0.01], index=["N2", "C1", "C2", "C3"], dtype="pint[mol/mol]") mass_energy_density = gas_instance.mass_energy_density_from_molar_fracs(molar_fracs) assert mass_energy_density == ureg.Quantity(pytest.approx(49.7703477), "MJ/kg") def test_combustion_enthalpy(gas_instance, stream): - molar_fracs = pd.Series([9.2878, 2.4624, 0.0035, 0.2399], - index=["N2", "O2", "CO2", "H2O"], dtype="pint[mol/mol]") + molar_fracs = pd.Series([9.2878, 2.4624, 0.0035, 0.2399], index=["N2", "O2", "CO2", "H2O"], dtype="pint[mol/mol]") temperature = ureg.Quantity(80.33, "degF") enthalpy = gas_instance.combustion_enthalpy(molar_fracs, temperature, PHASE_GAS) assert enthalpy["H2O"] == ureg.Quantity(pytest.approx(2162.8143928135105), "joule/mole") @@ -374,7 +370,7 @@ def test_water_saturated_temperature(water_instance): def test_water_enthalpy_PT(water_instance): press = ureg.Quantity(13.7895, "bar") temp = ureg.Quantity(60.0, "degC") - mass_rate = ureg.Quantity(3.94E7, "kg/day") + mass_rate = ureg.Quantity(3.94e7, "kg/day") enthalpy = water_instance.enthalpy_PT(press, temp, mass_rate) assert enthalpy == ureg.Quantity(pytest.approx(9940445.92), "MJ/day") @@ -382,16 +378,16 @@ def test_water_enthalpy_PT(water_instance): def test_steam_enthalpy(water_instance): press = ureg.Quantity(77.359177, "bar") steam_quality = ureg.Quantity(0.7, "frac") - mass_rate = ureg.Quantity(5.52E7, "kg/day") + mass_rate = ureg.Quantity(5.52e7, "kg/day") enthalpy = water_instance.steam_enthalpy(press, steam_quality, mass_rate) - assert enthalpy == ureg.Quantity(pytest.approx(1.28341315e+08), "MJ/day") + assert enthalpy == ureg.Quantity(pytest.approx(1.28341315e08), "MJ/day") def test_check_balance(test_model): from opgee.error import BalanceError field = test_model.get_field("test") - proc = field.find_process('SteamGeneration') + proc = field.find_process("SteamGeneration") input = ureg.Quantity(100.0, "tonne/day") output1 = ureg.Quantity(100.0001, "tonne/day") diff --git a/tests/test_utils.py b/tests/test_utils.py index 31d310da..c519dda0 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,14 +3,13 @@ from opgee.units import ureg from opgee.error import OpgeeException -from opgee.utils import (getBooleanXML, coercible, mkdirs, loadModuleFromPath, - removeTree, parseTrialString) +from opgee.utils import getBooleanXML, coercible, mkdirs, loadModuleFromPath, removeTree, parseTrialString from .utils_for_tests import tmpdir + @pytest.mark.parametrize( - "value, expected", [("true", True), ("yes", True), ("1", True), - ("false", False), ("no", False), ("0", False), ("none", False) - ] + "value, expected", + [("true", True), ("yes", True), ("1", True), ("false", False), ("no", False), ("0", False), ("none", False)], ) def test_boolean_xml(value, expected): assert getBooleanXML(value) == expected @@ -21,10 +20,14 @@ def test_boolean_xml_failure(): with pytest.raises(OpgeeException, match=f"Can't convert '{value}' to boolean.*"): getBooleanXML(value) + @pytest.mark.parametrize( - "value, pytype, result", [(10, "float", 10.0), - ("11.0", "int", 11), - (ureg.Quantity(20.9, "tonnes"), "ignored", ureg.Quantity(20.9, "tonnes"))] + "value, pytype, result", + [ + (10, "float", 10.0), + ("11.0", "int", 11), + (ureg.Quantity(20.9, "tonnes"), "ignored", ureg.Quantity(20.9, "tonnes")), + ], ) def test_coercible(value, pytype, result): assert coercible(value, pytype) == result @@ -40,11 +43,12 @@ def test_coercible_failure(): with pytest.raises(OpgeeException, match=".*is not coercible.*"): coercible("foobar", float) + def test_mkdirs(): with pytest.raises(TypeError): mkdirs(12345) - d = tmpdir('foo', 'bar', 'baz') + d = tmpdir("foo", "bar", "baz") mkdirs(d) assert os.path.isdir(d) @@ -57,9 +61,10 @@ def test_mkdirs(): removeTree(d, ignore_errors=False) from opgee.config import IsWindows + if not IsWindows: with pytest.raises(OSError): - mkdirs('/not/a/real/path') + mkdirs("/not/a/real/path") def test_load_module_failure(): @@ -68,10 +73,11 @@ def test_load_module_failure(): loadModuleFromPath("/not/a/rea/path.py", raiseError=False) + def test_trial_string(): - s = '1, 41, 3, 7-11' + s = "1, 41, 3, 7-11" nums = parseTrialString(s) assert set(nums) == {1, 3, 7, 8, 9, 10, 11, 41} with pytest.raises(ValueError): - parseTrialString(s + 'junk') + parseTrialString(s + "junk") diff --git a/tests/test_xml_file.py b/tests/test_xml_file.py index 196ac5f2..aef514a8 100644 --- a/tests/test_xml_file.py +++ b/tests/test_xml_file.py @@ -4,8 +4,9 @@ from opgee.error import XmlFormatError from .utils_for_tests import path_to_test_file + def test_read_xml(): - xml_path = path_to_test_file('test_model.xml') + xml_path = path_to_test_file("test_model.xml") xml_file = XMLFile(xml_path) assert xml_file.getFilename() == xml_path @@ -15,20 +16,23 @@ def test_read_xml(): tree = xml_file.getTree() assert isinstance(tree, etree._ElementTree) + def test_bad_filename(): - xml_file = 'nonexistent-model.xml' + xml_file = "nonexistent-model.xml" xml_path = path_to_test_file(xml_file) with pytest.raises(XmlFormatError, match=f"Can't read from XML file '{xml_path}': .*"): XMLFile(xml_path) + def test_bad_model(): - xml_file = 'bad_model.xml' + xml_file = "bad_model.xml" xml_path = path_to_test_file(xml_file) - schema_path = 'etc/opgee.xsd' + schema_path = "etc/opgee.xsd" with pytest.raises(XmlFormatError, match=f"Validation of '{xml_path}'\n.*using schema '{schema_path}' failed:\n.*"): XMLFile(xml_path, schemaPath=schema_path) + # Validation of '.../OPGEEv4/tests/files/bad_model.xml' # using schema 'etc/opgee.xsd' failed: # Element 'Field': The attribute 'name' is required but missing., line 24 diff --git a/tests/utils_for_tests.py b/tests/utils_for_tests.py index ef22275b..634dfa0a 100644 --- a/tests/utils_for_tests.py +++ b/tests/utils_for_tests.py @@ -4,6 +4,7 @@ from opgee.model_file import ModelFile from opgee.process import Process + @contextmanager def tempdir(): import tempfile @@ -15,14 +16,17 @@ def tempdir(): finally: shutil.rmtree(d) + class ProcA(Process): def run(self, analysis): pass + class ProcB(Process): def run(self, analysis): pass + class Before(Process): def run(self, analysis): pass @@ -30,6 +34,7 @@ def run(self, analysis): def impute(self): pass + # Required to load opgee.xml and some test XML files class After(Process): def run(self, analysis): @@ -37,42 +42,49 @@ def run(self, analysis): def path_to_test_file(filename): - path = pathjoin(__file__, '..', f'files/{filename}', abspath=True) + path = pathjoin(__file__, "..", f"files/{filename}", abspath=True) return path + def load_test_model(xml_file, use_default_model=False, stream_components=None, class_path=None): - old_class_path = getParam('OPGEE.ClassPath') - old_stream_comps = getParam('OPGEE.StreamComponents') - project = getParam('OPGEE.DefaultProject') + old_class_path = getParam("OPGEE.ClassPath") + old_stream_comps = getParam("OPGEE.StreamComponents") + project = getParam("OPGEE.DefaultProject") if class_path: - setParam('OPGEE.ClassPath', path_to_test_file('user_processes.py'), section=project) + setParam("OPGEE.ClassPath", path_to_test_file("user_processes.py"), section=project) if stream_components: - setParam('OPGEE.StreamComponents', stream_components, section=project) + setParam("OPGEE.StreamComponents", stream_components, section=project) xml_path = path_to_test_file(xml_file) - mf = ModelFile(xml_path, use_default_model=use_default_model, - add_stream_components=bool(stream_components), - use_class_path=bool(class_path)) + mf = ModelFile( + xml_path, + use_default_model=use_default_model, + add_stream_components=bool(stream_components), + use_class_path=bool(class_path), + ) if class_path: - setParam('OPGEE.ClassPath', old_class_path, section=project) + setParam("OPGEE.ClassPath", old_class_path, section=project) if stream_components: - setParam('OPGEE.StreamComponents', old_stream_comps, section=project) + setParam("OPGEE.StreamComponents", old_stream_comps, section=project) return mf.model + def load_model_from_str(xml_str, use_default_model=False): mf = ModelFile.from_xml_string(xml_str, use_default_model=use_default_model) return mf.model + def tmpdir(*args): - d = pathjoin(getParam('OPGEE.TempDir'), *args) + d = pathjoin(getParam("OPGEE.TempDir"), *args) return d + @contextmanager def tempdir(): import tempfile @@ -84,7 +96,7 @@ def tempdir(): finally: shutil.rmtree(d) + def load_config_from_string(text): stream = StringIO(text) readConfigFile(stream) - From 8adcbd197e7f7a1f70785395f99c603d428dfe89 Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Tue, 2 Sep 2025 16:26:40 -0600 Subject: [PATCH 3/6] style(stream): reformat code for improved readability and consistency - add pint_pandas import with noqa - wrap long lines and expressions for clarity - add trailing commas and line breaks in multi-line statements - improve exception message formatting - align comments and code spacing consistently --- opgee/stream.py | 51 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/opgee/stream.py b/opgee/stream.py index c96bf0e4..3a06f5fc 100644 --- a/opgee/stream.py +++ b/opgee/stream.py @@ -11,6 +11,7 @@ import pandas as pd import pint +import pint_pandas # noqa: F401 from .units import ureg, magnitude from .attributes import AttributeMixin @@ -46,7 +47,9 @@ def molecule_to_carbon(molecule): m = _hydrocarbon_prog.match(molecule) if m is None: - raise OpgeeException(f"Expected hydrocarbon molecule name like CxHy, got {molecule}") + raise OpgeeException( + f"Expected hydrocarbon molecule name like CxHy, got {molecule}" + ) c_name = m.group(1) return c_name @@ -97,7 +100,9 @@ class Stream(AttributeMixin, XmlInstantiable): # Verify that the pubchem-cid index includes 1..N where N is the max_carbon number if set(_carbon_number_dict.keys()) != set(idx): - raise ModelValidationError(f"{table_name} must contain carbon numbers 1..{max_carbon_number}.") + raise ModelValidationError( + f"{table_name} must contain carbon numbers 1..{max_carbon_number}." + ) # All hydrocarbon gases other than methane (C1) are considered VOCs. VOCs = _hydrocarbons[1:] @@ -158,7 +163,9 @@ def __init__( XmlInstantiable.__init__(self, name, parent=parent) # TBD: rename this self.comp_matrix for clarity - self.components = self.create_component_matrix() if comp_matrix is None else comp_matrix + self.components = ( + self.create_component_matrix() if comp_matrix is None else comp_matrix + ) self.tp = copy(tp) @@ -192,7 +199,9 @@ def to_dataframe(self): :return: (pd.DataFrame) data series """ - df = self.components.reset_index().melt(id_vars=["index"], var_name="phase", value_name="value") + df = self.components.reset_index().melt( + id_vars=["index"], var_name="phase", value_name="value" + ) df.rename(columns={"index": "component"}, inplace=True) df["units"] = "metric_ton / day" @@ -210,7 +219,9 @@ def to_dataframe(self): items = [("T", self.tp.T), ("P", self.tp.P), ("API", self.API)] tuples = [ - (no_phase, name, value.m, str(value.units)) for name, value in items if value is not None and value.m != 0 + (no_phase, name, value.m, str(value.units)) + for name, value in items + if value is not None and value.m != 0 ] extras = pd.DataFrame(data=tuples, columns=columns) @@ -239,7 +250,9 @@ def reset(self): :return: none """ self.initialized = has_xml_data = self.xml_data is not None - self.components = self.xml_data if has_xml_data else self.create_component_matrix() + self.components = ( + self.xml_data if has_xml_data else self.create_component_matrix() + ) self.tp.copy_from(self.initial_tp) @@ -269,7 +282,9 @@ def extend_components(cls, names): # ensure no duplicate names bad = name_set.intersection(set(cls._extensions)) if bad: - raise OpgeeException(f"extend_components: these proposed extensions are already defined: {bad}") + raise OpgeeException( + f"extend_components: these proposed extensions are already defined: {bad}" + ) _logger.info(f"Extended stream components to include {names}") @@ -466,9 +481,9 @@ def set_rates_from_series(self, series, phase, upper_bound_stream=None): self.initialized = True self.components.loc[series.index, phase] = series.clip(lower=0) if upper_bound_stream is not None: - self.components.loc[series.index, phase] = self.components.loc[series.index, phase].clip( - upper=upper_bound_stream.components.loc[series.index, phase] - ) + self.components.loc[series.index, phase] = self.components.loc[ + series.index, phase + ].clip(upper=upper_bound_stream.components.loc[series.index, phase]) def multiply_factor_from_series(self, series, phase): """ @@ -479,7 +494,9 @@ def multiply_factor_from_series(self, series, phase): :return: """ self.initialized = True - self.components.loc[series.index, phase] = series * self.components.loc[series.index, phase] + self.components.loc[series.index, phase] = ( + series * self.components.loc[series.index, phase] + ) def set_tp(self, tp): """ @@ -671,7 +688,9 @@ def from_xml(cls, elt, parent=None): attr_dict = cls.instantiate_attrs(elt) expected = {"temperature", "pressure", "API"} if set(attr_dict.keys()) != expected: - raise OpgeeException(f"Stream {name}: expected attributes {sorted(expected)}") + raise OpgeeException( + f"Stream {name}: expected attributes {sorted(expected)}" + ) temp = attr_dict["temperature"].value pres = attr_dict["pressure"].value @@ -692,14 +711,18 @@ def from_xml(cls, elt, parent=None): a = comp_elt.attrib comp_name = elt_name(comp_elt) rate = coercible(comp_elt.text, float) - phase = a["phase"] # required by XML schema to be one of the 3 legal values + phase = a[ + "phase" + ] # required by XML schema to be one of the 3 legal values # convert hydrocarbon molecule name to carbon number format if is_hydrocarbon(comp_name): comp_name = molecule_to_carbon(comp_name) if comp_name not in matrix.index: - raise OpgeeException(f"Unrecognized stream component name '{comp_name}'.") + raise OpgeeException( + f"Unrecognized stream component name '{comp_name}'." + ) matrix.loc[comp_name, phase] = rate From ffb94c4b33344c07444a7f7cdbd2eaaeb678eceb Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Tue, 2 Sep 2025 16:27:57 -0600 Subject: [PATCH 4/6] style(stream): reformat code for consistency and remove unnecessary line breaks --- opgee/stream.py | 50 ++++++++++++++----------------------------------- 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/opgee/stream.py b/opgee/stream.py index 3a06f5fc..4a343684 100644 --- a/opgee/stream.py +++ b/opgee/stream.py @@ -47,9 +47,7 @@ def molecule_to_carbon(molecule): m = _hydrocarbon_prog.match(molecule) if m is None: - raise OpgeeException( - f"Expected hydrocarbon molecule name like CxHy, got {molecule}" - ) + raise OpgeeException(f"Expected hydrocarbon molecule name like CxHy, got {molecule}") c_name = m.group(1) return c_name @@ -100,9 +98,7 @@ class Stream(AttributeMixin, XmlInstantiable): # Verify that the pubchem-cid index includes 1..N where N is the max_carbon number if set(_carbon_number_dict.keys()) != set(idx): - raise ModelValidationError( - f"{table_name} must contain carbon numbers 1..{max_carbon_number}." - ) + raise ModelValidationError(f"{table_name} must contain carbon numbers 1..{max_carbon_number}.") # All hydrocarbon gases other than methane (C1) are considered VOCs. VOCs = _hydrocarbons[1:] @@ -163,9 +159,7 @@ def __init__( XmlInstantiable.__init__(self, name, parent=parent) # TBD: rename this self.comp_matrix for clarity - self.components = ( - self.create_component_matrix() if comp_matrix is None else comp_matrix - ) + self.components = self.create_component_matrix() if comp_matrix is None else comp_matrix self.tp = copy(tp) @@ -199,9 +193,7 @@ def to_dataframe(self): :return: (pd.DataFrame) data series """ - df = self.components.reset_index().melt( - id_vars=["index"], var_name="phase", value_name="value" - ) + df = self.components.reset_index().melt(id_vars=["index"], var_name="phase", value_name="value") df.rename(columns={"index": "component"}, inplace=True) df["units"] = "metric_ton / day" @@ -219,9 +211,7 @@ def to_dataframe(self): items = [("T", self.tp.T), ("P", self.tp.P), ("API", self.API)] tuples = [ - (no_phase, name, value.m, str(value.units)) - for name, value in items - if value is not None and value.m != 0 + (no_phase, name, value.m, str(value.units)) for name, value in items if value is not None and value.m != 0 ] extras = pd.DataFrame(data=tuples, columns=columns) @@ -250,9 +240,7 @@ def reset(self): :return: none """ self.initialized = has_xml_data = self.xml_data is not None - self.components = ( - self.xml_data if has_xml_data else self.create_component_matrix() - ) + self.components = self.xml_data if has_xml_data else self.create_component_matrix() self.tp.copy_from(self.initial_tp) @@ -282,9 +270,7 @@ def extend_components(cls, names): # ensure no duplicate names bad = name_set.intersection(set(cls._extensions)) if bad: - raise OpgeeException( - f"extend_components: these proposed extensions are already defined: {bad}" - ) + raise OpgeeException(f"extend_components: these proposed extensions are already defined: {bad}") _logger.info(f"Extended stream components to include {names}") @@ -481,9 +467,9 @@ def set_rates_from_series(self, series, phase, upper_bound_stream=None): self.initialized = True self.components.loc[series.index, phase] = series.clip(lower=0) if upper_bound_stream is not None: - self.components.loc[series.index, phase] = self.components.loc[ - series.index, phase - ].clip(upper=upper_bound_stream.components.loc[series.index, phase]) + self.components.loc[series.index, phase] = self.components.loc[series.index, phase].clip( + upper=upper_bound_stream.components.loc[series.index, phase] + ) def multiply_factor_from_series(self, series, phase): """ @@ -494,9 +480,7 @@ def multiply_factor_from_series(self, series, phase): :return: """ self.initialized = True - self.components.loc[series.index, phase] = ( - series * self.components.loc[series.index, phase] - ) + self.components.loc[series.index, phase] = series * self.components.loc[series.index, phase] def set_tp(self, tp): """ @@ -688,9 +672,7 @@ def from_xml(cls, elt, parent=None): attr_dict = cls.instantiate_attrs(elt) expected = {"temperature", "pressure", "API"} if set(attr_dict.keys()) != expected: - raise OpgeeException( - f"Stream {name}: expected attributes {sorted(expected)}" - ) + raise OpgeeException(f"Stream {name}: expected attributes {sorted(expected)}") temp = attr_dict["temperature"].value pres = attr_dict["pressure"].value @@ -711,18 +693,14 @@ def from_xml(cls, elt, parent=None): a = comp_elt.attrib comp_name = elt_name(comp_elt) rate = coercible(comp_elt.text, float) - phase = a[ - "phase" - ] # required by XML schema to be one of the 3 legal values + phase = a["phase"] # required by XML schema to be one of the 3 legal values # convert hydrocarbon molecule name to carbon number format if is_hydrocarbon(comp_name): comp_name = molecule_to_carbon(comp_name) if comp_name not in matrix.index: - raise OpgeeException( - f"Unrecognized stream component name '{comp_name}'." - ) + raise OpgeeException(f"Unrecognized stream component name '{comp_name}'.") matrix.loc[comp_name, phase] = rate From 4c74867d8e2aeb807b92c556a1aac172e5d1dc4b Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Tue, 2 Sep 2025 16:36:36 -0600 Subject: [PATCH 5/6] style: more linting and formatting --- opgee/XMLFile.py | 2 +- opgee/analysis.py | 5 +- opgee/attributes.py | 6 +- opgee/audit.py | 12 +- opgee/bfs.py | 17 +- opgee/bin/combine_csvs.py | 1 + opgee/bin/venting_fugitive_dummy_csv.py | 3 +- opgee/built_ins/__init__.py | 6 +- opgee/built_ins/collect_plugin.py | 2 +- opgee/built_ins/compare_excel.py | 3 +- opgee/built_ins/compare_plugin.py | 1 + opgee/built_ins/config_plugin.py | 5 +- opgee/built_ins/csv2xml_plugin.py | 4 +- opgee/built_ins/gensim_plugin.py | 2 +- opgee/built_ins/graph_plugin.py | 4 +- opgee/built_ins/merge_plugin.py | 3 +- opgee/built_ins/run_plugin.py | 9 +- opgee/built_ins/update_plugin.py | 4 +- opgee/combine_streams.py | 10 +- opgee/config.py | 25 +-- opgee/core.py | 15 +- opgee/emissions.py | 2 +- opgee/energy.py | 2 +- opgee/field.py | 57 +++--- opgee/graph.py | 2 +- opgee/gui/app.py | 164 +++++++++--------- opgee/gui/process_pane.py | 16 +- opgee/gui/results_pane.py | 36 ++-- opgee/gui/settings_pane.py | 12 +- opgee/gui/testapp.py | 57 +++--- opgee/gui/widgets.py | 2 +- opgee/import_export.py | 2 +- opgee/log.py | 4 +- opgee/manager.py | 27 ++- opgee/mcs/LHS.py | 2 +- opgee/mcs/distributed_mcs_dask.py | 11 +- opgee/mcs/distro.py | 10 +- opgee/mcs/parameter_list.py | 14 +- opgee/mcs/simulation.py | 10 +- opgee/model.py | 4 +- opgee/model_file.py | 4 +- opgee/post_processor.py | 5 +- opgee/process.py | 49 +++--- opgee/processes/CO2_reinjection_compressor.py | 5 +- opgee/processes/LNG_regasification.py | 2 +- opgee/processes/__init__.py | 14 +- opgee/processes/acid_gas_removal.py | 12 +- opgee/processes/bitumen_mining.py | 9 +- opgee/processes/compressor.py | 12 +- opgee/processes/crude_oil_dewatering.py | 5 +- opgee/processes/crude_oil_stabilization.py | 9 +- opgee/processes/crude_oil_storage.py | 8 +- opgee/processes/demethanizer.py | 13 +- opgee/processes/downhole_pump.py | 8 +- opgee/processes/drilling.py | 4 +- opgee/processes/exploration.py | 2 +- opgee/processes/gas_dehydration.py | 11 +- opgee/processes/gas_gathering.py | 3 +- opgee/processes/gas_lifting_compressor.py | 2 +- opgee/processes/gas_partition.py | 11 +- opgee/processes/gas_reinjection_compressor.py | 2 +- opgee/processes/heavy_oil_upgrading.py | 7 +- opgee/processes/post_storage_compressor.py | 4 +- opgee/processes/pre_membrane_chiller.py | 2 +- opgee/processes/pre_membrane_compressor.py | 2 +- opgee/processes/reservoir_well_interface.py | 4 +- opgee/processes/ryan_holmes.py | 4 +- opgee/processes/separation.py | 9 +- opgee/processes/shared.py | 7 +- opgee/processes/sour_gas_compressor.py | 2 +- opgee/processes/steam_generation.py | 5 +- opgee/processes/steam_generator.py | 30 ++-- opgee/processes/transmission_compressor.py | 4 +- opgee/processes/transport_energy.py | 6 +- opgee/processes/venting.py | 4 +- opgee/processes/water_injection.py | 2 +- opgee/processes/water_treatment.py | 19 +- opgee/stream.py | 16 +- opgee/subcommand.py | 2 +- opgee/table_manager.py | 5 +- opgee/table_update.py | 2 +- opgee/thermodynamics.py | 45 ++--- opgee/tool.py | 8 +- opgee/units.py | 7 +- opgee/utils.py | 3 +- opgee/xml_utils.py | 6 +- tests/BROKEN_test_smart_defaults.py | 3 +- tests/conftest.py | 5 +- tests/test_attr_constraints.py | 2 + tests/test_attributes.py | 7 +- tests/test_audit.py | 7 +- tests/test_boundary.py | 2 + tests/test_boundary_procs.py | 2 + tests/test_coeff.py | 1 + tests/test_comparison.py | 3 +- tests/test_config.py | 15 +- tests/test_core.py | 7 +- tests/test_csv2xml.py | 4 +- tests/test_distributed_mcs.py | 1 + tests/test_emissions.py | 7 +- tests/test_energy.py | 7 +- tests/test_field.py | 7 +- tests/test_import_export.py | 2 +- tests/test_impute.py | 4 +- tests/test_intermediate_boundary.py | 5 +- tests/test_mcs.py | 7 +- tests/test_merge_xml.py | 11 +- tests/test_model.py | 2 + tests/test_molecule_names.py | 3 +- tests/test_packet.py | 1 + tests/test_post_proc_plugin.py | 5 +- tests/test_process_groups.py | 2 + tests/test_process_loop.py | 5 +- tests/test_processes.py | 11 +- tests/test_run_subcmd.py | 8 +- tests/test_smart_defaults.py | 5 +- tests/test_stream.py | 1 + tests/test_table_manager.py | 2 + tests/test_thermofunction.py | 3 +- tests/test_utils.py | 6 +- tests/test_xml_file.py | 4 +- tests/utils_for_tests.py | 7 +- 122 files changed, 570 insertions(+), 572 deletions(-) diff --git a/opgee/XMLFile.py b/opgee/XMLFile.py index ba0c0721..c1cef00a 100644 --- a/opgee/XMLFile.py +++ b/opgee/XMLFile.py @@ -17,7 +17,7 @@ _logger = getLogger(__name__) -class XMLFile(object): +class XMLFile: parsed_schemas = {} # cache parsed schemas to avoid re-reading and parsing opgee.xsd def __init__( diff --git a/opgee/analysis.py b/opgee/analysis.py index 55dff4f5..870f64a9 100644 --- a/opgee/analysis.py +++ b/opgee/analysis.py @@ -8,7 +8,7 @@ from .config import getParamAsList from .container import Container -from .core import elt_name, OpgeeObject +from .core import OpgeeObject, elt_name from .emissions import Emissions from .error import OpgeeException from .field import Field @@ -124,8 +124,7 @@ def field_names(self, enabled_only=True): if enabled_only: names = [f.name for f in self.fields()] return names - else: - return self._field_names + return self._field_names def first_field(self): return self.get_field(self._field_names[0]) diff --git a/opgee/attributes.py b/opgee/attributes.py index 31725110..9f8af6d2 100644 --- a/opgee/attributes.py +++ b/opgee/attributes.py @@ -10,10 +10,10 @@ import pandas as pd -from .units import ureg, validate_unit, magnitude -from .core import OpgeeObject, XmlInstantiable, A, instantiate_subelts, elt_name +from .core import A, OpgeeObject, XmlInstantiable, elt_name, instantiate_subelts from .error import AttributeError, ModelValidationError from .log import getLogger +from .units import magnitude, ureg, validate_unit from .utils import coercible _logger = getLogger(__name__) @@ -371,7 +371,7 @@ def is_a_process(cls): values = [attr_dict[attr_name].value for attr_name in attr_names] if sum(values) not in (0, 1): - items = list(zip(attr_names, values)) + items = list(zip(attr_names, values, strict=False)) raise ModelValidationError(f"Exclusive attribute group '{group}' has multiple items selected: {items}") # Check synchronized groups diff --git a/opgee/audit.py b/opgee/audit.py index eabbadf6..80b43099 100644 --- a/opgee/audit.py +++ b/opgee/audit.py @@ -1,7 +1,7 @@ """Provides functionality for auditing the source of field attribute values.""" from enum import Flag, auto -from typing import Literal, TypeGuard, TypedDict +from typing import Literal, TypedDict, TypeGuard from lxml import etree from pint import Quantity @@ -56,8 +56,7 @@ def audit_required(audit_level: str | None): if _audit_level == "none": return False - else: - return True + return True def _is_audit_level_str(level: str) -> TypeGuard[AuditLevelStr]: @@ -70,12 +69,11 @@ def _translate_audit_level( _level = str(level).strip().lower() if _level == "all": return AuditFlag.ALL - elif _level == "field": + if _level == "field": return AuditFlag.FIELD - elif _level == "processes": + if _level == "processes": return AuditFlag.PROCESSES - else: - return AuditFlag.NONE + return AuditFlag.NONE def _generate_field_audit_report(field: Field, original_field_element: etree._Element) -> list[AuditRow]: diff --git a/opgee/bfs.py b/opgee/bfs.py index 3686c357..20fcf9dd 100644 --- a/opgee/bfs.py +++ b/opgee/bfs.py @@ -6,6 +6,7 @@ # See LICENSE.txt for license details. # from collections import deque + from .error import OpgeeException @@ -19,10 +20,10 @@ def is_cyclic_until(successor, visited, rec_stack, successors): rec_stack (dict): A dictionary to keep track of nodes in the recursion stack. successors (list): A list of successors to check for cycles. - Returns: + Returns + ------- bool: True if a cycle is found, False otherwise. """ - visited[successor] = True rec_stack[successor] = True @@ -46,7 +47,8 @@ def is_cyclic(successors): Args: successors (list): A list of successors to check for cycles. - Returns: + Returns + ------- bool: True if a cycle is found, False otherwise. """ visited = {} @@ -93,7 +95,8 @@ def get_successor_pairs(successors): Args: successors (list): A list of successors. - Returns: + Returns + ------- list: A list of tuples, where each tuple contains a successor node and its maximum distance from the starting node. """ successors_dist_dict = {} @@ -116,7 +119,8 @@ def bfs(start_node, unvisited, ordered_cycle): unvisited: The notes yet to be visited (N.B. caller's list is modified) ordered_cycle: the BFS ordered nodes (N.B. caller's list is modified) - Returns: + Returns + ------- none """ deck = deque([start_node]) @@ -132,8 +136,7 @@ def bfs(start_node, unvisited, ordered_cycle): if is_cyclic(successors): raise OpgeeException(f"Cycle detected in process {current_node} with successors {successors}") - else: - successors_pairs = get_successor_pairs(successors) + successors_pairs = get_successor_pairs(successors) successors_pairs.sort(key=lambda x: x[1]) for successor in successors_pairs: deck.append(successor[0]) diff --git a/opgee/bin/combine_csvs.py b/opgee/bin/combine_csvs.py index 79345d62..20da5cab 100755 --- a/opgee/bin/combine_csvs.py +++ b/opgee/bin/combine_csvs.py @@ -6,6 +6,7 @@ # 28-OCT-2021 # import argparse + import pandas as pd DEFAULT_OUTPUT = "combined.csv" diff --git a/opgee/bin/venting_fugitive_dummy_csv.py b/opgee/bin/venting_fugitive_dummy_csv.py index 463cabf6..087cc645 100755 --- a/opgee/bin/venting_fugitive_dummy_csv.py +++ b/opgee/bin/venting_fugitive_dummy_csv.py @@ -1,6 +1,7 @@ #!/usr/bin/env python -import pandas as pd import numpy as np +import pandas as pd + from opgee.process import Process, _subclass_dict N = 1000 diff --git a/opgee/built_ins/__init__.py b/opgee/built_ins/__init__.py index da959dce..ab0a6442 100644 --- a/opgee/built_ins/__init__.py +++ b/opgee/built_ins/__init__.py @@ -1,12 +1,12 @@ from .collect_plugin import CollectCommand from .compare_plugin import CompareCommand from .config_plugin import ConfigCommand +from .csv2xml_plugin import Csv2XmlCommand +from .gensim_plugin import GensimCommand from .graph_plugin import GraphCommand from .gui_plugin import GUICommand -from .run_plugin import RunCommand -from .csv2xml_plugin import Csv2XmlCommand from .merge_plugin import MergeCommand -from .gensim_plugin import GensimCommand +from .run_plugin import RunCommand from .update_plugin import UpdateCommand BuiltinSubcommands = [ diff --git a/opgee/built_ins/collect_plugin.py b/opgee/built_ins/collect_plugin.py index 779fdb90..93bb7abf 100644 --- a/opgee/built_ins/collect_plugin.py +++ b/opgee/built_ins/collect_plugin.py @@ -50,7 +50,7 @@ def addArgs(self, parser): return parser # for auto-doc generation def run(self, args, tool): - from ..manager import combine_mcs_results, combine_field_results + from ..manager import combine_field_results, combine_mcs_results if args.sim_dir: combine_mcs_results(args.sim_dir, args.fields, args.delete) diff --git a/opgee/built_ins/compare_excel.py b/opgee/built_ins/compare_excel.py index 1ce17a34..a826d601 100644 --- a/opgee/built_ins/compare_excel.py +++ b/opgee/built_ins/compare_excel.py @@ -1,6 +1,7 @@ -from matplotlib import pyplot as plt import pandas as pd import seaborn as sns +from matplotlib import pyplot as plt + # from scipy import stats # Could load this from opgee.built_ins.compare_plugin (RP) diff --git a/opgee/built_ins/compare_plugin.py b/opgee/built_ins/compare_plugin.py index e2e29810..971fb4d0 100644 --- a/opgee/built_ins/compare_plugin.py +++ b/opgee/built_ins/compare_plugin.py @@ -3,6 +3,7 @@ # Usually used to compare with similar output from Excel (OPGEEv3) and here (OPGEEv4). # from enum import Enum + from opgee.subcommand import SubcommandABC # Convert OPGEEv3 (Excel) process names to OPGEEv4 names diff --git a/opgee/built_ins/config_plugin.py b/opgee/built_ins/config_plugin.py index e105afe4..ed2d8d66 100644 --- a/opgee/built_ins/config_plugin.py +++ b/opgee/built_ins/config_plugin.py @@ -5,7 +5,7 @@ See the https://opensource.org/licenses/MIT for license details. """ -from ..error import OpgeeException, CommandlineError +from ..error import CommandlineError, OpgeeException from ..subcommand import SubcommandABC, clean_help @@ -63,7 +63,8 @@ def addArgs(self, parser): def run(self, args, tool): import re import subprocess - from ..config import getParam, _ConfigParser, USR_CONFIG_FILE + + from ..config import USR_CONFIG_FILE, _ConfigParser, getParam if args.edit: editor = getParam("OPGEE.TextEditor") diff --git a/opgee/built_ins/csv2xml_plugin.py b/opgee/built_ins/csv2xml_plugin.py index 91d5d2c2..fcf42fbb 100644 --- a/opgee/built_ins/csv2xml_plugin.py +++ b/opgee/built_ins/csv2xml_plugin.py @@ -6,8 +6,9 @@ """ from pathlib import Path -from ..subcommand import SubcommandABC + from ..log import getLogger +from ..subcommand import SubcommandABC _logger = getLogger(__name__) @@ -29,6 +30,7 @@ def read_fields(csv_path, from_package=False, skip_fields=None): the type ("str", "int", or "float") of the attribute. """ import pandas as pd + from ..pkg_utils import resourceStream stream = resourceStream(csv_path) if from_package else csv_path diff --git a/opgee/built_ins/gensim_plugin.py b/opgee/built_ins/gensim_plugin.py index adfb109d..39d97fa6 100644 --- a/opgee/built_ins/gensim_plugin.py +++ b/opgee/built_ins/gensim_plugin.py @@ -82,7 +82,7 @@ def addArgs(self, parser): return parser # for auto-doc generation def run(self, args, tool): - from ..error import McsUserError, CommandlineError + from ..error import CommandlineError, McsUserError from ..mcs.simulation import Simulation, read_distributions use_default_model = not args.no_default_model diff --git a/opgee/built_ins/graph_plugin.py b/opgee/built_ins/graph_plugin.py index dff8a433..98e49926 100644 --- a/opgee/built_ins/graph_plugin.py +++ b/opgee/built_ins/graph_plugin.py @@ -7,8 +7,8 @@ See the https://opensource.org/licenses/MIT for license details. """ -from ..subcommand import SubcommandABC, clean_help from ..log import getLogger +from ..subcommand import SubcommandABC, clean_help _logger = getLogger(__name__) @@ -84,7 +84,7 @@ def addArgs(self, parser): def run(self, args, tool): from ..error import CommandlineError - from ..graph import write_model_diagram, write_class_diagram, write_process_diagram + from ..graph import write_class_diagram, write_model_diagram, write_process_diagram from ..model_file import ModelFile use_default_model = not args.no_default_model diff --git a/opgee/built_ins/merge_plugin.py b/opgee/built_ins/merge_plugin.py index f2bac6b8..c56940db 100644 --- a/opgee/built_ins/merge_plugin.py +++ b/opgee/built_ins/merge_plugin.py @@ -8,8 +8,9 @@ """ from pathlib import Path -from ..subcommand import SubcommandABC + from ..log import getLogger +from ..subcommand import SubcommandABC _logger = getLogger(__name__) diff --git a/opgee/built_ins/run_plugin.py b/opgee/built_ins/run_plugin.py index 560e80ed..6886cf1a 100644 --- a/opgee/built_ins/run_plugin.py +++ b/opgee/built_ins/run_plugin.py @@ -5,9 +5,10 @@ See the https://opensource.org/licenses/MIT for license details. """ -from ..subcommand import SubcommandABC from opgee.constants import SIMPLE_RESULT + from ..log import getLogger +from ..subcommand import SubcommandABC _logger = getLogger(__name__) @@ -226,10 +227,10 @@ def addArgs(self, parser): def run(self, args, tool): from ..config import setParam from ..error import CommandlineError - from ..model_file import model_analysis_names, fields_for_analysis - from ..manager import Manager, save_results, TrialPacket, FieldPacket - from ..utils import parseTrialString, mkdirs + from ..manager import FieldPacket, Manager, TrialPacket, save_results from ..mcs.simulation import Simulation, model_file_path + from ..model_file import fields_for_analysis, model_analysis_names + from ..utils import mkdirs, parseTrialString analysis_names = args.analyses or [] batch_size = args.batch_size diff --git a/opgee/built_ins/update_plugin.py b/opgee/built_ins/update_plugin.py index 76da5b17..01ed86ac 100644 --- a/opgee/built_ins/update_plugin.py +++ b/opgee/built_ins/update_plugin.py @@ -41,9 +41,11 @@ def addArgs(self, parser): return parser # used for auto-doc generation def run(self, args, tool): - from collections import OrderedDict import os + from collections import OrderedDict + from lxml import etree as ET + from opgee.error import CommandlineError, XmlFormatError from opgee.XMLFile import XMLFile diff --git a/opgee/combine_streams.py b/opgee/combine_streams.py index 9cf710a8..d6d4ceb0 100644 --- a/opgee/combine_streams.py +++ b/opgee/combine_streams.py @@ -8,12 +8,11 @@ # import pandas as pd -from .units import ureg -from .core import STP -from .core import TemperaturePressure +from .core import STP, TemperaturePressure from .log import getLogger from .stream import Stream -from .thermodynamics import Oil, Gas, Water +from .thermodynamics import Gas, Oil, Water +from .units import ureg _logger = getLogger(__name__) @@ -48,7 +47,8 @@ def calculated_combined_API_using_weighted_average(streams): Args: streams (List): A list of crude oil stream objects. - Returns: + Returns + ------- float: The combined API of the crude oil streams. """ if len(streams) == 1: diff --git a/opgee/config.py b/opgee/config.py index 1243e491..c71ed58b 100644 --- a/opgee/config.py +++ b/opgee/config.py @@ -55,7 +55,6 @@ def unixPath(path, abspath=False): using the current working directory as the starting point. :return: (str) the modified pathname """ - # Use str values, not Paths path = str(path).replace("\\", "/") @@ -205,11 +204,10 @@ def getConfig(reload=False, allowMissing=False, createDefault=False, systemConfi def _readConfigResourceFile(filename, package="opgee", raiseError=True): try: data = getResource(filename, decode="utf-8") - except IOError: + except OSError: if raiseError: raise - else: - return None + return None _ConfigParser.read_string(data, source=filename) return data @@ -299,12 +297,11 @@ def readConfigFiles(allowMissing=False, systemConfigOnly=False): try: readConfigFile(usrConfigPath) - except IOError: + except OSError: if not allowMissing: if not os.path.lexists(usrConfigPath): raise ConfigFileError(f"Missing user config file '{usrConfigPath}'") - else: - raise ConfigFileError(f"Can't read user config file '{usrConfigPath}'") + raise ConfigFileError(f"Can't read user config file '{usrConfigPath}'") # Dynamically set (if not defined) OPGEE.ProjectName in each section, holding the # section (i.e., project) name. If user has set this, the value is unchanged. @@ -336,7 +333,6 @@ def getConfigDict(section=DEFAULT_SECTION, raw=False): :return: (dict) all variables defined in the section (which includes those defined in DEFAULT.) """ - # Deprecated (docker) # Translation function of identity # func = _translatePath if _PathMap else lambda x: x @@ -395,14 +391,12 @@ def getParam(name, section=None, raw=False, raiseError=True): except configparser.NoSectionError: if raiseError: raise OpgeeException(f'getParam: unknown section "{section}"') - else: - return None + return None except configparser.NoOptionError: if raiseError: raise OpgeeException(f'getParam: unknown variable "{name}"') - else: - return None + return None # Deprecated (docker) # perform pathname translation for use of opgee.cfg in Docker images @@ -426,10 +420,9 @@ def stringTrue(value, raiseError=True): return False if raiseError: - msg = 'Unrecognized boolean value: "{}". Must one of {}'.format(value, _True + _False) + msg = f'Unrecognized boolean value: "{value}". Must one of {_True + _False}' raise ConfigFileError(msg) - else: - return None + return None def getParamAsBoolean(name, section=None): @@ -452,7 +445,7 @@ def getParamAsBoolean(name, section=None): result = stringTrue(value, raiseError=False) if result is None: - msg = 'The value of variable "{}", {}, could not converted to boolean.'.format(name, value) + msg = f'The value of variable "{name}", {value}, could not converted to boolean.' raise ConfigFileError(msg) return result diff --git a/opgee/core.py b/opgee/core.py index ba2a5dd6..afd16fad 100644 --- a/opgee/core.py +++ b/opgee/core.py @@ -6,12 +6,13 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -import pint -import time import datetime +import time +import pint + +from .error import AbstractMethodError, ModelValidationError, OpgeeException from .units import ureg, validate_unit -from .error import OpgeeException, AbstractMethodError, ModelValidationError from .utils import coercible, getBooleanXML @@ -52,8 +53,7 @@ def instantiate_subelts(elt, cls, parent=None, as_dict=False, include_names=None if as_dict: d = {obj.name: obj for obj in objs} return d - else: - return objs + return objs def dict_from_list(objs): @@ -243,8 +243,7 @@ def set_value(self, value): if value.units == unit_obj: self.value = value return value - else: - value = value.magnitude + value = value.magnitude if self.pytype: value = coercible(value, self.pytype) @@ -275,7 +274,7 @@ class TemperaturePressure(OpgeeObject): Stores temperature and pressure together for convenience. """ - __slots__ = ("T", "P") # keeps instances small and fast + __slots__ = ("P", "T") # keeps instances small and fast def __init__(self, T, P): self.T = None diff --git a/opgee/emissions.py b/opgee/emissions.py index 6931299e..23d35833 100644 --- a/opgee/emissions.py +++ b/opgee/emissions.py @@ -9,10 +9,10 @@ import pandas as pd import pint -from .units import magnitude, ureg from .core import OpgeeObject from .error import OpgeeException from .stream import Stream +from .units import magnitude, ureg EM_COMBUSTION = "Combustion" EM_LAND_USE = "Land-use" diff --git a/opgee/energy.py b/opgee/energy.py index 5de02e39..27f044f2 100644 --- a/opgee/energy.py +++ b/opgee/energy.py @@ -8,10 +8,10 @@ # import pandas as pd -from .units import ureg from .core import OpgeeObject from .error import OpgeeException from .log import getLogger +from .units import ureg _logger = getLogger(__name__) diff --git a/opgee/field.py b/opgee/field.py index b5888055..b38d1d23 100644 --- a/opgee/field.py +++ b/opgee/field.py @@ -7,36 +7,36 @@ # See LICENSE.txt for license details. # import networkx as nx -import pint import pandas as pd +import pint -from .units import ureg +from .bfs import bfs +from .combine_streams import combine_streams from .config import getParamAsList from .constants import DETAILED_RESULT from .container import Container -from .core import elt_name, instantiate_subelts, dict_from_list, STP +from .core import STP, dict_from_list, elt_name, instantiate_subelts from .energy import Energy from .error import ( + ModelValidationError, OpgeeException, - OpgeeStopIteration, - OpgeeMaxIterationsReached, OpgeeIterationConverged, - ModelValidationError, + OpgeeMaxIterationsReached, + OpgeeStopIteration, ZeroEnergyFlowError, ) from .import_export import ImportExport from .log import getLogger from .post_processor import PostProcessor -from .process import Process, Aggregator, Reservoir, decache_subclasses +from .process import Aggregator, Process, Reservoir, decache_subclasses from .process_groups import ProcessChoice from .processes.steam_generator import SteamGenerator from .processes.transport_energy import TransportEnergy from .smart_defaults import SmartDefault from .stream import Stream -from .thermodynamics import Oil, Gas, Water +from .thermodynamics import Gas, Oil, Water +from .units import ureg from .utils import getBooleanXML, roundup -from .combine_streams import combine_streams -from .bfs import bfs _logger = getLogger(__name__) @@ -582,8 +582,7 @@ def boundary_energy_flow_rate(self, analysis, raiseError=True): if energy.m == 0: if raiseError: raise ZeroEnergyFlowError(boundary_proc) - else: - _logger.warning(f"Zero energy flow rate for {boundary_proc.boundary} boundary process {boundary_proc}") + _logger.warning(f"Zero energy flow rate for {boundary_proc.boundary} boundary process {boundary_proc}") return energy @@ -746,7 +745,7 @@ def get_imported_emissions(self, net_import): :param net_import: (Pandas.Series) net import energy rates (water is mass rate) :return: total emissions (units of g CO2) """ - from .import_export import WATER, N2, CO2_Flooding, ELECTRICITY + from .import_export import ELECTRICITY, N2, WATER, CO2_Flooding imported_emissions = ureg.Quantity(0.0, "tonne/day") @@ -776,7 +775,6 @@ def get_carbon_credit(self, byproduct_names, analysis): :param net_import: (Pandas.Series) net import energy rates (water is mass rate) :return: total emissions (units of g CO2) """ - carbon_credit = ureg.Quantity(0.0, "tonne/day") export = self.import_export.export_df process_names = set(export.index) @@ -857,12 +855,12 @@ def get_component_fugitive(self): ) field_productivity["Mean gas rate (Mscf/well/day)"] = ( - prod_mat_gas["Normalized rate"] if GOR > GOR_cutoff else prod_mat_oil["Normalized rate"] + prod_mat_gas["Normalized rate"] if GOR_cutoff < GOR else prod_mat_oil["Normalized rate"] ) field_productivity["Mean gas rate (Mscf/well/day)"] *= productivity field_productivity["Frac total gas"] = ( - prod_mat_gas["Frac total gas"] if GOR > GOR_cutoff else prod_mat_oil["Frac total gas"] + prod_mat_gas["Frac total gas"] if GOR_cutoff < GOR else prod_mat_oil["Frac total gas"] ) field_productivity["Assignment"] = field_productivity.apply( @@ -893,8 +891,8 @@ def get_component_fugitive(self): loss_mat_gas_ave = loss_mat_gas_ave.reshape(len(tranch), len(cols_gas)) loss_mat_gas_ave_df = pd.DataFrame(data=loss_mat_gas_ave, index=prod_mat_gas["Bin low"], columns=cols_gas) - cols = cols_gas if GOR > GOR_cutoff else cols_oil - loss_mat = loss_mat_gas if GOR > GOR_cutoff else loss_mat_oil + cols = cols_gas if GOR_cutoff < GOR else cols_oil + loss_mat = loss_mat_gas if GOR_cutoff < GOR else loss_mat_oil loss_mat_ave = loss_mat.mean(axis=0).values loss_mat_ave = loss_mat_ave.reshape(len(tranch), len(cols)) df = pd.DataFrame(loss_mat_ave, columns=cols, index=range(len(tranch))) @@ -909,7 +907,7 @@ def get_component_fugitive(self): pump_loss_rate.drop("Separator", inplace=True) # TBD: drop both at same time pump_loss_rate.drop("Flash factor", inplace=True) - if GOR > GOR_cutoff: + if GOR_cutoff < GOR: pump_loss_rate["LU-plunger-norm"] = ( pump_loss_rate["LU-plunger"] * frac_wells_with_plunger + pump_loss_rate["LU-no plunger"] * frac_wells_with_non_plunger @@ -939,7 +937,8 @@ def get_completion_and_workover_C1_rate(self): to determine the C1 rates for completion and workover events. The calculation uses a dataframe containing C1 rates for different scenarios of hydraulic fracturing, well type, flaring, and REC. - Returns: + Returns + ------- float: The total C1 rate for completion and workover events in the well system. """ oil_sands_mine = self.oil_sands_mine @@ -987,7 +986,6 @@ def validate(self): :return: none :raises ModelValidationError: raised if any validation condition is violated. """ - # Allow test models to skip validation to avoid overly complicating all tests if self.model.attr("skip_validation"): _logger.warning(f"{self} skipping Process and Stream validation") @@ -1076,7 +1074,6 @@ def _depends_on_cycle(self, process, visited=None): :param visited: (set) the Processes we've already encountered in our search. :return: (bool) True if ``process`` depends on any cycle, False otherwise. """ - visited = visited or set() for predecessor in process.predecessors(): @@ -1130,7 +1127,6 @@ def check_enabled_processes(self): """ Iterate all processes and allow them to check if they should be disabled before they run. """ - processes = self.processes() for proc in processes: @@ -1411,8 +1407,7 @@ def get_process_data(self, name, raiseError=None): except KeyError: if raiseError: raise OpgeeException(f"Process data dictionary does not include {name}") - else: - return None + return None def resolve_process_choices(self, process_choice_dict=None): """ @@ -1565,10 +1560,9 @@ def GOR_default(self, API): # =IF(API_grav<20,1122.4,IF(AND(API_grav>=20,API_grav<=30),1205.4,2429.3)) if api < 20: return 1122.4 - elif 20 <= api <= 30: + if 20 <= api <= 30: return 1205.4 - else: - return 2429.3 + return 2429.3 # TODO: handle special case of API depending on GOR # @SmartDefault.register('API', ['GOR']) @@ -1602,14 +1596,13 @@ def GFIR_default(self, flood_gas_type, GOR): if flood_gas_type == 1: return 1.5 * GOR - elif flood_gas_type == 2: + if flood_gas_type == 2: return 1200 - elif flood_gas_type == 3: + if flood_gas_type == 3: return 10000 - else: - return 1.5 * GOR + return 1.5 * GOR @SmartDefault.register("depth", ["GOR"]) def depth_default(self, GOR): diff --git a/opgee/graph.py b/opgee/graph.py index 1cef3a2e..bede9e40 100644 --- a/opgee/graph.py +++ b/opgee/graph.py @@ -118,7 +118,7 @@ def create_process_diagram(field: Field): for name, stream in field.stream_dict.items(): contents = ", ".join(stream.contents) procs: tuple[str, str] = stream.src_name, stream.dst_name - if not all((field.process_dict[prc].enabled for prc in procs)): + if not all(field.process_dict[prc].enabled for prc in procs): continue src, dst = procs graph.add_edge(pydot.Edge(src, dst, color="black", label=contents)) diff --git a/opgee/gui/app.py b/opgee/gui/app.py index 54e8f443..8e27f39f 100644 --- a/opgee/gui/app.py +++ b/opgee/gui/app.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- import dash from dash import dcc, html -from dash.dependencies import Input, Output, State, ClientsideFunction -from ..model_file import ModelFile -from ..log import getLogger +from dash.dependencies import ClientsideFunction, Input, Output, State -from .widgets import get_analysis_and_field, horiz_space, pulldown_style, label_style +from ..log import getLogger +from ..model_file import ModelFile +from .widgets import get_analysis_and_field, horiz_space, label_style, pulldown_style _logger = getLogger(__name__) @@ -24,94 +23,88 @@ def app_layout(app, model, analysis): analysis_names = [analysis.name for analysis in model.analyses()] # noinspection PyCallingNonCallable - layout = html.Div( - [ - dcc.Store(id="analysis-and-field", storage_type="session"), - # TBD: Experiment to see if client-side function fixes graph resizing problem, per - # https://stackoverflow.com/questions/55462861/dash-dynamic-layout-does-not-propagate-resized-graph-dimensions-until-window-i - html.Div(id="output-clientside"), - html.Div( - [ - html.H1(app.title), - html.Div( - [ - html.Center( - [ - html.Span("Model: ", style=label_style), - html.Span(f"{model.pathnames}"), - horiz_space, - html.Span("Analysis: ", style=label_style), - dcc.Dropdown( - id="analysis-selector", - placeholder="Select analysis...", - options=[{"value": name, "label": name} for name in analysis_names], - value=analysis.name, - style=pulldown_style, - ), - horiz_space, - html.Span("Field: ", style=label_style), - dcc.Dropdown( - id="field-selector", - placeholder="Select field...", - options=[{"value": "none", "label": "none"}], - value="none", - style=pulldown_style, - ), - ] - ), - html.Br(), - html.Button("Run model", id="run-button", n_clicks=0), - dcc.Markdown(id="run-model-status"), - ], - # style = {'height': '130px'} - ), - ], - style={"textAlign": "center"}, - ), - html.Div( - [ - dcc.Tabs( - id="tabs", - value="processes", - parent_className="custom-tabs", - className="custom-tabs-container", - children=[ - dcc.Tab( - children=[], # see processes_layout() - label="Processes", - value="processes", - className="custom-tab", - selected_className="custom-tab--selected", + layout = html.Div([ + dcc.Store(id="analysis-and-field", storage_type="session"), + # TBD: Experiment to see if client-side function fixes graph resizing problem, per + # https://stackoverflow.com/questions/55462861/dash-dynamic-layout-does-not-propagate-resized-graph-dimensions-until-window-i + html.Div(id="output-clientside"), + html.Div( + [ + html.H1(app.title), + html.Div( + [ + html.Center([ + html.Span("Model: ", style=label_style), + html.Span(f"{model.pathnames}"), + horiz_space, + html.Span("Analysis: ", style=label_style), + dcc.Dropdown( + id="analysis-selector", + placeholder="Select analysis...", + options=[{"value": name, "label": name} for name in analysis_names], + value=analysis.name, + style=pulldown_style, ), - dcc.Tab( - children=[], # see settings_layout() - label="Settings", - value="settings", - className="custom-tab", - selected_className="custom-tab--selected", + horiz_space, + html.Span("Field: ", style=label_style), + dcc.Dropdown( + id="field-selector", + placeholder="Select field...", + options=[{"value": "none", "label": "none"}], + value="none", + style=pulldown_style, ), - dcc.Tab( - children=[], # see results_layout() - label="Results", - value="results", - className="custom-tab", - selected_className="custom-tab--selected", - ), - ], + ]), + html.Br(), + html.Button("Run model", id="run-button", n_clicks=0), + dcc.Markdown(id="run-model-status"), + ], + # style = {'height': '130px'} + ), + ], + style={"textAlign": "center"}, + ), + html.Div([ + dcc.Tabs( + id="tabs", + value="processes", + parent_className="custom-tabs", + className="custom-tabs-container", + children=[ + dcc.Tab( + children=[], # see processes_layout() + label="Processes", + value="processes", + className="custom-tab", + selected_className="custom-tab--selected", + ), + dcc.Tab( + children=[], # see settings_layout() + label="Settings", + value="settings", + className="custom-tab", + selected_className="custom-tab--selected", + ), + dcc.Tab( + children=[], # see results_layout() + label="Results", + value="results", + className="custom-tab", + selected_className="custom-tab--selected", ), - html.Div(id="tab-content"), - ] + ], ), - ] - ) + html.Div(id="tab-content"), + ]), + ]) return layout def main(args): from ..version import VERSION from .process_pane import ProcessPane - from .settings_pane import SettingsPane from .results_pane import ResultsPane + from .settings_pane import SettingsPane use_default_model = not args.no_default_model @@ -188,8 +181,7 @@ def update_output(n_clicks, analysis_and_field): field.run(analysis) field.report() return "Model has been run" - else: - return "Model has not been run" + return "Model has not been run" @app.callback( Output("tab-content", "children"), @@ -202,10 +194,10 @@ def render_content(tab, analysis_and_field): if tab == "processes": return process_pane.get_layout(field) - elif tab == "settings": + if tab == "settings": return settings_pane.get_layout(field) - elif tab == "results": + if tab == "results": return results_pane.get_layout(field) # @app.callback( diff --git a/opgee/gui/process_pane.py b/opgee/gui/process_pane.py index 6c69fcf4..3c81d219 100644 --- a/opgee/gui/process_pane.py +++ b/opgee/gui/process_pane.py @@ -1,11 +1,12 @@ -from dash import dcc, html, dash_table +from textwrap import dedent as d + import dash_cytoscape as cyto +from dash import dash_table, dcc, html from dash.dependencies import Input, Output, State -from textwrap import dedent as d from ..core import name_of from ..log import getLogger -from .widgets import get_analysis_and_field, gui_switches, OpgeePane +from .widgets import OpgeePane, get_analysis_and_field, gui_switches _logger = getLogger(__name__) @@ -154,8 +155,7 @@ def display_emissions_and_energy(node_data, analysis_and_field): intermediate_str += f"\n\n{key}:\n{em_str}\n{en_str}" return header + emissions_str + energy_str + intermediate_str - else: - return "" + return "" @app.callback( Output("stream-data", "children"), @@ -234,8 +234,7 @@ def field_network_graph(field, show_stream_contents=False, show_disabled_procs=F def edge_class(stream): if stream.enabled and stream.dst_proc.enabled and stream.src_proc.enabled: return "enabled-edge" - else: - return "disabled-edge" + return "disabled-edge" def node_class(proc): return "disabled-node" if not proc.enabled else ("boundary-node" if proc.boundary else "enabled-node") @@ -349,6 +348,7 @@ def node_class(proc): def emissions_table(analysis, procs): import pandas as pd + from ..emissions import Emissions columns = [{"name": "Name", "id": "Name"}] + [{"name": col, "id": col} for col in Emissions.categories] @@ -369,7 +369,7 @@ def series_for_df(proc): # convert to scientific notation for col_name, col in df.iteritems(): if col.dtype == float: - df[col_name] = col.apply(lambda x: "{:.2E}".format(x)) + df[col_name] = col.apply(lambda x: f"{x:.2E}") data = df.to_dict("records") diff --git a/opgee/gui/results_pane.py b/opgee/gui/results_pane.py index c0d7c9d9..54e8b1c9 100644 --- a/opgee/gui/results_pane.py +++ b/opgee/gui/results_pane.py @@ -31,13 +31,11 @@ def get_layout(self, field, **kwargs): className="row", # style={'textAlign': "center"}, ), - html.Center( - [ - dcc.Graph(id="ci-barchart", style=barchart_style), - horiz_space, - dcc.Graph(id="energy-barchart", style=barchart_style), - ] - ), + html.Center([ + dcc.Graph(id="ci-barchart", style=barchart_style), + horiz_space, + dcc.Graph(id="energy-barchart", style=barchart_style), + ]), ], className="row", style={ # 'display': 'flex', @@ -84,13 +82,11 @@ def ci_barchart(ci_text, analysis_and_field): fn_unit = analysis.fn_unit.title() - df = pd.DataFrame( - { - "category": [pair[0] for pair in top_level], - "value": [pair[1] for pair in top_level], - "unit": [fn_unit] * len(top_level), - } - ) + df = pd.DataFrame({ + "category": [pair[0] for pair in top_level], + "value": [pair[1] for pair in top_level], + "unit": [fn_unit] * len(top_level), + }) fig = go.Figure( data=[ @@ -131,13 +127,11 @@ def energy_barchart(n_clicks, ci_figure, analysis_and_field): # Show results for top-level aggregators and procs for the selected field that are within the boundary top_level = [(obj.name, obj.energy.data.sum() / energy) for obj in field.children() if obj not in beyond] - df = pd.DataFrame( - { - "category": [pair[0] for pair in top_level], - "value": [pair[1] for pair in top_level], - "unit": [fn_unit] * len(top_level), - } - ) + df = pd.DataFrame({ + "category": [pair[0] for pair in top_level], + "value": [pair[1] for pair in top_level], + "unit": [fn_unit] * len(top_level), + }) fig = go.Figure( data=[ diff --git a/opgee/gui/settings_pane.py b/opgee/gui/settings_pane.py index 3bf5a533..c1f775b8 100644 --- a/opgee/gui/settings_pane.py +++ b/opgee/gui/settings_pane.py @@ -1,14 +1,14 @@ from collections import defaultdict +from pathlib import Path + from dash import dcc, html from dash.dependencies import Input, Output, State -from pathlib import Path from ..attributes import AttrDefs from ..config import getParam from ..gui.widgets import attr_inputs from ..log import getLogger from ..utils import mkdirs - from .widgets import OpgeePane _logger = getLogger(__name__) @@ -97,9 +97,8 @@ def generate_settings_callback(self, analysis, field): def func(n_clicks, xml_path, *values): if n_clicks == 0 or not values or not xml_path: return "Save attributes to an xml file" - else: - self.save_attributes(xml_path, ids, values, analysis, field) - return f"Attributes saved to '{xml_path}'" + self.save_attributes(xml_path, ids, values, analysis, field) + return f"Attributes saved to '{xml_path}'" app.callback( Output("save-button-status", "children"), @@ -122,6 +121,7 @@ def save_attributes(xml_path, ids, values, analysis, field): :return: none """ from lxml import etree as ET + from ..units import magnitude from ..utils import coercible @@ -130,7 +130,7 @@ def save_attributes(xml_path, ids, values, analysis, field): class_value_dict = defaultdict(list) - for id, value in zip(ids, values): + for id, value in zip(ids, values, strict=False): class_name, attr_name = id.split(":") # Don't write out values that are equal to defaults diff --git a/opgee/gui/testapp.py b/opgee/gui/testapp.py index f3cc46ce..4394d924 100644 --- a/opgee/gui/testapp.py +++ b/opgee/gui/testapp.py @@ -1,41 +1,37 @@ #!/usr/bin/env python3 import dash from dash import dcc, html -from dash.dependencies import Input, Output, State, ClientsideFunction +from dash.dependencies import ClientsideFunction, Input, Output, State def app_layout(app): label_style = {"font-weight": "bold"} # noinspection PyCallingNonCallable - layout = html.Div( - [ - dcc.Store(id="analysis-and-field", storage_type="session"), - # TBD: Experiment to see if client-side function fixes graph resizing problem, per - # https://stackoverflow.com/questions/55462861/dash-dynamic-layout-does-not-propagate-resized-graph-dimensions-until-window-i - # html.Div(id="output-clientside"), - html.Div( - [ - html.H1(app.title), - html.Div( - [ - html.Center( - [ - html.Span("Model: ", style=label_style), - html.Span("Not a real model"), - ] - ), - html.Br(), - html.Button("Run model", id="run-button", n_clicks=0), - dcc.Markdown(id="run-model-status"), - ], - # style = {'height': '130px'} - ), - ], - style={"textAlign": "center"}, - ), - ] - ) + layout = html.Div([ + dcc.Store(id="analysis-and-field", storage_type="session"), + # TBD: Experiment to see if client-side function fixes graph resizing problem, per + # https://stackoverflow.com/questions/55462861/dash-dynamic-layout-does-not-propagate-resized-graph-dimensions-until-window-i + # html.Div(id="output-clientside"), + html.Div( + [ + html.H1(app.title), + html.Div( + [ + html.Center([ + html.Span("Model: ", style=label_style), + html.Span("Not a real model"), + ]), + html.Br(), + html.Button("Run model", id="run-button", n_clicks=0), + dcc.Markdown(id="run-model-status"), + ], + # style = {'height': '130px'} + ), + ], + style={"textAlign": "center"}, + ), + ]) return layout @@ -65,8 +61,7 @@ def main(): def update_output(n_clicks, analysis_and_field): if n_clicks: return "Model has been run" - else: - return "Model has not been run" + return "Model has not been run" app.run_server(debug=True) diff --git a/opgee/gui/widgets.py b/opgee/gui/widgets.py index 4ec88ff7..7b068d2a 100644 --- a/opgee/gui/widgets.py +++ b/opgee/gui/widgets.py @@ -13,7 +13,7 @@ label_style = {"font-weight": "bold"} -class OpgeePane(object): +class OpgeePane: def __init__(self, app, model): self.app = app self.model = model diff --git a/opgee/import_export.py b/opgee/import_export.py index 8a986821..bbe99968 100644 --- a/opgee/import_export.py +++ b/opgee/import_export.py @@ -10,7 +10,6 @@ import pint from .core import OpgeeObject -from .error import OpgeeException from .energy import ( EN_CRUDE_OIL, EN_DIESEL, @@ -21,6 +20,7 @@ EN_RESID, EN_UPG_PROC_GAS, ) +from .error import OpgeeException from .log import getLogger _logger = getLogger(__name__) diff --git a/opgee/log.py b/opgee/log.py index 485bc85f..62050036 100644 --- a/opgee/log.py +++ b/opgee/log.py @@ -18,7 +18,7 @@ import logging import os -from .config import getParam, getParamAsBoolean, configLoaded +from .config import configLoaded, getParam, getParamAsBoolean PKGNAME = __name__.split(".")[0] @@ -214,7 +214,7 @@ def configureLogs(force=False): # Next do all the implicit ones, setting their log levels # to their parent log levels and setting propagate to False. - for name in _Loggers.keys(): + for name in _Loggers: if name not in explicit: _configureLogger(name, force=force) diff --git a/opgee/manager.py b/opgee/manager.py index c8b0bea1..98971161 100644 --- a/opgee/manager.py +++ b/opgee/manager.py @@ -7,28 +7,28 @@ # Junior University. See LICENSE.txt for license details. # import asyncio -import dask -from dask_jobqueue import SLURMCluster -from dask.distributed import Client, SubprocessCluster, as_completed -from glob import glob import os -import pandas as pd -import pint import re -from typing import Sequence +from collections.abc import Sequence +from glob import glob +import dask +import pandas as pd +import pint +from dask.distributed import Client, SubprocessCluster, as_completed +from dask_jobqueue import SLURMCluster -from .audit import audit_required, audit_field +from .audit import audit_field, audit_required +from .config import getParam, getParamAsBoolean, getParamAsInt, pathjoin +from .constants import CLUSTER_NONE, DETAILED_RESULT, ERROR_RESULT, SIMPLE_RESULT from .core import OpgeeObject, Timer -from .config import getParam, getParamAsInt, getParamAsBoolean, pathjoin -from .constants import CLUSTER_NONE, SIMPLE_RESULT, DETAILED_RESULT, ERROR_RESULT -from .error import McsSystemError, AbstractMethodError +from .error import AbstractMethodError, McsSystemError from .field import FieldResult from .log import getLogger, setLogFile +from .mcs.simulation import FAILURES_CSV, RESULTS_CSV, Simulation from .model_file import extract_model from .post_processor import PostProcessor -from .utils import flatten, pushd, mkdirs -from .mcs.simulation import Simulation, RESULTS_CSV, FAILURES_CSV +from .utils import flatten, mkdirs, pushd # To debug dask, uncomment the following 2 lines # import logging @@ -340,7 +340,6 @@ def run_packets( self.stop_cluster() _logger.info(timer.stop()) - return None def _run_field(analysis_name, field_name, xml_string, result_type, use_default_model=True): diff --git a/opgee/mcs/LHS.py b/opgee/mcs/LHS.py index 11038ab7..cdfed4c4 100644 --- a/opgee/mcs/LHS.py +++ b/opgee/mcs/LHS.py @@ -15,8 +15,8 @@ # Heavily modified from http://nullege.com/codes/show/src@m@o@model-builder-HEAD@Bayes@lhs.py # import numpy as np -from scipy import stats from pandas import DataFrame +from scipy import stats from ..log import getLogger diff --git a/opgee/mcs/distributed_mcs_dask.py b/opgee/mcs/distributed_mcs_dask.py index 25ee80f6..9aa8f71c 100644 --- a/opgee/mcs/distributed_mcs_dask.py +++ b/opgee/mcs/distributed_mcs_dask.py @@ -7,19 +7,20 @@ # See LICENSE.txt for license details. # import asyncio +from itertools import islice, product + import dask # from dask_jobqueue import SLURMCluster from dask.distributed import Client, LocalCluster, as_completed -from itertools import islice, product + +from ..config import getParam, getParamAsBoolean, getParamAsInt # To debug dask, uncomment the following 2 lines # import logging # logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG) - from ..core import OpgeeObject, Timer -from ..config import getParam, getParamAsInt, getParamAsBoolean -from ..error import RemoteError, McsSystemError, TrialErrorWrapper +from ..error import McsSystemError, RemoteError, TrialErrorWrapper from ..log import getLogger, setLogFile from .simulation import Simulation, combine_results @@ -53,7 +54,7 @@ def _walltime(minutes: int) -> str: class FieldResult(OpgeeObject): - __slots__ = ["ok", "field_name", "packet_num", "duration", "completed", "task_count", "error"] + __slots__ = ["completed", "duration", "error", "field_name", "ok", "packet_num", "task_count"] def __init__(self, field_name, duration, completed, packet_num=None, error=None): self.ok = error is None diff --git a/opgee/mcs/distro.py b/opgee/mcs/distro.py index 46a80dac..2864f784 100644 --- a/opgee/mcs/distro.py +++ b/opgee/mcs/distro.py @@ -14,9 +14,9 @@ import re import numpy as np -from scipy.stats import lognorm, triang, uniform, norm, rv_discrete, truncnorm +from scipy.stats import lognorm, norm, rv_discrete, triang, truncnorm, uniform -from ..error import OpgeeException, DistributionSpecError, McsUserError +from ..error import DistributionSpecError, McsUserError, OpgeeException from ..log import getLogger from ..pkg_utils import resourceStream @@ -310,7 +310,7 @@ def clear_file_cache(cls): cls.file_cache.clear() -class GridRV(object): +class GridRV: """ Return an object that behaves like an RV in that it returns N values when when requested via the ppf (percent point function), though the N values are @@ -340,7 +340,7 @@ def ppf(self, q): return tiled -class linkedDistro(object): +class linkedDistro: def __init__(self, parameter): """Linked to (i.e., shares RV data with) `withParameter`""" self.parameter = parameter @@ -360,7 +360,7 @@ def ppf(self, q): return self.trialData[self.parameter] # TBD: return as an ndarray -class DistroGen(object): +class DistroGen: """ Stores information required to generate a Distro instance from an argDict """ diff --git a/opgee/mcs/parameter_list.py b/opgee/mcs/parameter_list.py index 79856a39..19eb74f9 100644 --- a/opgee/mcs/parameter_list.py +++ b/opgee/mcs/parameter_list.py @@ -116,20 +116,18 @@ def number(name, default=None): elif shape == "Normal": if stdev == 0.0: _logger.info(f"* Ignoring distribution on {name}, Normal has stdev = 0") + elif low is None or high is None: + rv = get_frozen_rv("normal", mean=mean, stdev=stdev) else: - if low is None or high is None: - rv = get_frozen_rv("normal", mean=mean, stdev=stdev) - else: - rv = get_frozen_rv("truncated_normal", mean=mean, stdev=stdev, low=low, high=high) + rv = get_frozen_rv("truncated_normal", mean=mean, stdev=stdev, low=low, high=high) elif shape == "Lognormal": if log_stdev == 0.0: _logger.info(f"* Ignoring distribution on {name}, Lognormal has stdev = 0") + elif low is None or high is None: # must specify both low and high + rv = get_frozen_rv("lognormal", logmean=log_mean, logstdev=log_stdev) else: - if low is None or high is None: # must specify both low and high - rv = get_frozen_rv("lognormal", logmean=log_mean, logstdev=log_stdev) - else: - rv = get_frozen_rv("truncated_lognormal", logmean=log_mean, logstdev=log_stdev, low=low, high=high) + rv = get_frozen_rv("truncated_lognormal", logmean=log_mean, logstdev=log_stdev, low=low, high=high) elif shape == "Choice": _logger.info("* Choice distribution is not yet supported") diff --git a/opgee/mcs/simulation.py b/opgee/mcs/simulation.py index f1871549..2c59351d 100644 --- a/opgee/mcs/simulation.py +++ b/opgee/mcs/simulation.py @@ -8,19 +8,19 @@ # import json import os -import pandas as pd import traceback +import pandas as pd + from ..config import pathjoin -from ..constants import SIMPLE_RESULT, ERROR_RESULT +from ..constants import ERROR_RESULT, SIMPLE_RESULT from ..core import OpgeeObject, split_attr_name -from ..error import OpgeeException, McsSystemError, McsUserError, CommandlineError +from ..error import CommandlineError, McsSystemError, McsUserError, OpgeeException from ..field import FieldResult from ..log import getLogger from ..model_file import ModelFile from ..pkg_utils import resourceStream from ..utils import mkdirs, removeTree - from .distro import get_frozen_rv from .LHS import lhs @@ -283,7 +283,7 @@ def _save_meta_data(self): def _load_meta_data(self, field_names): metadata_path = self.metadata_path() try: - with open(metadata_path, "r") as fp: + with open(metadata_path) as fp: self.metadata = metadata = json.load(fp) except Exception as e: raise McsUserError(f"Failed to load simulation '{metadata_path}' : {e}") diff --git a/opgee/model.py b/opgee/model.py index 4d4f7651..41c8b03f 100644 --- a/opgee/model.py +++ b/opgee/model.py @@ -7,15 +7,15 @@ # See LICENSE.txt for license details. # -from .units import ureg from .analysis import Analysis from .container import Container from .core import elt_name, instantiate_subelts -from .error import OpgeeException, CommandlineError +from .error import CommandlineError, OpgeeException from .field import Field from .log import getLogger from .table_manager import TableManager from .table_update import TableUpdate +from .units import ureg DEFAULT_SCHEMA_VERSION = "4.0.0.a" diff --git a/opgee/model_file.py b/opgee/model_file.py index 644f213e..f27325ab 100644 --- a/opgee/model_file.py +++ b/opgee/model_file.py @@ -26,7 +26,7 @@ _logger = getLogger(__name__) -class ModelCache(object): +class ModelCache: """ Support for optimizing reading / running fields from large (i.e., thousands) of Fields from Model files generated from XML. XMLFile instances are cached by @@ -385,7 +385,7 @@ def __init__( if found is None or len(found) == 0: raise XmlFormatError(f"Missing as child of in '{pathnames}'") - elif len(found) > 1: + if len(found) > 1: raise XmlFormatError("Multiple appear as children of in '{pathnames}'") AttrDefs.load_attr_defs(found[0]) diff --git a/opgee/post_processor.py b/opgee/post_processor.py index c6ee13e3..2ac72a47 100644 --- a/opgee/post_processor.py +++ b/opgee/post_processor.py @@ -7,6 +7,7 @@ # import glob import os + from .config import getParam from .core import OpgeeObject from .error import AbstractMethodError, McsUserError @@ -65,7 +66,6 @@ def __init__(self): def run(self, analysis, field, results): # to avoid an import cycle, args have no type specs - """ [Required method to be implemented by subclasses.] @@ -119,6 +119,7 @@ def load_plugin(cls, path): """ import inspect import os.path + from .utils import loadModuleFromPath if not os.path.exists(path): @@ -131,7 +132,7 @@ def load_plugin(cls, path): # Subclasses import PostProcessor, but we want only proper subclasses, not PostProcessor if subcls != PostProcessor and inspect.isclass(subcls) and issubclass(subcls, PostProcessor): # ensure that only one instance of a given class is registered - if any((isinstance(inst, subcls) for inst in cls.instances)): + if any(isinstance(inst, subcls) for inst in cls.instances): continue instance = subcls() cls.instances.append(instance) diff --git a/opgee/process.py b/opgee/process.py index a4ce0fff..afc6a782 100644 --- a/opgee/process.py +++ b/opgee/process.py @@ -6,23 +6,22 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from typing import Union, Optional import pandas as pd import pint -from .units import ureg, magnitude from .attributes import AttrDefs, AttributeMixin from .combine_streams import combine_streams from .config import getParamAsBoolean from .container import Container from .core import OpgeeObject, XmlInstantiable, elt_name, instantiate_subelts -from .emissions import Emissions, EM_COMBUSTION +from .emissions import EM_COMBUSTION, Emissions from .energy import EN_ELECTRICITY, Energy -from .error import OpgeeException, AbstractMethodError, OpgeeIterationConverged, ModelValidationError +from .error import AbstractMethodError, ModelValidationError, OpgeeException, OpgeeIterationConverged from .import_export import ImportExport from .log import getLogger from .stream import Stream +from .units import magnitude, ureg from .utils import getBooleanXML _logger = getLogger(__name__) @@ -51,13 +50,12 @@ def _subclass_dict(superclass): if prior is None: d[name] = cls - else: - if prior != cls: - msg = f"Class '{name}' is defined by both {cls} and {prior}" - if allow_redef: - print(msg) - else: - raise OpgeeException(msg) + elif prior != cls: + msg = f"Class '{name}' is defined by both {cls} and {prior}" + if allow_redef: + print(msg) + else: + raise OpgeeException(msg) return d @@ -65,7 +63,7 @@ def _subclass_dict(superclass): # # Cache of known subclasses of Aggregator and Process # -_Subclass_dict: Optional[dict] = None +_Subclass_dict: dict | None = None def decache_subclasses(): @@ -136,7 +134,6 @@ def run_corr_eqns(x1, x2, x3, x4, x5, coef_df): :param coef_df: (pandas.DataFrame) data values :return: pandas.Series """ - x = pd.Series( data=[ 1, @@ -381,7 +378,7 @@ def within_boundary(self): visited = dict() def _visit(proc): - if proc is None or visited.get(id(proc), False): + if proc is None or visited.get(id(proc)): return visited[id(proc)] = proc @@ -403,7 +400,7 @@ def beyond_boundary(self): visited = dict() def _visit(proc): - if proc is None or visited.get(id(proc), False): + if proc is None or visited.get(id(proc)): return visited[id(proc)] = proc @@ -517,7 +514,7 @@ def set_import_from_energy(self, energy_use): def set_gas_fugitives(self, stream, loss_rate) -> Stream: # TODO: complete this using Jeff's code """ - initialize the gas fugitives stream, get loss rate, copy.. + Initialize the gas fugitives stream, get loss rate, copy.. :param loss_rate: :param stream: @@ -536,7 +533,8 @@ def get_compressor_and_well_loss_rate(self, inlet_stream): Args: inlet_stream: A Stream object representing the inlet stream to the system. - Returns: + Returns + ------- A Quantity object representing the compressor and well loss rate for the given inlet stream. @@ -547,7 +545,6 @@ def get_compressor_and_well_loss_rate(self, inlet_stream): compressor, the compressor loss rate is returned, otherwise the well loss rate is returned. The result is returned as a Quantity object with units of "frac". """ - if inlet_stream.total_flow_rate() == 0: return ureg.Quantity(0, "frac") @@ -586,7 +583,7 @@ def find_stream(self, name, raiseError=False) -> Stream: def _find_streams_by_type( self, direction, stream_type, combine=False, as_list=False, regex=False, raiseError=True - ) -> Union[Stream, list, dict]: + ) -> Stream | list | dict: """ Find the input or output streams (indicated by `direction`) that contain the indicated `stream_type`, e.g., 'oil', 'water' and so on. @@ -614,7 +611,7 @@ def _find_streams_by_type( def find_input_streams( self, stream_type, combine=False, as_list=False, regex=False, raiseError=True - ) -> Union[Stream, list, dict]: + ) -> Stream | list | dict: """ Convenience method to call `_find_streams_by_type` with direction "input" @@ -632,7 +629,7 @@ def find_input_streams( def find_output_streams( self, stream_type, combine=False, as_list=False, regex=False, raiseError=True - ) -> Union[Stream, list, dict]: + ) -> Stream | list | dict: """ Convenience method to call `_find_streams_by_type` with direction "output" @@ -648,7 +645,7 @@ def find_output_streams( self.OUTPUT, stream_type, regex=regex, combine=combine, as_list=as_list, raiseError=raiseError ) - def find_input_stream(self, stream_type, regex=False, raiseError=True) -> Union[Stream, None]: + def find_input_stream(self, stream_type, regex=False, raiseError=True) -> Stream | None: """ Find exactly one input stream connected to a downstream Process that produces the indicated `stream_type`, e.g., 'oil', 'water' and so on. @@ -667,7 +664,7 @@ def find_input_stream(self, stream_type, regex=False, raiseError=True) -> Union[ return streams[0] - def find_output_stream(self, stream_type, regex=False, raiseError=True) -> Union[Stream, None]: + def find_output_stream(self, stream_type, regex=False, raiseError=True) -> Stream | None: """ Find exactly one output stream connected to a downstream Process that consumes the indicated `stream_type`, e.g., 'oil', 'water' and so on. @@ -777,7 +774,9 @@ def converged(prior_value, value): _logger.debug(f"prior value is {prior_value}") else: pairs = ( - zip(prior_value, value) if isinstance(value, (tuple, list)) else [(prior_value, value)] + zip(prior_value, value, strict=False) + if isinstance(value, (tuple, list)) + else [(prior_value, value)] ) # make a list of the one pair if all([converged(old, new) for old, new in pairs]): @@ -897,7 +896,6 @@ def get_intermediate_results(self): :return: A dictionary of energy and emission instances or None """ - return self.intermediate_results def sum_intermediate_results(self): @@ -906,7 +904,6 @@ def sum_intermediate_results(self): :return: """ - if self.intermediate_results is None: return diff --git a/opgee/processes/CO2_reinjection_compressor.py b/opgee/processes/CO2_reinjection_compressor.py index b706ea48..b9af5a15 100644 --- a/opgee/processes/CO2_reinjection_compressor.py +++ b/opgee/processes/CO2_reinjection_compressor.py @@ -6,11 +6,11 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg from ..emissions import EM_FUGITIVES from ..log import getLogger from ..process import Process from ..processes.compressor import Compressor +from ..units import ureg from .shared import get_energy_carrier _logger = getLogger(__name__) @@ -26,7 +26,8 @@ class CO2ReinjectionCompressor(Process): Outputs: - gas: The outlet stream of CO2 gas that is reinjected into the reservoir. - Attributes: + Attributes + ---------- - res_press: The reservoir pressure in psia. - eta_compressor: The compressor efficiency. - prime_mover_type: The type of prime mover used to power the compressor. diff --git a/opgee/processes/LNG_regasification.py b/opgee/processes/LNG_regasification.py index 25b2bdeb..2b2dcd80 100644 --- a/opgee/processes/LNG_regasification.py +++ b/opgee/processes/LNG_regasification.py @@ -6,9 +6,9 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from .shared import get_energy_carrier, get_energy_consumption from ..log import getLogger from ..process import Process +from .shared import get_energy_carrier, get_energy_consumption _logger = getLogger(__name__) diff --git a/opgee/processes/__init__.py b/opgee/processes/__init__.py index b1487f3b..d78c2c1d 100644 --- a/opgee/processes/__init__.py +++ b/opgee/processes/__init__.py @@ -1,13 +1,8 @@ +from .acid_gas_removal import AcidGasRemoval +from .bitumen_mining import BitumenMining from .CO2_injection_well import CO2InjectionWell from .CO2_membrane import CO2Membrane from .CO2_reinjection_compressor import CO2ReinjectionCompressor -from .LNG_liquefaction import LNGLiquefaction -from .LNG_regasification import LNGRegasification -from .LNG_transport import LNGTransport -from .VF_partition import VFPartition -from .VRU_compressor import VRUCompressor -from .acid_gas_removal import AcidGasRemoval -from .bitumen_mining import BitumenMining from .crude_oil_dewatering import CrudeOilDewatering from .crude_oil_stabilization import CrudeOilStabilization from .crude_oil_storage import CrudeOilStorage @@ -26,6 +21,9 @@ from .gas_reinjection_well import GasReinjectionWell from .heavy_oil_dilution import HeavyOilDilution from .heavy_oil_upgrading import HeavyOilUpgrading +from .LNG_liquefaction import LNGLiquefaction +from .LNG_regasification import LNGRegasification +from .LNG_transport import LNGTransport from .natural_gas_liquid import NGL from .petrocoke_transport import PetrocokeTransport from .post_storage_compressor import PostStorageCompressor @@ -42,5 +40,7 @@ from .storage_well import StorageWell from .transmission_compressor import TransmissionCompressor from .venting import Venting +from .VF_partition import VFPartition +from .VRU_compressor import VRUCompressor from .water_injection import WaterInjection from .water_treatment import WaterTreatment diff --git a/opgee/processes/acid_gas_removal.py b/opgee/processes/acid_gas_removal.py index 053e696a..051177b7 100644 --- a/opgee/processes/acid_gas_removal.py +++ b/opgee/processes/acid_gas_removal.py @@ -6,13 +6,13 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg from ..emissions import EM_FUGITIVES from ..energy import EN_ELECTRICITY from ..log import getLogger from ..process import Process, run_corr_eqns +from ..units import ureg from .compressor import Compressor -from .shared import get_energy_carrier, predict_blower_energy_use, get_bounded_value, get_energy_consumption +from .shared import get_bounded_value, get_energy_carrier, get_energy_consumption, predict_blower_energy_use _logger = getLogger(__name__) @@ -30,7 +30,8 @@ class AcidGasRemoval(Process): - gas for demethanizer: Gas stream with CO2 and H2S removed, and fed to the demethanizer. - gas for CO2 compressor: Gas stream containing CO2 to be compressed and injected back into the reservoir. - Attributes: + Attributes + ---------- - type_amine: The type of amine solution used for CO2 and H2S removal. - ratio_reflux_reboiler: The reflux-to-reboil ratio used in the process. - AGR_feedin_press: The feed-in pressure of the gas stream. @@ -48,7 +49,8 @@ class AcidGasRemoval(Process): - prime_mover_type: The type of prime mover used in the process. - amine_solution_K_value: The K value of the amine solution used for CO2 and H2S removal. - Methods: + Methods + ------- - run(analysis): Runs the acid gas removal process. - calculate_energy_consumption_from_Aspen(input_stream, output_stream, mol_frac_CO2, mol_frac_H2S): Calculates energy consumption for the acid gas removal process using Aspen HYSYS simulation. @@ -220,7 +222,6 @@ def calculate_energy_consumption_from_Aspen(self, input, output_gas, mol_frac_CO :return: (tuple) Compressor energy consumption (Quantity), Reboiler fuel use (Quantity), and Electricity consumption (Quantity) """ - # Define a dictionary of the bounds for each variable used in the calculation from HYSYS variable_bound_dict = { "mol_frac_CO2": [0.0, 0.2], @@ -289,7 +290,6 @@ def calculate_energy_consumption_from_textbook(self, input, mol_frac_CO2, mol_fr :return: (tuple) Compressor energy consumption (Quantity) == 0, Reboiler fuel use (Quantity), and Electricity consumption (Quantity) """ - # Calculate feed gas volume rate at STP feedin_gas_volume_rate_STP = self.gas.volume_flow_rates_STP(input) diff --git a/opgee/processes/bitumen_mining.py b/opgee/processes/bitumen_mining.py index ba2e1f83..a87a7736 100644 --- a/opgee/processes/bitumen_mining.py +++ b/opgee/processes/bitumen_mining.py @@ -6,14 +6,14 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg from ..core import TemperaturePressure from ..emissions import EM_FUGITIVES -from ..energy import EN_NATURAL_GAS, EN_ELECTRICITY, EN_DIESEL +from ..energy import EN_DIESEL, EN_ELECTRICITY, EN_NATURAL_GAS +from ..error import OpgeeException from ..log import getLogger from ..process import Process from ..stream import Stream -from ..error import OpgeeException +from ..units import ureg _logger = getLogger(__name__) @@ -31,7 +31,8 @@ class BitumenMining(Process): - Bitumen stream for upgrading or dilution - Gas stream for partition - Attributes: + Attributes + ---------- - oil_sands_mine: Name of the oil sands mine - API_bitumen: API gravity of the bitumen - bitumen_SG: Specific gravity of the bitumen diff --git a/opgee/processes/compressor.py b/opgee/processes/compressor.py index 6a5807c8..d8bbcaaa 100644 --- a/opgee/processes/compressor.py +++ b/opgee/processes/compressor.py @@ -6,7 +6,7 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from typing import Optional, Sequence, Tuple +from collections.abc import Sequence from pint.facets.plain import PlainQuantity as Quantity @@ -17,7 +17,7 @@ # type aliases Q_Float = Quantity[float] -Q_IntTuple = Tuple[Q_Float, int] +Q_IntTuple = tuple[Q_Float, int] _power = [1, 1 / 2, 1 / 3, 1 / 4, 1 / 5] @@ -35,7 +35,7 @@ def get_compressor_work_temp( gas_stream, compression_ratio, num_of_compression, - ) -> Tuple[Q_Float, Q_Float, Q_Float]: + ) -> tuple[Q_Float, Q_Float, Q_Float]: """ :param field: @@ -70,14 +70,14 @@ def get_compressor_work_temp( @staticmethod def get_compression_ratio_stages(overall_compression_ratio_stages: Sequence[Q_Float]) -> Sequence[Q_IntTuple]: - compression_ratios: map[Optional[Q_IntTuple]] = map( + compression_ratios: map[Q_IntTuple | None] = map( Compressor.get_compression_ratio_and_stage, overall_compression_ratio_stages ) return [ratio for ratio in compression_ratios if ratio is not None] @staticmethod @ureg.wraps(("frac", None), "frac", strict=False) - def get_compression_ratio_and_stage(overall_compression_ratio: float) -> Optional[Q_IntTuple]: + def get_compression_ratio_and_stage(overall_compression_ratio: float) -> Q_IntTuple | None: max_stages = len(_power) for pow in _power: comp_raised = overall_compression_ratio**pow @@ -92,7 +92,7 @@ def get_compressor_energy_consumption( eta_compressor, overall_compression_ratio, inlet_stream: Stream, - inlet_tp: Optional[TemperaturePressure] = None, + inlet_tp: TemperaturePressure | None = None, ): """ Calculate compressor energy consumption diff --git a/opgee/processes/crude_oil_dewatering.py b/opgee/processes/crude_oil_dewatering.py index 9744f462..51a32171 100644 --- a/opgee/processes/crude_oil_dewatering.py +++ b/opgee/processes/crude_oil_dewatering.py @@ -6,12 +6,12 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg from ..core import TemperaturePressure from ..error import OpgeeException from ..log import getLogger from ..process import Process from ..stream import PHASE_LIQUID +from ..units import ureg from .shared import get_energy_carrier _logger = getLogger(__name__) @@ -22,7 +22,8 @@ class CrudeOilDewatering(Process): A subclass of the Process class that represents a crude oil dewatering process in an oil and gas production system. - Attributes: + Attributes + ---------- field (Field): The field associated with the dewatering process. heater_treater (bool): Whether a heater treater is used in the process. temperature_heater_treater (Quantity): Temperature of the heater treater. diff --git a/opgee/processes/crude_oil_stabilization.py b/opgee/processes/crude_oil_stabilization.py index 4beb9233..b4258e89 100644 --- a/opgee/processes/crude_oil_stabilization.py +++ b/opgee/processes/crude_oil_stabilization.py @@ -6,13 +6,13 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from .compressor import Compressor -from .shared import get_energy_carrier from ..core import TemperaturePressure from ..emissions import EM_FUGITIVES from ..log import getLogger from ..process import Process -from ..stream import Stream, PHASE_LIQUID, PHASE_GAS +from ..stream import PHASE_GAS, PHASE_LIQUID, Stream +from .compressor import Compressor +from .shared import get_energy_carrier _logger = getLogger(__name__) @@ -23,7 +23,8 @@ class CrudeOilStabilization(Process): This class handles the stabilization of oil by removing gas, managing energy use, and calculating emissions associated with the stabilization process. - Attributes: + Attributes + ---------- field (Field): The field associated with the stabilization process. stab_tp (TemperaturePressure): The temperature and pressure of the stabilizer column. mol_per_scf (float): The number of moles per standard cubic feet. diff --git a/opgee/processes/crude_oil_storage.py b/opgee/processes/crude_oil_storage.py index 49bcb84d..e25be3e9 100644 --- a/opgee/processes/crude_oil_storage.py +++ b/opgee/processes/crude_oil_storage.py @@ -6,12 +6,11 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg from ..emissions import EM_FUGITIVES from ..log import getLogger from ..process import Process -from ..stream import PHASE_GAS -from ..stream import Stream +from ..stream import PHASE_GAS, Stream +from ..units import ureg _logger = getLogger(__name__) @@ -32,7 +31,8 @@ class CrudeOilStorage(Process): This process does not use any energy, and only produces emissions from the gas fugitives stream. - Attributes: + Attributes + ---------- field: The `Field` object that this process belongs to. oil: The `Oil` object representing the type of crude oil being stored. oil_sands_mine: A string representing the name of the oil sands mine, or "None" if there is no mine. diff --git a/opgee/processes/demethanizer.py b/opgee/processes/demethanizer.py index e8d3160a..526bd700 100644 --- a/opgee/processes/demethanizer.py +++ b/opgee/processes/demethanizer.py @@ -8,16 +8,15 @@ # import pandas as pd -from ..units import ureg from ..core import STP, TemperaturePressure from ..emissions import EM_FUGITIVES from ..energy import EN_ELECTRICITY from ..log import getLogger -from ..process import Process -from ..process import run_corr_eqns +from ..process import Process, run_corr_eqns from ..stream import PHASE_GAS, Stream +from ..units import ureg from .compressor import Compressor -from .shared import get_energy_carrier, predict_blower_energy_use, get_bounded_value +from .shared import get_bounded_value, get_energy_carrier, predict_blower_energy_use _logger = getLogger(__name__) @@ -29,6 +28,7 @@ class Demethanizer(Process): and a heavier hydrocarbon stream (LPG). Attributes + ---------- feed_press_demethanizer : pint.Quantity The pressure of the feed gas entering the demethanizer column. column_pressure : pint.Quantity @@ -57,6 +57,7 @@ class Demethanizer(Process): The type of prime mover used in the process. Methods + ------- run(analysis) Simulates the Demethanizer process to separate the incoming gas stream into a methane-rich stream and a heavier hydrocarbon stream. @@ -175,14 +176,14 @@ def run(self, analysis): NGL_mol_frac = pd.Series( { name: max(0, corr_result_df.loc[tbl_name, :].sum()) - for name, tbl_name in zip(hydrocarbon_label, NGL_label) + for name, tbl_name in zip(hydrocarbon_label, NGL_label, strict=False) }, dtype="pint[frac]", ) fuel_gas_mol_frac = pd.Series( { name: max(0, corr_result_df.loc[tbl_name, :].sum()) - for name, tbl_name in zip(hydrocarbon_label, fuel_gas_label) + for name, tbl_name in zip(hydrocarbon_label, fuel_gas_label, strict=False) }, dtype="pint[frac]", ) diff --git a/opgee/processes/downhole_pump.py b/opgee/processes/downhole_pump.py index 0e2d97d1..fecf6e7b 100644 --- a/opgee/processes/downhole_pump.py +++ b/opgee/processes/downhole_pump.py @@ -8,13 +8,13 @@ # import numpy as np -from ..units import ureg +from ..combine_streams import combine_streams from ..core import TemperaturePressure from ..emissions import EM_FUGITIVES from ..log import getLogger from ..process import Process -from ..stream import Stream, PHASE_GAS -from ..combine_streams import combine_streams +from ..stream import PHASE_GAS, Stream +from ..units import ureg from .shared import get_energy_carrier, get_energy_consumption_stages _logger = getLogger(__name__) @@ -26,6 +26,7 @@ class DownholePump(Process): crude oil from a reservoir to the surface using a downhole pump. Attributes + ---------- gas_lifting : bool Whether gas lifting is enabled in the field. res_temp : pint.Quantity @@ -56,6 +57,7 @@ class DownholePump(Process): Whether the field is an oil sands mine. Methods + ------- run(analysis) Simulates the DownholePump process to lift crude oil from the reservoir to the surface and calculates the energy consumption and emissions. diff --git a/opgee/processes/drilling.py b/opgee/processes/drilling.py index 14578800..aa2a09cd 100644 --- a/opgee/processes/drilling.py +++ b/opgee/processes/drilling.py @@ -8,12 +8,12 @@ # import numpy as np -from ..units import ureg from ..emissions import EM_LAND_USE from ..energy import EN_DIESEL from ..log import getLogger from ..process import Process from ..stream import Stream +from ..units import ureg _logger = getLogger(__name__) @@ -23,6 +23,7 @@ class Drilling(Process): A class representing the drilling process in a field. Attributes + ---------- fraction_wells_fractured : float The fraction of wells that are fractured. fracture_consumption_tbl : pandas.DataFrame @@ -125,7 +126,6 @@ def get_fracture_constant(self): :return:Array list of const [a, b, c] """ - value = self.pressure_gradient_fracturing tbl = self.fracture_consumption_tbl result = [np.interp(value.m, tbl[col].index, tbl[col].values) for col in ["a", "b", "c"]] diff --git a/opgee/processes/exploration.py b/opgee/processes/exploration.py index 74c7a8c1..a869b22e 100644 --- a/opgee/processes/exploration.py +++ b/opgee/processes/exploration.py @@ -8,10 +8,10 @@ # import math -from ..units import ureg from ..energy import EN_DIESEL from ..log import getLogger from ..process import Process +from ..units import ureg _logger = getLogger(__name__) diff --git a/opgee/processes/gas_dehydration.py b/opgee/processes/gas_dehydration.py index af9d0d69..5e8e6326 100644 --- a/opgee/processes/gas_dehydration.py +++ b/opgee/processes/gas_dehydration.py @@ -8,14 +8,13 @@ # import numpy as np -from ..units import ureg from ..emissions import EM_FUGITIVES -from ..energy import EN_NATURAL_GAS, EN_ELECTRICITY +from ..energy import EN_ELECTRICITY, EN_NATURAL_GAS from ..error import OpgeeException from ..log import getLogger -from ..process import Process -from ..process import run_corr_eqns +from ..process import Process, run_corr_eqns from ..thermodynamics import ChemicalInfo +from ..units import ureg from .shared import get_bounded_value, predict_blower_energy_use _logger = getLogger(__name__) @@ -26,7 +25,8 @@ class GasDehydration(Process): This class represents the gas dehydration process in an oil and gas field. It calculates the energy consumption and emissions related to the gas dehydration process. - Attributes: + Attributes + ---------- gas_dehydration_tbl (DataFrame): A table containing gas dehydration correlations. mol_to_scf (float): Constant to convert moles to standard cubic feet. air_elevation_const (float): Constant used for air elevation correction. @@ -195,7 +195,6 @@ def pseudo_pressure(tau, Tc_over_T, critical_pressure): :param critical_pressure: water critical pressure (unit = "Pa") :return: (flaot) pseudo pressure (unit = "Pa") """ - a1 = -7.85951783 a2 = 1.84408259 a3 = -11.7866497 diff --git a/opgee/processes/gas_gathering.py b/opgee/processes/gas_gathering.py index a4325c87..fc71c7d9 100644 --- a/opgee/processes/gas_gathering.py +++ b/opgee/processes/gas_gathering.py @@ -7,11 +7,12 @@ # See LICENSE.txt for license details. # import math -from ..units import ureg + from ..emissions import EM_FUGITIVES from ..log import getLogger from ..process import Process from ..stream import Stream +from ..units import ureg _logger = getLogger(__name__) diff --git a/opgee/processes/gas_lifting_compressor.py b/opgee/processes/gas_lifting_compressor.py index 0cb9b7ab..80b7b531 100644 --- a/opgee/processes/gas_lifting_compressor.py +++ b/opgee/processes/gas_lifting_compressor.py @@ -6,11 +6,11 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg from ..emissions import EM_FUGITIVES from ..log import getLogger from ..process import Process from ..processes.compressor import Compressor +from ..units import ureg from .shared import get_energy_carrier _logger = getLogger(__name__) diff --git a/opgee/processes/gas_partition.py b/opgee/processes/gas_partition.py index 230a9f81..40cfbf01 100644 --- a/opgee/processes/gas_partition.py +++ b/opgee/processes/gas_partition.py @@ -6,17 +6,15 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg from ..combine_streams import combine_streams -from ..core import STP -from ..core import TemperaturePressure -from ..error import OpgeeException +from ..core import STP, TemperaturePressure from ..energy import EN_NATURAL_GAS -from ..import_export import N2, CO2_Flooding, NATURAL_GAS +from ..error import OpgeeException +from ..import_export import N2, NATURAL_GAS, CO2_Flooding from ..log import getLogger from ..process import Process from ..stream import PHASE_GAS, Stream - +from ..units import ureg from .shared import get_init_lifting_stream _logger = getLogger(__name__) @@ -203,7 +201,6 @@ def gas_flooding_setup(self, import_product, reinjected_gas_stream, exported_gas :raises: OpgeeException if flood_gas_type is not in known gas types ("N2", "NG", "CO2") :return: None """ - field = self.field known_types = ["N2", "NG", "CO2"] diff --git a/opgee/processes/gas_reinjection_compressor.py b/opgee/processes/gas_reinjection_compressor.py index 76ec6fe7..83adf2f6 100644 --- a/opgee/processes/gas_reinjection_compressor.py +++ b/opgee/processes/gas_reinjection_compressor.py @@ -6,11 +6,11 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg from ..emissions import EM_FUGITIVES from ..energy import EN_ELECTRICITY from ..log import getLogger from ..process import Process +from ..units import ureg from .compressor import Compressor from .shared import get_energy_carrier diff --git a/opgee/processes/heavy_oil_upgrading.py b/opgee/processes/heavy_oil_upgrading.py index 4d35efa2..872502f4 100644 --- a/opgee/processes/heavy_oil_upgrading.py +++ b/opgee/processes/heavy_oil_upgrading.py @@ -8,15 +8,14 @@ # import pandas as pd -from ..units import ureg from ..core import STP from ..emissions import EM_FLARING -from ..energy import EN_NATURAL_GAS, EN_ELECTRICITY, EN_UPG_PROC_GAS, EN_PETCOKE +from ..energy import EN_ELECTRICITY, EN_NATURAL_GAS, EN_PETCOKE, EN_UPG_PROC_GAS from ..import_export import ELECTRICITY, H2 from ..log import getLogger from ..process import Process -from ..stream import PHASE_GAS -from ..stream import Stream +from ..stream import PHASE_GAS, Stream +from ..units import ureg _logger = getLogger(__name__) diff --git a/opgee/processes/post_storage_compressor.py b/opgee/processes/post_storage_compressor.py index 529f48ae..32825319 100644 --- a/opgee/processes/post_storage_compressor.py +++ b/opgee/processes/post_storage_compressor.py @@ -6,11 +6,11 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from .compressor import Compressor -from .shared import get_energy_carrier from ..emissions import EM_FUGITIVES from ..log import getLogger from ..process import Process +from .compressor import Compressor +from .shared import get_energy_carrier _logger = getLogger(__name__) diff --git a/opgee/processes/pre_membrane_chiller.py b/opgee/processes/pre_membrane_chiller.py index 625b77c0..90664ce3 100644 --- a/opgee/processes/pre_membrane_chiller.py +++ b/opgee/processes/pre_membrane_chiller.py @@ -6,11 +6,11 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg from ..emissions import EM_FUGITIVES from ..energy import EN_ELECTRICITY from ..log import getLogger from ..process import Process +from ..units import ureg _logger = getLogger(__name__) diff --git a/opgee/processes/pre_membrane_compressor.py b/opgee/processes/pre_membrane_compressor.py index 64797abf..c69a40c4 100644 --- a/opgee/processes/pre_membrane_compressor.py +++ b/opgee/processes/pre_membrane_compressor.py @@ -6,11 +6,11 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg from ..emissions import EM_FUGITIVES from ..log import getLogger from ..process import Process from ..processes.compressor import Compressor +from ..units import ureg from .shared import get_energy_carrier _logger = getLogger(__name__) diff --git a/opgee/processes/reservoir_well_interface.py b/opgee/processes/reservoir_well_interface.py index 191d9a3a..8a7d9da2 100644 --- a/opgee/processes/reservoir_well_interface.py +++ b/opgee/processes/reservoir_well_interface.py @@ -8,11 +8,11 @@ # import numpy as np -from ..units import ureg -from ..core import TemperaturePressure, STP +from ..core import STP, TemperaturePressure from ..log import getLogger from ..process import Process from ..stream import PHASE_GAS +from ..units import ureg _logger = getLogger(__name__) # data logging diff --git a/opgee/processes/ryan_holmes.py b/opgee/processes/ryan_holmes.py index b71f274e..6e443d22 100644 --- a/opgee/processes/ryan_holmes.py +++ b/opgee/processes/ryan_holmes.py @@ -6,12 +6,12 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg from ..emissions import EM_FUGITIVES -from ..energy import EN_NATURAL_GAS, EN_DIESEL +from ..energy import EN_DIESEL, EN_NATURAL_GAS from ..log import getLogger from ..process import Process from ..stream import PHASE_GAS +from ..units import ureg _logger = getLogger(__name__) diff --git a/opgee/processes/separation.py b/opgee/processes/separation.py index f48ee325..195ba1c4 100644 --- a/opgee/processes/separation.py +++ b/opgee/processes/separation.py @@ -12,7 +12,7 @@ from ..log import getLogger from ..process import Process from ..processes.compressor import Compressor -from ..stream import Stream, PHASE_GAS +from ..stream import PHASE_GAS, Stream from .shared import get_energy_carrier, get_energy_consumption_stages _logger = getLogger(__name__) @@ -233,7 +233,6 @@ def compressor_brake_horsepower_of_stages(self, field, gas_stream, gas_compressi :param gas_compression_volume_stages: (float) a list contains gas compression volume for each stages :return: (float) compresssor brake horsepower for each stages """ - temperature_of_stages, pressure_of_stages = self.get_stages_temperature_and_pressure() overall_compression_ratio_stages = [ @@ -243,7 +242,11 @@ def compressor_brake_horsepower_of_stages(self, field, gas_stream, gas_compressi brake_horsepower_of_stages = [] for inlet_temp, inlet_press, (compression_ratio, num_of_compression), gas_compression_volume in zip( - temperature_of_stages, pressure_of_stages, compression_ratio_per_stages, gas_compression_volume_stages + temperature_of_stages, + pressure_of_stages, + compression_ratio_per_stages, + gas_compression_volume_stages, + strict=False, ): work_sum, _, _ = Compressor.get_compressor_work_temp( field, inlet_temp, inlet_press, gas_stream, compression_ratio, num_of_compression diff --git a/opgee/processes/shared.py b/opgee/processes/shared.py index 9454388c..8f11f765 100644 --- a/opgee/processes/shared.py +++ b/opgee/processes/shared.py @@ -6,10 +6,10 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg -from ..energy import EN_NATURAL_GAS, EN_ELECTRICITY, EN_DIESEL, EN_RESID +from ..energy import EN_DIESEL, EN_ELECTRICITY, EN_NATURAL_GAS, EN_RESID from ..error import OpgeeException -from ..stream import Stream, PHASE_GAS +from ..stream import PHASE_GAS, Stream +from ..units import ureg _slope = {"NG_engine": -0.6035, "NG_turbine": -0.1279} @@ -47,7 +47,6 @@ def get_init_lifting_stream(gas, lifting_gas_stream, gas_lifting_vol_rate): :param gas: (Gas) the current Field's ``Gas`` instance :return: (Stream) initial gas lifting stream """ - lifting_gas_mass_fracs = gas.component_mass_fractions(gas.component_molar_fractions(lifting_gas_stream)) series = ( diff --git a/opgee/processes/sour_gas_compressor.py b/opgee/processes/sour_gas_compressor.py index 7a38f787..801f7351 100644 --- a/opgee/processes/sour_gas_compressor.py +++ b/opgee/processes/sour_gas_compressor.py @@ -6,11 +6,11 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg from ..emissions import EM_FUGITIVES from ..log import getLogger from ..process import Process from ..processes.compressor import Compressor +from ..units import ureg from .shared import get_energy_carrier _logger = getLogger(__name__) diff --git a/opgee/processes/steam_generation.py b/opgee/processes/steam_generation.py index 43066a4f..64a53009 100644 --- a/opgee/processes/steam_generation.py +++ b/opgee/processes/steam_generation.py @@ -6,13 +6,13 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg from ..core import TemperaturePressure -from ..energy import EN_NATURAL_GAS, EN_ELECTRICITY +from ..energy import EN_ELECTRICITY, EN_NATURAL_GAS from ..error import BalanceError from ..import_export import WATER from ..log import getLogger from ..process import Process +from ..units import ureg from .shared import get_energy_consumption _logger = getLogger(__name__) @@ -229,7 +229,6 @@ def check_balance(self, input, output, label): :param label: :return: """ - unit = input.units if abs(input.m - output.to(unit).m) > tolerance * input.m: raise BalanceError(self.name, label) diff --git a/opgee/processes/steam_generator.py b/opgee/processes/steam_generator.py index e195e80b..501fe6e9 100644 --- a/opgee/processes/steam_generator.py +++ b/opgee/processes/steam_generator.py @@ -8,9 +8,9 @@ # import pandas as pd -from ..units import ureg from ..core import OpgeeObject from ..stream import PHASE_GAS +from ..units import ureg class SteamGenerator(OpgeeObject): # N.B. NOT a subclass of Process @@ -117,7 +117,6 @@ def once_through_SG( :return: Tuple with fuel consumption for steam generation (MJ/day), mass flow rate in (kg/day), mass flow rate out (kg/day), energy flow rate in (MJ/day), and energy flow rate out (MJ/day) """ - ( prod_water_enthalpy_rate, makeup_water_enthalpy_rate, @@ -382,7 +381,7 @@ def solar_SG(self, prod_water_mass_rate, makeup_water_mass_rate): @staticmethod def get_combustion_comp(coeff_table, gas_comp): """ - calculate reaction gas comp using combustion table and gas comp + Calculate reaction gas comp using combustion table and gas comp :param coeff_table: :param gas_comp: @@ -405,7 +404,6 @@ def get_LHV_fuel_and_steam_series(self, comp_series, temp_series, fuel_MW, comp_ :param comp_series_MW: DataFrame, exhaust composition molar weight :return: Tuple with LHV fuel series and LHV steam series """ - LHV_fuel = pd.Series(dtype="pint[joule/gram]") LHV_stream = pd.Series(dtype="pint[joule/gram]") @@ -424,7 +422,6 @@ def get_air_requirement(self, gas_MW_combust, SG_type): :param SG_type: :return: (float, Pandas.Series) air_requirement_fuel; (float) air_requirement_LHV_fuel (unit = MJ/kg) """ - liquid_fuel_comp = self.oil.liquid_fuel_composition(self.field.attr("API")) if SG_type == "OTSG": @@ -530,17 +527,16 @@ def get_HRSG_inlet_combustion(self, inlet_temp, gas_MW_combust, gas_LHV, exhaust if counter >= max_iter: raise ValueError("Maximum number of iterations reached in HRSG inlet combustion calculation") - else: - HRSG_inlet = HRSG_inlet.drop(labels=["C1"]) - - return ( - HRSG_inlet, - HRSG_inlet_sum, - HRSG_inlet_MW, - HRSG_inlet_LHV_fuel, - HRSG_inlet_LHV_stream, - duct_additional_fuel, - ) + HRSG_inlet = HRSG_inlet.drop(labels=["C1"]) + + return ( + HRSG_inlet, + HRSG_inlet_sum, + HRSG_inlet_MW, + HRSG_inlet_LHV_fuel, + HRSG_inlet_LHV_stream, + duct_additional_fuel, + ) def get_water_steam_enthalpy_rate( self, prod_water_mass_rate, makeup_water_mass_rate, water_mass_rate_for_injection, blowdown_water_mass_rate @@ -554,7 +550,6 @@ def get_water_steam_enthalpy_rate( :param blowdown_water_mass_rate: :return: """ - prod_water_enthalpy_rate = self.water.enthalpy_PT( self.prod_water_inlet_press, self.field.attr("prod_water_inlet_temp"), prod_water_mass_rate ) @@ -601,7 +596,6 @@ def get_combustion_parameters(self, SG_type): :param SG_type: (str) steam generator type such as "OTSG" and "HRSG" :return: """ - processed_prod_gas_comp = self.processed_prod_gas_comp exported_gas_stream = self.field.get_process_data("exported_gas") if exported_gas_stream and exported_gas_stream.total_flow_rate().m != 0.0: diff --git a/opgee/processes/transmission_compressor.py b/opgee/processes/transmission_compressor.py index 94b3a4db..263264c2 100644 --- a/opgee/processes/transmission_compressor.py +++ b/opgee/processes/transmission_compressor.py @@ -8,12 +8,12 @@ # import math -from .compressor import Compressor -from .shared import get_energy_carrier from ..core import TemperaturePressure from ..emissions import EM_FUGITIVES from ..log import getLogger from ..process import Process +from .compressor import Compressor +from .shared import get_energy_carrier _logger = getLogger(__name__) diff --git a/opgee/processes/transport_energy.py b/opgee/processes/transport_energy.py index 640a521f..9f7c46c3 100644 --- a/opgee/processes/transport_energy.py +++ b/opgee/processes/transport_energy.py @@ -8,10 +8,10 @@ # import pandas as pd -from ..units import ureg from ..core import OpgeeObject -from ..error import OpgeeException from ..energy import EN_DIESEL +from ..error import OpgeeException +from ..units import ureg class TransportEnergy(OpgeeObject): @@ -151,7 +151,6 @@ def get_ocean_tanker_dest_energy_intensity(self, parameter_table): :param parameter_table: DataFrame with transport parameters :return: Energy intensity for ocean tankers from origin to destination (unit: btu/tonne/mile) """ - field = self.field parameter_dict = TransportEnergy.get_parameter_dict(parameter_table) @@ -200,7 +199,6 @@ def transport_energy_intensity( :param hp: (pint.Quantity, optional) Horsepower of the transport, required for energy consumption calculation. :return: (float) Transport energy intensity. """ - residual_oil_LHV = self.residual_oil_LHV residual_oil_density = self.residual_oil_density diff --git a/opgee/processes/venting.py b/opgee/processes/venting.py index 6ffd3128..b4792c26 100644 --- a/opgee/processes/venting.py +++ b/opgee/processes/venting.py @@ -6,11 +6,11 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg -from ..emissions import EM_VENTING, EM_FUGITIVES +from ..emissions import EM_FUGITIVES, EM_VENTING from ..log import getLogger from ..process import Process from ..stream import Stream +from ..units import ureg _logger = getLogger(__name__) diff --git a/opgee/processes/water_injection.py b/opgee/processes/water_injection.py index bbbcb47f..7318eadd 100644 --- a/opgee/processes/water_injection.py +++ b/opgee/processes/water_injection.py @@ -15,11 +15,11 @@ # import numpy as np -from .shared import get_energy_carrier, get_energy_consumption from ..error import OpgeeException from ..log import getLogger from ..process import Process from ..units import ureg +from .shared import get_energy_carrier, get_energy_consumption _logger = getLogger(__name__) diff --git a/opgee/processes/water_treatment.py b/opgee/processes/water_treatment.py index 5790c257..8c7d2c8f 100644 --- a/opgee/processes/water_treatment.py +++ b/opgee/processes/water_treatment.py @@ -6,13 +6,13 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from ..units import ureg from ..core import TemperaturePressure from ..energy import EN_ELECTRICITY from ..error import OpgeeException from ..import_export import WATER from ..log import getLogger from ..process import Process +from ..units import ureg _logger = getLogger(__name__) @@ -39,12 +39,10 @@ def __init__(self, name, **kwargs): field = self.field if field.steam_flooding: - self._required_outputs.extend( - [ - "makeup water", - "produced water", - ] - ) + self._required_outputs.extend([ + "makeup water", + "produced water", + ]) if field.water_flooding or field.water_reinjection: self._required_outputs.append("water") @@ -167,11 +165,10 @@ def run(self, analysis): prod_water_elec = self.get_water_treatment_elec(self.water_treatment_table, input_water_volume_rate) if self.makeup_water_treatment_tbl: makeup_water_table = self.water_treatment_table + elif self.makeup_water_treatment is None: + raise OpgeeException("no makeup water table provided") else: - if self.makeup_water_treatment is None: - raise OpgeeException("no makeup water table provided") - else: - makeup_water_table = self.makeup_water_treatment + makeup_water_table = self.makeup_water_treatment makeup_water_elec = self.get_water_treatment_elec(makeup_water_table, makeup_water_vol_downstream) energy_use_prod, emissions_prod = self.intermediate_results["Produced Water"] diff --git a/opgee/stream.py b/opgee/stream.py index 4a343684..a66f621c 100644 --- a/opgee/stream.py +++ b/opgee/stream.py @@ -13,13 +13,13 @@ import pint import pint_pandas # noqa: F401 -from .units import ureg, magnitude from .attributes import AttributeMixin -from .core import XmlInstantiable, elt_name, TemperaturePressure -from .error import OpgeeException, ModelValidationError +from .core import TemperaturePressure, XmlInstantiable, elt_name +from .error import ModelValidationError, OpgeeException from .log import getLogger from .table_manager import TableManager -from .utils import getBooleanXML, coercible +from .units import magnitude, ureg +from .utils import coercible, getBooleanXML _logger = getLogger(__name__) @@ -321,7 +321,7 @@ def flow_rate(self, name, phase): def total_flow_rate(self): """ - total mass flow rate + Total mass flow rate :return: """ @@ -457,7 +457,7 @@ def set_solid_flow_rate(self, name, rate, tp=None): def set_rates_from_series(self, series, phase, upper_bound_stream=None): """ - set rates from pandas series given phase given the upper bound stream + Set rates from pandas series given phase given the upper bound stream :param series: :param phase: @@ -535,7 +535,6 @@ def copy_gas_rates_from(self, stream, tp=None, API=None): the source stream's API is copied. :return: none """ - if stream.is_uninitialized(): raise OpgeeException(f"Can't copy from uninitialized stream: {stream}") @@ -648,8 +647,7 @@ def contains(self, stream_type, regex=False): """ if regex: return any(re.fullmatch(stream_type, name) for name in self.contents) - else: - return stream_type in self.contents + return stream_type in self.contents @classmethod def from_xml(cls, elt, parent=None): diff --git a/opgee/subcommand.py b/opgee/subcommand.py index 4c26d468..b8cc18a3 100644 --- a/opgee/subcommand.py +++ b/opgee/subcommand.py @@ -14,7 +14,7 @@ def clean_help(s): return " ".join(map(lambda s: s.strip(), lines)) -class SubcommandABC(object): +class SubcommandABC: """ Abstract base class for sub-commands. Defines the protocol expected by ``gt`` for defining sub-commands. Plugin files should be named ``'*_plugin.py'`` and must diff --git a/opgee/table_manager.py b/opgee/table_manager.py index 1c34e998..7fbcbc8a 100644 --- a/opgee/table_manager.py +++ b/opgee/table_manager.py @@ -18,7 +18,7 @@ _logger = getLogger(__name__) -class TableDef(object): +class TableDef: """ Holds meta-data for built-in tables (CSV files loaded into `pandas.DataFrames`). """ @@ -99,8 +99,7 @@ def get_table(self, name, raiseError=True): except KeyError: if raiseError: raise OpgeeException(f"Unknown table '{name}'") - else: - return None + return None relpath = f"tables/{name}.csv" s = resourceStream(relpath, stream_type="text") diff --git a/opgee/table_update.py b/opgee/table_update.py index 8ad1ce2f..bdb8892a 100644 --- a/opgee/table_update.py +++ b/opgee/table_update.py @@ -6,7 +6,7 @@ # Copyright (c) 2021-2022 The Board of Trustees of the Leland Stanford Junior University. # See LICENSE.txt for license details. # -from .core import elt_name, XmlInstantiable, OpgeeObject +from .core import OpgeeObject, XmlInstantiable, elt_name class Cell(OpgeeObject): diff --git a/opgee/thermodynamics.py b/opgee/thermodynamics.py index 761bee5d..c80536ed 100644 --- a/opgee/thermodynamics.py +++ b/opgee/thermodynamics.py @@ -8,16 +8,16 @@ # import math -import pandas as pd import numpy as np +import pandas as pd import pint from pyXSteam.XSteam import XSteam from thermosteam import Chemical, IdealMixture -from .units import ureg -from .core import OpgeeObject, STP, TemperaturePressure +from .core import STP, OpgeeObject, TemperaturePressure from .error import ModelValidationError from .stream import PHASE_GAS, PHASE_LIQUID, PHASE_SOLID, Stream +from .units import ureg class ChemicalInfo(OpgeeObject): @@ -143,7 +143,7 @@ def Cp(component, kelvin, with_units=True): # return chemical.H('g', T=temp_K) def Enthalpy(component, kelvin, phase=PHASE_GAS, with_units=True): """ - calculate enthalpy of component given temperature and phase + Calculate enthalpy of component given temperature and phase :param phase: :param component: @@ -379,7 +379,6 @@ def _gas_specific_gravity(self): :return: (float) gas specific gravity (unit = fraction) """ - gas_SG = self.total_molar_weight / self.dry_air.mol_weight return gas_SG.to("frac") @@ -416,7 +415,6 @@ def API_from_SG(SG): :param SG: :return: """ - SG = SG.to("frac").m if isinstance(SG, pint.Quantity) else SG result = 141.5 / SG - 131.5 return ureg.Quantity(result, "degAPI") @@ -581,7 +579,7 @@ def isothermal_compressibility(oil_specific_gravity): def formation_volume_factor(self, stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio): """ - the formation volume factor is defined as the ratio of the volume of oil (plus the gas in solution) + The formation volume factor is defined as the ratio of the volume of oil (plus the gas in solution) at the prevailing reservoir temperature and pressure to the volume of oil at standard conditions :return:(float) final formation volume factor (unit = fraction) @@ -590,7 +588,7 @@ def formation_volume_factor(self, stream, oil_specific_gravity, gas_specific_gra result = ( self.saturated_formation_volume_factor(stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio) - if stream.tp.P < p_bubblepoint + if p_bubblepoint > stream.tp.P else self.unsat_formation_volume_factor(stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio) ) return result @@ -627,7 +625,6 @@ def volume_flow_rate(self, stream, oil_specific_gravity, gas_specific_gravity, g :return:(float) oil volume flow rate (unit = bbl/day) """ - mass_flow_rate = stream.liquid_flow_rate("oil") density = self.density(stream, oil_specific_gravity, gas_specific_gravity, gas_oil_ratio) @@ -711,7 +708,7 @@ def specific_heat(API, temperature): @staticmethod def liquid_fuel_composition(API): """ - calculate Carbon, Hydrogen, Sulfur, Nitrogen mol per crude oil + Calculate Carbon, Hydrogen, Sulfur, Nitrogen mol per crude oil reference: Fuel Specs, Table Crude oil chemical composition :return:(float) liquid fuel composition (unit = mol/kg) @@ -766,14 +763,13 @@ def total_molar_flow_rate(self, stream): def molar_flow_rate(self, stream, name): """ - get molar flow rate from stream + Get molar flow rate from stream :param stream: :param name: :return: (float) molar flow rate (unit = mol/day) """ - mass_flow_rate = stream.gas_flow_rate(name) molar_flow_rate = (mass_flow_rate / self.component_MW[name]).to("mol/day") @@ -781,14 +777,13 @@ def molar_flow_rate(self, stream, name): def molar_flow_rates(self, stream): """ - get molar flow rate from stream + Get molar flow rate from stream :param stream: :param name: :return: (float) molar flow rate (unit = mol/day) """ - return pd.Series({name: self.molar_flow_rate(stream, name) for name in stream.component_names}) def component_molar_fraction(self, name, stream): @@ -815,7 +810,6 @@ def component_molar_fractions(self, stream, index=None): :return:(float) Panda Series component molar fractions """ - total_molar_flow_rate = self.total_molar_flow_rate(stream) gas_flow_rates = stream.gas_flow_rates(index) @@ -833,7 +827,7 @@ def component_molar_fractions(self, stream, index=None): def component_mass_fractions(self, molar_fracs): """ - generate mass fractions from molar fractions + Generate mass fractions from molar fractions :param molar_fracs: @@ -994,7 +988,8 @@ def Z_factor(reduced_temperature, reduced_pressure): reduced_temperature (pint.Quantity): Reduced temperature as a Pint Quantity object, defined as T/Tc (ratio of temperature to critical temperature). reduced_pressure (pint.Quantity): Reduced pressure as a Pint Quantity object, defined as P/Pc (ratio of pressure to critical pressure). - Returns: + Returns + ------- pint.Quantity: Compressibility factor (Z) as a Pint Quantity object (dimensionless fraction) for the given reduced temperature and reduced pressure. """ @@ -1031,7 +1026,6 @@ def volume_factor(self, stream): :return: """ - z_factor = self.Z_factor(self.reduced_temperature(stream), self.reduced_pressure(stream)) stream_T = stream.tp.T.to("rankine") stream_P = stream.tp.P @@ -1154,7 +1148,7 @@ def mass_energy_density(self, stream): def mass_energy_density_from_molar_fracs(self, molar_fracs): """ - calculate gas mass energy density from series + Calculate gas mass energy density from series :param molar_fracs: :return: (float) gas mass energy density (unit = MJ/kg) @@ -1170,14 +1164,13 @@ def mass_energy_density_from_molar_fracs(self, molar_fracs): @staticmethod def combustion_enthalpy(molar_fracs, temperature, phase): """ - calculate OTSG/HRSG combustion enthalpy + Calculate OTSG/HRSG combustion enthalpy :param molar_fracs: :param temperature: :return: """ - enthalpy = pd.Series( {name: Enthalpy(name, temperature, phase=phase, with_units=False) for name in molar_fracs.index}, dtype="pint[joule/mole]", @@ -1248,11 +1241,10 @@ def __init__(self, field): def density(self, temperature=None, pressure=None): """ - water density + Water density :return: (float) water density (unit = kg/m3) """ - temp = temperature if temperature is not None else self.model.const("std-temperature") press = pressure if pressure is not None else self.model.const("std-pressure") @@ -1308,13 +1300,12 @@ def heat_capacity(cls, stream): @staticmethod def saturated_temperature(saturated_pressure): """ - calculate water saturated temperature given the saturated pressure + Calculate water saturated temperature given the saturated pressure :param saturated_pressure: :return: (float) water saturated temperature (unit = degF) """ - psat = saturated_pressure.to("Pa").m saturated_temp = Tsat("H2O", psat, with_units=True) @@ -1322,7 +1313,7 @@ def saturated_temperature(saturated_pressure): def enthalpy_PT(self, pressure, temperature, mass_rate): """ - calculate water enthalpy given pressure and temperature + Calculate water enthalpy given pressure and temperature :param pressure: :param temperature: @@ -1343,7 +1334,7 @@ def enthalpy_PT(self, pressure, temperature, mass_rate): def steam_enthalpy(self, pressure, steam_quality, mass_rate): """ - calculate steam enthalpy from steam quality + Calculate steam enthalpy from steam quality :param pressure: :param steam_quality: diff --git a/opgee/tool.py b/opgee/tool.py index a457ed4a..a54f83e6 100644 --- a/opgee/tool.py +++ b/opgee/tool.py @@ -13,16 +13,16 @@ import sys from glob import glob -from .config import pathjoin, getParam, getConfig, getParamAsBoolean, setParam, getSection, setSection -from .error import OpgeeException, CommandlineError -from .log import setLogLevels, configureLogs +from .config import getConfig, getParam, getParamAsBoolean, getSection, pathjoin, setParam, setSection +from .error import CommandlineError, OpgeeException +from .log import configureLogs, setLogLevels from .subcommand import clean_help from .version import VERSION PROGRAM = "opg" -class Opgee(object): +class Opgee: # plugin instances by command name _plugins = {} diff --git a/opgee/units.py b/opgee/units.py index be17529c..785d162e 100644 --- a/opgee/units.py +++ b/opgee/units.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final import pint from pint.registry import ApplicationRegistry @@ -10,7 +10,7 @@ _logger = getLogger(__name__) # "shadowed" variable here to improve type hinting for `ureg` -_ureg: Optional[ApplicationRegistry] = None +_ureg: ApplicationRegistry | None = None if _ureg is None: _ureg = pint.get_application_registry() @@ -69,5 +69,4 @@ def magnitude(value, units=None): raise OpgeeException(f"magnitude: value {value} units are not {units}") return value.m - else: - return value + return value diff --git a/opgee/utils.py b/opgee/utils.py index 081b8a1a..263061cd 100644 --- a/opgee/utils.py +++ b/opgee/utils.py @@ -229,8 +229,7 @@ def coercible(value, pytype, raiseError=True): except (TypeError, ValueError): if raiseError: raise OpgeeException("%s: %r is not coercible to %s" % (getFuncName(1), value, pytype)) - else: - return None + return None return value diff --git a/opgee/xml_utils.py b/opgee/xml_utils.py index ca6c09af..9c7d245f 100644 --- a/opgee/xml_utils.py +++ b/opgee/xml_utils.py @@ -27,8 +27,8 @@ def str_to_xml(s): def save_xml(path, root, backup=False, overwrite=False): - from pathlib import Path import os + from pathlib import Path if path: p = Path(path) @@ -51,8 +51,8 @@ def save_xml(path, root, backup=False, overwrite=False): def _load_opgee_template(template): from .config import getParam - from .pkg_utils import resourceStream from .model_file import XMLFile + from .pkg_utils import resourceStream opgee_xml = getParam("OPGEE.ModelFile") base_stream = resourceStream(opgee_xml, stream_type="bytes", decode=None) @@ -73,8 +73,8 @@ def _find_proc_in_agg(process_name, aggs): def attr_to_xml(fields, dtypes, xml_path, analysis_name, modifies="default"): - from lxml import etree as ET import numpy as np + from lxml import etree as ET known_types = {"int": int, "float": float, "str": str} diff --git a/tests/BROKEN_test_smart_defaults.py b/tests/BROKEN_test_smart_defaults.py index b392ec87..b094cff3 100644 --- a/tests/BROKEN_test_smart_defaults.py +++ b/tests/BROKEN_test_smart_defaults.py @@ -1,9 +1,8 @@ from opgee.analysis import Analysis from opgee.core import A from opgee.field import Field -from opgee.mcs.simulation import Simulation +from opgee.mcs.simulation import Distribution, Simulation from opgee.smart_defaults import SmartDefault -from opgee.mcs.simulation import Distribution @SmartDefault.register("TEST_abcdef", ["TEST_foo", "TEST_bar"]) diff --git a/tests/conftest.py b/tests/conftest.py index ffd75951..89e9bdb8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,10 @@ import pytest + from opgee.config import getConfig -from opgee.log import setLogLevels, configureLogs +from opgee.log import configureLogs, setLogLevels from opgee.model_file import ModelFile from opgee.tool import Opgee + from .utils_for_tests import load_test_model @@ -12,7 +14,6 @@ def configure_logging_for_tests(): getConfig() setLogLevels("ERROR") configureLogs(force=True) - return None @pytest.fixture(scope="function") diff --git a/tests/test_attr_constraints.py b/tests/test_attr_constraints.py index d2bbb6cb..a9714eb9 100644 --- a/tests/test_attr_constraints.py +++ b/tests/test_attr_constraints.py @@ -1,5 +1,7 @@ import pytest + from opgee.error import OpgeeException + from .utils_for_tests import load_test_model diff --git a/tests/test_attributes.py b/tests/test_attributes.py index ede450b1..98b90e7c 100644 --- a/tests/test_attributes.py +++ b/tests/test_attributes.py @@ -1,11 +1,12 @@ import pytest from lxml import etree as ET -from opgee.units import ureg + from opgee.analysis import Analysis -from opgee.attributes import ClassAttrs, AttributeMixin, AttrDefs +from opgee.attributes import AttrDefs, AttributeMixin, ClassAttrs from opgee.core import instantiate_subelts -from opgee.error import OpgeeException, AttributeError +from opgee.error import AttributeError, OpgeeException from opgee.model import Model +from opgee.units import ureg @pytest.fixture diff --git a/tests/test_audit.py b/tests/test_audit.py index 91313bc9..2249bef5 100644 --- a/tests/test_audit.py +++ b/tests/test_audit.py @@ -1,12 +1,12 @@ import os from pathlib import Path from unittest.mock import patch -import pytest import pandas as pd +import pytest from opgee.audit import AuditRow, _generate_field_audit_report, audit_field -from opgee.config import setParam, getConfig +from opgee.config import getConfig, setParam from opgee.error import OpgeeException from opgee.model_file import ModelFile from tests.utils_for_tests import path_to_test_file @@ -18,7 +18,7 @@ def audit_model_file(configure_logging_for_tests): mf = ModelFile(model_path, use_default_model=True) - yield mf + return mf def find_audit_row(report_data: list[AuditRow], attr_name: str) -> AuditRow: @@ -30,7 +30,6 @@ def find_audit_row(report_data: list[AuditRow], attr_name: str) -> AuditRow: def test_audit_source_input(audit_model_file: ModelFile): """Verify explicitly set attributes have source='input'.""" - model = audit_model_file.model field = model.get_field("audit-field") diff --git a/tests/test_boundary.py b/tests/test_boundary.py index 01a0b274..145a810a 100644 --- a/tests/test_boundary.py +++ b/tests/test_boundary.py @@ -1,5 +1,7 @@ import pytest + from opgee.process import Process + from .utils_for_tests import load_test_model diff --git a/tests/test_boundary_procs.py b/tests/test_boundary_procs.py index 3ebd1935..e35e405a 100644 --- a/tests/test_boundary_procs.py +++ b/tests/test_boundary_procs.py @@ -1,5 +1,7 @@ import pytest + from opgee.process import Process + from .utils_for_tests import load_test_model diff --git a/tests/test_coeff.py b/tests/test_coeff.py index 99433a65..592d6dfc 100644 --- a/tests/test_coeff.py +++ b/tests/test_coeff.py @@ -1,4 +1,5 @@ import pandas as pd + from opgee.process import run_corr_eqns diff --git a/tests/test_comparison.py b/tests/test_comparison.py index 2bb3b2e4..407db7f9 100644 --- a/tests/test_comparison.py +++ b/tests/test_comparison.py @@ -1,5 +1,6 @@ from io import StringIO -from opgee.built_ins.compare_plugin import compare, ComparisonStatus + +from opgee.built_ins.compare_plugin import ComparisonStatus, compare results1 = """ process,field_1,field_2,foobar,bumble diff --git a/tests/test_config.py b/tests/test_config.py index 114a0694..9f528ae2 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,19 +1,22 @@ import os + import pytest + from opgee.config import ( - unixPath, - getHomeDir, - pathjoin, + USR_CONFIG_FILE, getConfig, + getHomeDir, getParam, getParamAsBoolean, - getParamAsInt, getParamAsFloat, + getParamAsInt, + pathjoin, setSection, - USR_CONFIG_FILE, stringTrue, + unixPath, ) -from opgee.error import OpgeeException, ConfigFileError +from opgee.error import ConfigFileError, OpgeeException + from .utils_for_tests import load_config_from_string diff --git a/tests/test_core.py b/tests/test_core.py index ec0080c0..eace3ba9 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,7 +1,8 @@ import pytest -from opgee.units import ureg, magnitude, _undefined_units, validate_unit -from opgee.core import dict_from_list, XmlInstantiable, A -from opgee.error import OpgeeException, AbstractMethodError + +from opgee.core import A, XmlInstantiable, dict_from_list +from opgee.error import AbstractMethodError, OpgeeException +from opgee.units import _undefined_units, magnitude, ureg, validate_unit def test_magnitude_error(): diff --git a/tests/test_csv2xml.py b/tests/test_csv2xml.py index e68135fb..5e947568 100644 --- a/tests/test_csv2xml.py +++ b/tests/test_csv2xml.py @@ -1,6 +1,8 @@ import os -from opgee.tool import opg + from opgee.model_file import ModelFile +from opgee.tool import opg + from .utils_for_tests import path_to_test_file, tempdir diff --git a/tests/test_distributed_mcs.py b/tests/test_distributed_mcs.py index 142e7ccc..76315861 100644 --- a/tests/test_distributed_mcs.py +++ b/tests/test_distributed_mcs.py @@ -1,4 +1,5 @@ import re + from opgee.field import FieldResult diff --git a/tests/test_emissions.py b/tests/test_emissions.py index 0cd7e573..eda9a765 100644 --- a/tests/test_emissions.py +++ b/tests/test_emissions.py @@ -1,8 +1,9 @@ -import pytest import pandas as pd -from opgee.units import ureg -from opgee.emissions import Emissions, EM_FUGITIVES, EM_FLARING, EM_LAND_USE, EmissionsError +import pytest + +from opgee.emissions import EM_FLARING, EM_FUGITIVES, EM_LAND_USE, Emissions, EmissionsError from opgee.error import OpgeeException +from opgee.units import ureg def test_set_rate(): diff --git a/tests/test_energy.py b/tests/test_energy.py index 62b46642..3db36a27 100644 --- a/tests/test_energy.py +++ b/tests/test_energy.py @@ -1,8 +1,9 @@ -from opgee.units import ureg -from opgee.energy import Energy, EN_DIESEL, EN_NATURAL_GAS, EN_RESID, EN_PETCOKE, EN_CRUDE_OIL, EN_ELECTRICITY -from opgee.error import OpgeeException import pytest +from opgee.energy import EN_CRUDE_OIL, EN_DIESEL, EN_ELECTRICITY, EN_NATURAL_GAS, EN_PETCOKE, EN_RESID, Energy +from opgee.error import OpgeeException +from opgee.units import ureg + def test_set_rate(): e = Energy() diff --git a/tests/test_field.py b/tests/test_field.py index 7d29b65c..bdf689d5 100644 --- a/tests/test_field.py +++ b/tests/test_field.py @@ -1,9 +1,10 @@ import pytest -from opgee.units import ureg -from .utils_for_tests import load_model_from_str + from opgee.error import XmlFormatError -from .utils_for_tests import load_test_model +from opgee.units import ureg + from .test_processes import approx_equal +from .utils_for_tests import load_model_from_str, load_test_model model_xml_1 = """ diff --git a/tests/test_import_export.py b/tests/test_import_export.py index 46e54d7d..b4648b39 100644 --- a/tests/test_import_export.py +++ b/tests/test_import_export.py @@ -1,5 +1,5 @@ +from opgee.import_export import ELECTRICITY, NATURAL_GAS, ImportExport from opgee.units import ureg -from opgee.import_export import ImportExport, ELECTRICITY, NATURAL_GAS def test_import(): diff --git a/tests/test_impute.py b/tests/test_impute.py index e26a05ae..9e09c8c5 100644 --- a/tests/test_impute.py +++ b/tests/test_impute.py @@ -1,8 +1,10 @@ import pytest + from opgee.process import Process -from .utils_for_tests import load_test_model from opgee.units import ureg +from .utils_for_tests import load_test_model + class CopyingProcess(Process): def run(self, analysis): diff --git a/tests/test_intermediate_boundary.py b/tests/test_intermediate_boundary.py index 9408838e..1b4a6598 100644 --- a/tests/test_intermediate_boundary.py +++ b/tests/test_intermediate_boundary.py @@ -1,8 +1,9 @@ import pytest -from opgee.units import ureg + +from opgee.combine_streams import combine_streams from opgee.model_file import ModelFile from opgee.process import Process -from opgee.combine_streams import combine_streams +from opgee.units import ureg xml_string = """ diff --git a/tests/test_mcs.py b/tests/test_mcs.py index 47698b2d..3d8b205f 100644 --- a/tests/test_mcs.py +++ b/tests/test_mcs.py @@ -1,13 +1,14 @@ +from io import StringIO from pathlib import Path + import pytest -from io import StringIO from opgee.error import McsUserError -from opgee.mcs.simulation import read_distributions, Simulation, Distribution from opgee.mcs.parameter_list import ParameterList +from opgee.mcs.simulation import Distribution, Simulation, read_distributions from opgee.tool import opg -from .utils_for_tests import tmpdir, path_to_test_file +from .utils_for_tests import path_to_test_file, tmpdir def test_distro_xml(): diff --git a/tests/test_merge_xml.py b/tests/test_merge_xml.py index 7a53fbc9..685a6d36 100644 --- a/tests/test_merge_xml.py +++ b/tests/test_merge_xml.py @@ -1,10 +1,13 @@ -import pytest import os -from lxml import etree as ET from tempfile import mkdtemp -from .utils_for_tests import path_to_test_file -from opgee.xml_utils import merge_siblings, str_to_xml + +import pytest +from lxml import etree as ET + from opgee.tool import opg +from opgee.xml_utils import merge_siblings, str_to_xml + +from .utils_for_tests import path_to_test_file def assert_same_xml(x1, x2): diff --git a/tests/test_model.py b/tests/test_model.py index f90c26f7..e34ef38d 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1,6 +1,8 @@ import pytest + from opgee.error import OpgeeException from opgee.stream import Stream + from .utils_for_tests import load_test_model, path_to_test_file diff --git a/tests/test_molecule_names.py b/tests/test_molecule_names.py index 11b0b719..828313c9 100644 --- a/tests/test_molecule_names.py +++ b/tests/test_molecule_names.py @@ -1,6 +1,7 @@ import pytest + from opgee.error import OpgeeException -from opgee.stream import molecule_to_carbon, carbon_to_molecule +from opgee.stream import carbon_to_molecule, molecule_to_carbon parameterize_args = ["c_name, m_name", [("C1", "CH4"), ("C2", "C2H6"), ("C5", "C5H12")]] diff --git a/tests/test_packet.py b/tests/test_packet.py index f365ce09..4368eb08 100644 --- a/tests/test_packet.py +++ b/tests/test_packet.py @@ -1,4 +1,5 @@ import pytest + from opgee.manager import FieldPacket, TrialPacket, _batched diff --git a/tests/test_post_proc_plugin.py b/tests/test_post_proc_plugin.py index e9dd678a..7c5f1926 100644 --- a/tests/test_post_proc_plugin.py +++ b/tests/test_post_proc_plugin.py @@ -1,4 +1,5 @@ import os + import pytest from opgee.constants import DETAILED_RESULT @@ -82,7 +83,7 @@ def test_missing_subclass(): PostProcessor.load_plugin(path) -@pytest.mark.skip() +@pytest.mark.skip def test_cmd_line_post_proc(opgee_main): PostProcessor.decache() plugin_path = path_to_test_file("simple_post_processor.py") @@ -149,7 +150,7 @@ def test_auto_loading(opgee_main): assert os.path.exists(csv_file) -@pytest.mark.skip() +@pytest.mark.skip def test_no_auto_loading(opgee_main): from opgee.config import setParam diff --git a/tests/test_process_groups.py b/tests/test_process_groups.py index d26022e7..655c9cf8 100644 --- a/tests/test_process_groups.py +++ b/tests/test_process_groups.py @@ -1,5 +1,7 @@ import pytest + from opgee.error import OpgeeException + from .utils_for_tests import load_test_model diff --git a/tests/test_process_loop.py b/tests/test_process_loop.py index b124ca5d..71a0084e 100644 --- a/tests/test_process_loop.py +++ b/tests/test_process_loop.py @@ -1,6 +1,7 @@ -from opgee.units import ureg -from opgee.stream import Stream, PHASE_LIQUID from opgee.process import Process +from opgee.stream import PHASE_LIQUID, Stream +from opgee.units import ureg + from .utils_for_tests import load_test_model diff --git a/tests/test_processes.py b/tests/test_processes.py index 65c92b8d..b879c9eb 100644 --- a/tests/test_processes.py +++ b/tests/test_processes.py @@ -1,10 +1,11 @@ import pandas as pd import pytest -from opgee.units import ureg -from opgee.energy import EN_NATURAL_GAS, EN_CRUDE_OIL + from opgee.emissions import EM_FLARING +from opgee.energy import EN_CRUDE_OIL, EN_NATURAL_GAS from opgee.error import OpgeeException, ZeroEnergyFlowError -from opgee.process import Process, _get_subclass, Reservoir +from opgee.process import Process, Reservoir, _get_subclass +from opgee.units import ureg class NotProcess: @@ -38,7 +39,7 @@ def test_set_emission_rates(test_model_with_change): df = procA.get_emission_rates(analysis) rates = df[EM_FLARING] - assert rates.N2O == rate_n2o and rates.CH4 == rate_ch4 and rates.CO2 == rate_co2 + assert rate_n2o == rates.N2O and rate_ch4 == rates.CH4 and rate_co2 == rates.CO2 def test_add_energy_rates(test_model_with_change): @@ -702,7 +703,7 @@ def test_WaterTreatment(test_model): # TODO: RP created this test. Wennan should improve it! def test_CrudeOilTransport(): - from .utils_for_tests import path_to_test_file, load_test_model + from .utils_for_tests import load_test_model, path_to_test_file model2 = load_test_model("test_model2.xml", class_path=path_to_test_file("user_processes.py")) diff --git a/tests/test_run_subcmd.py b/tests/test_run_subcmd.py index f2a525be..16426f87 100644 --- a/tests/test_run_subcmd.py +++ b/tests/test_run_subcmd.py @@ -1,17 +1,19 @@ -from glob import glob import os +from glob import glob + import pytest -from opgee.config import setParam, pathjoin + +from opgee.config import pathjoin, setParam from opgee.error import CommandlineError from opgee.post_processor import PostProcessor from opgee.tool import opg + from .utils_for_tests import path_to_test_file, tempdir @pytest.fixture(autouse=True) def decache_post_plugins(): PostProcessor.decache() - yield def test_missing_output_dir(opgee_main): diff --git a/tests/test_smart_defaults.py b/tests/test_smart_defaults.py index ab468155..75f095ea 100644 --- a/tests/test_smart_defaults.py +++ b/tests/test_smart_defaults.py @@ -1,4 +1,5 @@ from opgee.units import ureg + from .utils_for_tests import load_model_from_str template = """ @@ -40,11 +41,11 @@ def test_model_1(configure_logging_for_tests): model = model_for_country("USA") field = model.get_field("test") T = field.attr("prod_water_inlet_temp") - assert T == ureg.Quantity(140, "degF") + assert ureg.Quantity(140, "degF") == T def test_model_2(configure_logging_for_tests): model = model_for_country("Canada") field = model.get_field("test") T = field.attr("prod_water_inlet_temp") - assert T == ureg.Quantity(340, "degF") + assert ureg.Quantity(340, "degF") == T diff --git a/tests/test_stream.py b/tests/test_stream.py index fe30c26f..a0781860 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -1,4 +1,5 @@ import pytest + from opgee.error import OpgeeException from opgee.process import Process from opgee.units import ureg diff --git a/tests/test_table_manager.py b/tests/test_table_manager.py index 434e6223..1ff02a8e 100644 --- a/tests/test_table_manager.py +++ b/tests/test_table_manager.py @@ -1,7 +1,9 @@ import pytest + from opgee.energy import EN_NATURAL_GAS, EN_NGL from opgee.error import OpgeeException from opgee.table_manager import TableManager + from .utils_for_tests import path_to_test_file diff --git a/tests/test_thermofunction.py b/tests/test_thermofunction.py index 173130ed..f1ee2d0c 100644 --- a/tests/test_thermofunction.py +++ b/tests/test_thermofunction.py @@ -1,7 +1,8 @@ import pandas as pd import pytest + from opgee.core import TemperaturePressure -from opgee.stream import Stream, PHASE_GAS, PHASE_LIQUID +from opgee.stream import PHASE_GAS, PHASE_LIQUID, Stream from opgee.units import ureg diff --git a/tests/test_utils.py b/tests/test_utils.py index c519dda0..86f4668b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,9 +1,11 @@ import os + import pytest -from opgee.units import ureg from opgee.error import OpgeeException -from opgee.utils import getBooleanXML, coercible, mkdirs, loadModuleFromPath, removeTree, parseTrialString +from opgee.units import ureg +from opgee.utils import coercible, getBooleanXML, loadModuleFromPath, mkdirs, parseTrialString, removeTree + from .utils_for_tests import tmpdir diff --git a/tests/test_xml_file.py b/tests/test_xml_file.py index aef514a8..256a2e49 100644 --- a/tests/test_xml_file.py +++ b/tests/test_xml_file.py @@ -1,7 +1,9 @@ import pytest from lxml import etree -from opgee.XMLFile import XMLFile + from opgee.error import XmlFormatError +from opgee.XMLFile import XMLFile + from .utils_for_tests import path_to_test_file diff --git a/tests/utils_for_tests.py b/tests/utils_for_tests.py index 634dfa0a..158ba26a 100644 --- a/tests/utils_for_tests.py +++ b/tests/utils_for_tests.py @@ -1,14 +1,15 @@ from contextlib import contextmanager from io import StringIO -from opgee.config import pathjoin, getParam, setParam, readConfigFile + +from opgee.config import getParam, pathjoin, readConfigFile, setParam from opgee.model_file import ModelFile from opgee.process import Process @contextmanager def tempdir(): - import tempfile import shutil + import tempfile d = tempfile.mkdtemp() try: @@ -87,8 +88,8 @@ def tmpdir(*args): @contextmanager def tempdir(): - import tempfile import shutil + import tempfile d = tempfile.mkdtemp() try: From 91bb7b5f8f894891925661d45347674d8a9b6ac5 Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Tue, 2 Sep 2025 16:40:54 -0600 Subject: [PATCH 6/6] style: more linting/formatting --- opgee/gui/app.py | 146 +++++++++++++++-------------- opgee/gui/results_pane.py | 36 ++++--- opgee/gui/testapp.py | 52 +++++----- opgee/processes/water_treatment.py | 10 +- 4 files changed, 131 insertions(+), 113 deletions(-) diff --git a/opgee/gui/app.py b/opgee/gui/app.py index 8e27f39f..22229d85 100644 --- a/opgee/gui/app.py +++ b/opgee/gui/app.py @@ -23,80 +23,86 @@ def app_layout(app, model, analysis): analysis_names = [analysis.name for analysis in model.analyses()] # noinspection PyCallingNonCallable - layout = html.Div([ - dcc.Store(id="analysis-and-field", storage_type="session"), - # TBD: Experiment to see if client-side function fixes graph resizing problem, per - # https://stackoverflow.com/questions/55462861/dash-dynamic-layout-does-not-propagate-resized-graph-dimensions-until-window-i - html.Div(id="output-clientside"), - html.Div( - [ - html.H1(app.title), - html.Div( - [ - html.Center([ - html.Span("Model: ", style=label_style), - html.Span(f"{model.pathnames}"), - horiz_space, - html.Span("Analysis: ", style=label_style), - dcc.Dropdown( - id="analysis-selector", - placeholder="Select analysis...", - options=[{"value": name, "label": name} for name in analysis_names], - value=analysis.name, - style=pulldown_style, + layout = html.Div( + [ + dcc.Store(id="analysis-and-field", storage_type="session"), + # TBD: Experiment to see if client-side function fixes graph resizing problem, per + # https://stackoverflow.com/questions/55462861/dash-dynamic-layout-does-not-propagate-resized-graph-dimensions-until-window-i + html.Div(id="output-clientside"), + html.Div( + [ + html.H1(app.title), + html.Div( + [ + html.Center( + [ + html.Span("Model: ", style=label_style), + html.Span(f"{model.pathnames}"), + horiz_space, + html.Span("Analysis: ", style=label_style), + dcc.Dropdown( + id="analysis-selector", + placeholder="Select analysis...", + options=[{"value": name, "label": name} for name in analysis_names], + value=analysis.name, + style=pulldown_style, + ), + horiz_space, + html.Span("Field: ", style=label_style), + dcc.Dropdown( + id="field-selector", + placeholder="Select field...", + options=[{"value": "none", "label": "none"}], + value="none", + style=pulldown_style, + ), + ] ), - horiz_space, - html.Span("Field: ", style=label_style), - dcc.Dropdown( - id="field-selector", - placeholder="Select field...", - options=[{"value": "none", "label": "none"}], - value="none", - style=pulldown_style, - ), - ]), - html.Br(), - html.Button("Run model", id="run-button", n_clicks=0), - dcc.Markdown(id="run-model-status"), - ], - # style = {'height': '130px'} - ), - ], - style={"textAlign": "center"}, - ), - html.Div([ - dcc.Tabs( - id="tabs", - value="processes", - parent_className="custom-tabs", - className="custom-tabs-container", - children=[ - dcc.Tab( - children=[], # see processes_layout() - label="Processes", - value="processes", - className="custom-tab", - selected_className="custom-tab--selected", - ), - dcc.Tab( - children=[], # see settings_layout() - label="Settings", - value="settings", - className="custom-tab", - selected_className="custom-tab--selected", - ), - dcc.Tab( - children=[], # see results_layout() - label="Results", - value="results", - className="custom-tab", - selected_className="custom-tab--selected", + html.Br(), + html.Button("Run model", id="run-button", n_clicks=0), + dcc.Markdown(id="run-model-status"), + ], + # style = {'height': '130px'} ), ], + style={"textAlign": "center"}, ), - html.Div(id="tab-content"), - ]), - ]) + html.Div( + [ + dcc.Tabs( + id="tabs", + value="processes", + parent_className="custom-tabs", + className="custom-tabs-container", + children=[ + dcc.Tab( + children=[], # see processes_layout() + label="Processes", + value="processes", + className="custom-tab", + selected_className="custom-tab--selected", + ), + dcc.Tab( + children=[], # see settings_layout() + label="Settings", + value="settings", + className="custom-tab", + selected_className="custom-tab--selected", + ), + dcc.Tab( + children=[], # see results_layout() + label="Results", + value="results", + className="custom-tab", + selected_className="custom-tab--selected", + ), + ], + ), + html.Div(id="tab-content"), + ] + ), + ] + ) return layout diff --git a/opgee/gui/results_pane.py b/opgee/gui/results_pane.py index 54e8b1c9..c0d7c9d9 100644 --- a/opgee/gui/results_pane.py +++ b/opgee/gui/results_pane.py @@ -31,11 +31,13 @@ def get_layout(self, field, **kwargs): className="row", # style={'textAlign': "center"}, ), - html.Center([ - dcc.Graph(id="ci-barchart", style=barchart_style), - horiz_space, - dcc.Graph(id="energy-barchart", style=barchart_style), - ]), + html.Center( + [ + dcc.Graph(id="ci-barchart", style=barchart_style), + horiz_space, + dcc.Graph(id="energy-barchart", style=barchart_style), + ] + ), ], className="row", style={ # 'display': 'flex', @@ -82,11 +84,13 @@ def ci_barchart(ci_text, analysis_and_field): fn_unit = analysis.fn_unit.title() - df = pd.DataFrame({ - "category": [pair[0] for pair in top_level], - "value": [pair[1] for pair in top_level], - "unit": [fn_unit] * len(top_level), - }) + df = pd.DataFrame( + { + "category": [pair[0] for pair in top_level], + "value": [pair[1] for pair in top_level], + "unit": [fn_unit] * len(top_level), + } + ) fig = go.Figure( data=[ @@ -127,11 +131,13 @@ def energy_barchart(n_clicks, ci_figure, analysis_and_field): # Show results for top-level aggregators and procs for the selected field that are within the boundary top_level = [(obj.name, obj.energy.data.sum() / energy) for obj in field.children() if obj not in beyond] - df = pd.DataFrame({ - "category": [pair[0] for pair in top_level], - "value": [pair[1] for pair in top_level], - "unit": [fn_unit] * len(top_level), - }) + df = pd.DataFrame( + { + "category": [pair[0] for pair in top_level], + "value": [pair[1] for pair in top_level], + "unit": [fn_unit] * len(top_level), + } + ) fig = go.Figure( data=[ diff --git a/opgee/gui/testapp.py b/opgee/gui/testapp.py index 4394d924..20b260ba 100644 --- a/opgee/gui/testapp.py +++ b/opgee/gui/testapp.py @@ -8,30 +8,34 @@ def app_layout(app): label_style = {"font-weight": "bold"} # noinspection PyCallingNonCallable - layout = html.Div([ - dcc.Store(id="analysis-and-field", storage_type="session"), - # TBD: Experiment to see if client-side function fixes graph resizing problem, per - # https://stackoverflow.com/questions/55462861/dash-dynamic-layout-does-not-propagate-resized-graph-dimensions-until-window-i - # html.Div(id="output-clientside"), - html.Div( - [ - html.H1(app.title), - html.Div( - [ - html.Center([ - html.Span("Model: ", style=label_style), - html.Span("Not a real model"), - ]), - html.Br(), - html.Button("Run model", id="run-button", n_clicks=0), - dcc.Markdown(id="run-model-status"), - ], - # style = {'height': '130px'} - ), - ], - style={"textAlign": "center"}, - ), - ]) + layout = html.Div( + [ + dcc.Store(id="analysis-and-field", storage_type="session"), + # TBD: Experiment to see if client-side function fixes graph resizing problem, per + # https://stackoverflow.com/questions/55462861/dash-dynamic-layout-does-not-propagate-resized-graph-dimensions-until-window-i + # html.Div(id="output-clientside"), + html.Div( + [ + html.H1(app.title), + html.Div( + [ + html.Center( + [ + html.Span("Model: ", style=label_style), + html.Span("Not a real model"), + ] + ), + html.Br(), + html.Button("Run model", id="run-button", n_clicks=0), + dcc.Markdown(id="run-model-status"), + ], + # style = {'height': '130px'} + ), + ], + style={"textAlign": "center"}, + ), + ] + ) return layout diff --git a/opgee/processes/water_treatment.py b/opgee/processes/water_treatment.py index 8c7d2c8f..a89decc6 100644 --- a/opgee/processes/water_treatment.py +++ b/opgee/processes/water_treatment.py @@ -39,10 +39,12 @@ def __init__(self, name, **kwargs): field = self.field if field.steam_flooding: - self._required_outputs.extend([ - "makeup water", - "produced water", - ]) + self._required_outputs.extend( + [ + "makeup water", + "produced water", + ] + ) if field.water_flooding or field.water_reinjection: self._required_outputs.append("water")