From ea671915e105cee4252e7efb0aae742ad7ced36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98ystein=20Olai=20Heggen?= Date: Thu, 15 Aug 2019 14:30:11 +0200 Subject: [PATCH] Refactor cli interface, make all modes available --- .gitignore | 1 + ert_gui/__init__.py | 3 +- ert_gui/cli.py | 329 ++++++++++-------- ert_gui/ert_adapter.py | 24 ++ ert_gui/ertnotifier.py | 12 +- ert_gui/main.py | 182 +++++++--- .../simulation/models/ensemble_experiment.py | 2 +- .../models/multiple_data_assimilation.py | 2 +- ert_gui/simulation/run_dialog.py | 4 +- tests/global/test_cli.py | 122 ++++++- tests/global/test_main.py | 180 ++++++---- 11 files changed, 578 insertions(+), 283 deletions(-) create mode 100644 ert_gui/ert_adapter.py diff --git a/.gitignore b/.gitignore index fe211d1ab6d..5a4dbc99a13 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *.user.* .idea .vscode +.pytest_cache python/lib64 /libenkf/src/.faultlist /develbranch/libenkf/src/.faultlist diff --git a/ert_gui/__init__.py b/ert_gui/__init__.py index fe5b9a99356..9c03fe13c62 100644 --- a/ert_gui/__init__.py +++ b/ert_gui/__init__.py @@ -22,5 +22,6 @@ def headless(): except ImportError: __version__ = '0.0.0' -from .ertnotifier import ERT +from .ert_adapter import ERT from .ertnotifier import configureErtNotifier +from .cli import run_cli diff --git a/ert_gui/cli.py b/ert_gui/cli.py index e065096c02b..bf197ea01f9 100644 --- a/ert_gui/cli.py +++ b/ert_gui/cli.py @@ -1,157 +1,182 @@ #!/usr/bin/env python +import logging +import os import sys +from argparse import ArgumentTypeError -from res.enkf import EnKFMain, ResConfig, ESUpdate, ErtRunContext -from res.enkf.enums import RealizationStateEnum, HookRuntime from ecl.util.util import BoolVector -import res -from res.util import ResLog - -import logging - - -def setup_fs(ert, target="default"): - fs_manager = ert.getEnkfFsManager() - - src_fs = fs_manager.getCurrentFileSystem() - tar_fs = fs_manager.getFileSystem(target) - - return src_fs, tar_fs - - -def resconfig(config_file): - return ResConfig(user_config_file=config_file) - - -def _test_run(ert): - source_fs, _ = setup_fs(ert) - - model_config = ert.getModelConfig() - subst_list = ert.getDataKW() - - single_mask = BoolVector(default_value=False) - single_mask[0] = True - - run_context = ErtRunContext.ensemble_experiment( - sim_fs=source_fs, - mask=single_mask, - path_fmt=model_config.getRunpathFormat(), - jobname_fmt=model_config.getJobnameFormat(), - subst_list=subst_list, - itr=0) - - sim_runner = ert.getEnkfSimulationRunner() - _run_ensemble_experiment(ert, run_context, sim_runner) - - -def _experiment_run(ert): - source_fs, _ = setup_fs(ert) - - model_config = ert.getModelConfig() - subst_list = ert.getDataKW() - - mask = BoolVector(default_value=True, initial_size=ert.getEnsembleSize()) - - run_context = ErtRunContext.ensemble_experiment( - sim_fs=source_fs, - mask=mask, - path_fmt=model_config.getRunpathFormat(), - jobname_fmt=model_config.getJobnameFormat(), - subst_list=subst_list, - itr=0) - - sim_runner = ert.getEnkfSimulationRunner() - _run_ensemble_experiment(ert, run_context, sim_runner) - - -def _ensemble_smoother_run(ert, target_case): - source_fs, target_fs = setup_fs(ert, target_case) - - model_config = ert.getModelConfig() - subst_list = ert.getDataKW() - - mask = BoolVector(default_value=True, initial_size=ert.getEnsembleSize()) - - prior_context = ErtRunContext.ensemble_smoother( - sim_fs=source_fs, - target_fs=target_fs, - mask=mask, - path_fmt=model_config.getRunpathFormat(), - jobname_fmt=model_config.getJobnameFormat(), - subst_list=subst_list, - itr=0) - - sim_runner = ert.getEnkfSimulationRunner() - _run_ensemble_experiment(ert, prior_context, sim_runner) - sim_runner.runWorkflows( HookRuntime.PRE_UPDATE ) - - es_update = ESUpdate(ert) - success = es_update.smootherUpdate(prior_context) - if not success: - raise AssertionError("Analysis of simulation failed!") - - sim_runner.runWorkflows( HookRuntime.POST_UPDATE ) - - ert.getEnkfFsManager().switchFileSystem(prior_context.get_target_fs()) - - sim_fs = prior_context.get_target_fs( ) - state = RealizationStateEnum.STATE_HAS_DATA | RealizationStateEnum.STATE_INITIALIZED - mask = sim_fs.getStateMap().createMask(state) - - rerun_context = ErtRunContext.ensemble_smoother( - sim_fs=sim_fs, - target_fs=None, - mask=mask, - path_fmt=model_config.getRunpathFormat(), - jobname_fmt=model_config.getJobnameFormat(), - subst_list=subst_list, - itr=1) - - _run_ensemble_experiment(ert, rerun_context, sim_runner) - - -def _run_ensemble_experiment(ert, run_context, sim_runner): - sim_runner.createRunPath(run_context) - sim_runner.runWorkflows(HookRuntime.PRE_SIMULATION) - - job_queue = ert.get_queue_config().create_job_queue() - num_successful_realizations = sim_runner.runEnsembleExperiment(job_queue, run_context) - _assert_minium_realizations_success(ert, num_successful_realizations) - - print("{} of the realizations were successful".format(num_successful_realizations)) - sim_runner.runWorkflows( HookRuntime.POST_SIMULATION ) - - -def _assert_minium_realizations_success(ert, num_successful_realizations): - if num_successful_realizations == 0: - raise AssertionError("Simulation failed! All realizations failed!") - elif not ert.analysisConfig().haveEnoughRealisations(num_successful_realizations, ert.getEnsembleSize()): - raise AssertionError("Too many simulations have failed! You can add/adjust MIN_REALIZATIONS to allow failures in your simulations.\n\n" - "Check ERT log file '%s' or simulation folder for details." % ResLog.getFilename()) - - -def main(): - # The ert_cli script should be called from ert.in. The arguments are parsed and verified in ert.in - if len(sys.argv) < 3: - raise AssertionError("Required arguments are missing, the config-file, " - "mode and target case must be provided") - - config_file, mode, target_case = sys.argv[1:] - - config = resconfig(config_file) - ert = EnKFMain(config) - - if not ert._real_enkf_main().have_observations() and mode == 'ensemble_smoother': - logging.error("No observations loaded. Unable to perform model update.") - return - - if mode == "test_run": - _test_run(ert) - if mode == "ensemble_experiment": - _experiment_run(ert) - if mode == "ensemble_smoother": - _ensemble_smoother_run(ert, target_case) - +from res.enkf import EnKFMain, ErtRunContext, ESUpdate, ResConfig + +from ert_gui import ERT +from ert_gui.ertwidgets.models import ertmodel +from ert_gui.ide.keywords.definitions import (NumberListStringArgument, + RangeStringArgument) +from ert_gui.simulation.models.ensemble_experiment import EnsembleExperiment +from ert_gui.simulation.models.ensemble_smoother import EnsembleSmoother +from ert_gui.simulation.models.iterated_ensemble_smoother import \ + IteratedEnsembleSmoother +from ert_gui.simulation.models.multiple_data_assimilation import \ + MultipleDataAssimilation +from ert_gui.simulation.models.single_test_run import SingleTestRun + + +def run_cli(args): + + res_config = ResConfig(args.config) + os.chdir(res_config.config_path) + ert = EnKFMain(res_config, strict=True, verbose=args.verbose) + notifier = ErtCliNotifier(ert, args.config) + ERT.adapt(notifier) + + # Setup model + if args.mode == 'test_run': + model, argument = _setup_single_test_run() + elif args.mode == 'ensemble_experiment': + model, argument = _setup_ensemble_experiment(args) + elif args.mode == 'ensemble_smoother': + model, argument = _setup_ensemble_smoother(args) + elif args.mode == 'es_mda': + model, argument = _setup_multiple_data_assimilation(args) + elif args.mode == 'iterated_ensemble_smoother': + model, argument = _setup_iterated_ensemble_smoother(args) + else: + raise NotImplementedError( + "Run type not supported {}".format(args.mode)) + + model.runSimulations(argument) + + +def _setup_single_test_run(): + model = SingleTestRun() + simulations_argument = { + "active_realizations": BoolVector(default_value=True, initial_size=1), + } + return model, simulations_argument + + +def _setup_ensemble_experiment(args): + + model = EnsembleExperiment() + simulations_argument = { + "active_realizations": _realizations(args), + } + return model, simulations_argument + + +def _setup_ensemble_smoother(args): + model = EnsembleSmoother() + + simulations_argument = { + "active_realizations": _realizations(args), + "target_case": _target_case_name(args, format_mode=False) + } + return model, simulations_argument + + +def _setup_multiple_data_assimilation(args): + model = MultipleDataAssimilation() + iterable = True + active_name = ERT.ert.analysisConfig().activeModuleName() + modules = ertmodel.getAnalysisModuleNames(iterable=iterable) + simulations_argument = { + "active_realizations": _realizations(args), + "target_case": _target_case_name(args, format_mode=True), + "analysis_module": _get_analysis_module_name(active_name, modules, iterable=False), + "weights": args.weights + } + return model, simulations_argument + + +def _setup_iterated_ensemble_smoother(args): + if args.iterations is not None: + ertmodel.setNumberOfIterations(args.iterations) + + model = IteratedEnsembleSmoother() + iterable = True + active_name = ERT.ert.analysisConfig().activeModuleName() + modules = ertmodel.getAnalysisModuleNames(iterable=iterable) + simulations_argument = { + "active_realizations": _realizations(args), + "target_case": _target_case_name(args, format_mode=True), + "analysis_module": _get_analysis_module_name(active_name, modules, iterable=iterable) + } + return model, simulations_argument + + +def _get_analysis_module_name(active_name, modules, iterable): + + if active_name in modules: + return active_name + elif "STD_ENKF" in modules and not iterable: + return "STD_ENKF" + elif "RML_ENKF" in modules and iterable: + return "RML_ENKF" + elif len(modules) > 0: + return modules[0] + + return None + + +def _realizations(args): + ensemble_size = ERT.ert.getEnsembleSize() + mask = BoolVector(default_value=False, initial_size=ensemble_size) + if args.realizations is None: + default = "0-{}".format(ensemble_size - 1) + mask.updateActiveMask(default) + return mask + + validator = RangeStringArgument(ensemble_size) + validated = validator.validate(args.realizations) + if validated.failed(): + raise ArgumentTypeError( + "Defined realizations is not within range of ensemble size: {}".format(args.realizations)) + mask.updateActiveMask(args.realizations) + return mask + + +def _target_case_name(args, format_mode=False): + """ @rtype: str """ + if args.target_case is not None: + return args.target_case + + if not format_mode: + case_name = ertmodel.getCurrentCaseName() + return "{}_smoother_update".format(case_name) -if __name__ == "__main__": - main() + aic = ERT.ert.analysisConfig().getAnalysisIterConfig() + if aic.caseFormatSet(): + return aic.getCaseFormat() + + case_name = ertmodel.getCurrentCaseName() + return "{}_%d".format(case_name) + + +class ErtCliNotifier(): + + def __init__(self, ert, config_file): + self._ert = ert + self._config_file = config_file + + @property + def ert(self): + """ @rtype: EnKFMain """ + if self._ert is None: + raise ValueError("Ert is undefined.") + return self._ert + + @property + def config_file(self): + """ @rtype: str """ + if self._ert is None: + raise ValueError("Ert is undefined.") + return self._config_file + + @property + def ertChanged(self): + pass + + def emitErtChange(self): + pass + + def reloadERT(self, config_file): + pass diff --git a/ert_gui/ert_adapter.py b/ert_gui/ert_adapter.py new file mode 100644 index 00000000000..0c0133298d0 --- /dev/null +++ b/ert_gui/ert_adapter.py @@ -0,0 +1,24 @@ +class ErtAdapter(): + + def adapt(self, implementation): + self._implementation = implementation + + @property + def ertChanged(self): + return self._implementation.ertChanged + + @property + def ert(self): + return self._implementation.ert + + @property + def config_file(self): + return self._implementation.config_file + + def emitErtChange(self): + self._implementation.emitErtChange() + + def reloadERT(self, config_file): + self._implementation.reloadERT(config_file) + +ERT = ErtAdapter() diff --git a/ert_gui/ertnotifier.py b/ert_gui/ertnotifier.py index b6965f7939f..06704283d3b 100644 --- a/ert_gui/ertnotifier.py +++ b/ert_gui/ertnotifier.py @@ -6,7 +6,7 @@ from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot from res.enkf import EnKFMain - +from ert_gui import ERT class ErtNotifier(QObject): ertChanged = pyqtSignal() @@ -48,10 +48,6 @@ def reloadERT(self, config_file): self._ert = None os.execl(python_executable, python_executable, ert_gui_main, config_file) - -ERT = ErtNotifier(None, None) -""" @type: ErtNotifier """ - -def configureErtNotifier(ert, config_file): - ERT._ert = ert - ERT._config_file = config_file +def configureErtNotifier(ert, config_file): + notifier = ErtNotifier(ert, config_file) + ERT.adapt(notifier) diff --git a/ert_gui/main.py b/ert_gui/main.py index ba8e59675e8..6cf95867d57 100644 --- a/ert_gui/main.py +++ b/ert_gui/main.py @@ -2,6 +2,10 @@ import os import sys from argparse import ArgumentParser, ArgumentTypeError +from ert_gui import run_cli +from ert_gui import ERT +from ert_gui.ide.keywords.definitions import RangeStringArgument, ProperNameFormatArgument, NumberListStringArgument +from ert_gui.simulation.models.multiple_data_assimilation import MultipleDataAssimilation def valid_file(fname): @@ -10,29 +14,59 @@ def valid_file(fname): return fname -def runExec(executable, args): - os.execvp(executable, [executable] + args) +def valid_realizations(user_input): + validator = RangeStringArgument() + validated = validator.validate(user_input) + if validated.failed(): + raise ArgumentTypeError( + "Defined realizations is not of correct format: {}".format(user_input)) + return user_input -def runGui(args): - runExec("python", ["-m", "ert_gui.gert_main"] + [args.config]) +def valid_weights(user_input): + validator = NumberListStringArgument() + validated = validator.validate(user_input) + if validated.failed(): + raise ArgumentTypeError( + "Defined weights is not of correct format: {}".format(user_input)) + + return user_input -def runCli(args): - runExec("python", ["-m", "ert_gui.cli"] + - [args.config, args.mode, args.target_case]) +def valid_name_format(user_input): + validator = ProperNameFormatArgument() + validated = validator.validate(user_input) + if validated.failed(): + raise ArgumentTypeError( + "Defined name is not of correct format: {}".format(user_input)) + return user_input -def validate_cli_args(parser, args): - if args.mode == "ensemble_smoother" and args.target_case == "default": +def valid_name_format_not_default(user_input): + if user_input == 'default': msg = "Target file system and source file system can not be the same. "\ - "They were both: . Please set using --target-case on "\ - "the command line." - parser.error(msg) + "They were both: ." + raise ArgumentTypeError(msg) + valid_name_format(user_input) + return user_input -def ert_parser(): - parser = ArgumentParser(description="ERT - Ensemble Reservoir Tool") +def range_limited_int(user_input): + try: + i = int(user_input) + except ValueError: + raise ArgumentTypeError("Must be a int") + if 0 < i < 100: + return i + raise ArgumentTypeError("Range must be in range 1 - 99") + + +def runGui(args): + os.execvp("python", ["python"] + + ["-m", "ert_gui.gert_main"] + [args.config]) + + +def ert_parser(parser, args): subparsers = parser.add_subparsers( title="Available user entries", @@ -41,37 +75,103 @@ def ert_parser(): 'interfaces. Note that different entry points may require ' 'different additional arguments. See the help section for ' 'each interface for more details.', - help="Available entry points", - dest="interface") - - gui_parser = subparsers.add_parser('gui', - help='Graphical User Interface - opens up an independent window for ' - 'the user to interact with ERT.', - description='Graphical User Interface') - gui_parser.add_argument('config', type=valid_file, - help="Ert configuration file") + help="Available entry points", dest="mode") + + parser.add_argument('config', type=valid_file, + help="Ert configuration file") + + # gui_parser + gui_parser = subparsers.add_parser('gui', help='opens up an independent graphical user interface for ' + 'the user to interact with ERT.') gui_parser.set_defaults(func=runGui) - cli_parser = subparsers.add_parser('cli', - help='Command Line Interface - provides a user interface in the terminal.', - description="Command Line Interface") - cli_parser.add_argument('config', type=valid_file, - help="Ert configuration file") - cli_parser.add_argument("--mode", type=str, required=True, - choices=["test_run", "ensemble_experiment", - "ensemble_smoother"], - help="The available modes") - cli_parser.add_argument("--target-case", type=str, default="default", - help="The target filesystem to store results") - cli_parser.set_defaults(func=runCli) - return parser + # test_run_parser + test_run_parser = subparsers.add_parser( + 'test_run', help="run 'test_run' in cli") + test_run_parser.add_argument('--verbose', action='store_true', + help="Show verbose output", default=False) + test_run_parser.set_defaults(func=run_cli) + + # ensemble_experiment_parser + ensemble_experiment_parser = subparsers.add_parser('ensemble_experiment', + help="run simulations in cli without performing any updates on the parameters.", + description='') + ensemble_experiment_parser.add_argument('--verbose', action='store_true', + help="Show verbose output", default=False) + ensemble_experiment_parser.add_argument('--realizations', type=valid_realizations, + help="These are the realizations that will be used to perform simulations." + "For example, if 'Number of realizations:50 and Active realizations is 0-9', " + "then only realizations 0,1,2,3,...,9 will be used to perform simulations " + "while realizations 10,11, 12,...,49 will be excluded") + ensemble_experiment_parser.set_defaults(func=run_cli) + + # ensemble_smoother_parser + ensemble_smoother_parser = subparsers.add_parser('ensemble_smoother', + help="run simulations in cli while performing one update on the " + "parameters by using the ensemble smoother algorithm") + ensemble_smoother_parser.add_argument('--target-case', type=valid_name_format_not_default, required=True, + help="This is the name of the case where the results for the " + "updated parameters will be stored") + ensemble_smoother_parser.add_argument('--verbose', action='store_true', + help="Show verbose output", default=False) + ensemble_smoother_parser.add_argument('--realizations', type=valid_realizations, + help="These are the realizations that will be used to perform simulations." + "For example, if 'Number of realizations:50 and Active realizations is 0-9', " + "then only realizations 0,1,2,3,...,9 will be used to perform simulations " + "while realizations 10,11, 12,...,49 will be excluded") + ensemble_smoother_parser.set_defaults(func=run_cli) + + # es_mda_parser + es_mda_parser = subparsers.add_parser('es_mda', + help="run 'es_mda' in cli") + es_mda_parser.add_argument('--target-case', type=valid_name_format, + help="The es_mda creates multiple cases for the different " + "iterations. The case names will follow the specified format. " + "For example, 'Target case format: iter_%%d' will generate " + "cases with the names iter_0, iter_1, iter_2, iter_3, ....") + es_mda_parser.add_argument('--verbose', action='store_true', + help="Show verbose output", default=False) + es_mda_parser.add_argument('--realizations', type=valid_realizations, + help="These are the realizations that will be used to perform simulations." + "For example, if 'Number of realizations:50 and Active realizations is 0-9', " + "then only realizations 0,1,2,3,...,9 will be used to perform simulations " + "while realizations 10,11, 12,...,49 will be excluded") + es_mda_parser.add_argument('--weights', type=valid_weights, default=MultipleDataAssimilation.default_weights, + help="Example Custom Relative Weights: '8,4,2,1'. This means Multiple Data " + "Assimilation Ensemble Smoother will half the weight applied to the " + "Observation Errors from one iteration to the next across 4 iterations.") + es_mda_parser.set_defaults(func=run_cli) + + # iterative_ensemble_smoother_parser + iterative_ensemble_smoother_parser = subparsers.add_parser('iterated_ensemble_smoother', + help="run simulations in cli while performing multiple updates on " + "the parameters by using the iterated ensemble smoother algorithm") + iterative_ensemble_smoother_parser.add_argument('--target-case', type=valid_name_format, + help="The iterated_ensemble_smoother creates multiple cases for the different " + "iterations. The case names will follow the specified format. " + "For example, 'Target case format: iter_%%d' will generate " + "cases with the names iter_0, iter_1, iter_2, iter_3, ....") + iterative_ensemble_smoother_parser.add_argument('--iterations', type=range_limited_int, default=4, + help="Specify the number of times to perform updates/iterations. " + "In general, the more updates the better, however, this could be time consuming. " + "The default value is 4.") + iterative_ensemble_smoother_parser.add_argument('--verbose', action='store_true', + help="Show verbose output", default=False) + iterative_ensemble_smoother_parser.add_argument('--realizations', type=valid_realizations, + help="These are the realizations that will be used to perform simulations." + "For example, if 'Number of realizations:50 and Active realizations is 0-9', " + "then only realizations 0,1,2,3,...,9 will be used to perform simulations " + "while realizations 10,11, 12,...,49 will be excluded") + iterative_ensemble_smoother_parser.set_defaults(func=run_cli) + + return parser.parse_args(args) def main(): - parser = ert_parser() - args = parser.parse_args(sys.argv[1:]) + parser = ArgumentParser(description="ERT - Ensemble Reservoir Tool") + args = ert_parser(parser, sys.argv[1:]) + args.func(args) - if args.interface == "cli": - validate_cli_args(parser, args) - args.func(args) +if __name__ == "__main__": + main() diff --git a/ert_gui/simulation/models/ensemble_experiment.py b/ert_gui/simulation/models/ensemble_experiment.py index c4855573a55..4d6c441e9c5 100644 --- a/ert_gui/simulation/models/ensemble_experiment.py +++ b/ert_gui/simulation/models/ensemble_experiment.py @@ -24,7 +24,7 @@ def runSimulations__(self, arguments, run_msg): num_successful_realizations = self.ert().getEnkfSimulationRunner().runEnsembleExperiment(self._job_queue, run_context) - num_successful_realizations += arguments['prev_successful_realizations'] + num_successful_realizations += arguments.get('prev_successful_realizations', 0) self.checkHaveSufficientRealizations(num_successful_realizations) self.setPhaseName("Post processing...", indeterminate=True) diff --git a/ert_gui/simulation/models/multiple_data_assimilation.py b/ert_gui/simulation/models/multiple_data_assimilation.py index 16b71bdf9b3..acee14edb75 100644 --- a/ert_gui/simulation/models/multiple_data_assimilation.py +++ b/ert_gui/simulation/models/multiple_data_assimilation.py @@ -107,7 +107,7 @@ def _simulateAndPostProcess(self, run_context, arguments): self.setPhaseName(phase_string, indeterminate=False) num_successful_realizations = self.ert().getEnkfSimulationRunner().runSimpleStep(self._job_queue, run_context) - num_successful_realizations += arguments['prev_successful_realizations'] + num_successful_realizations += arguments.get('prev_successful_realizations', 0) self.checkHaveSufficientRealizations(num_successful_realizations) phase_string = "Post processing for iteration: %d" % iteration diff --git a/ert_gui/simulation/run_dialog.py b/ert_gui/simulation/run_dialog.py index 45838c62ea8..b2730ced98f 100644 --- a/ert_gui/simulation/run_dialog.py +++ b/ert_gui/simulation/run_dialog.py @@ -170,9 +170,6 @@ def closeEvent(self, QCloseEvent): def startSimulation(self, arguments): self._simulations_argments = arguments - - if not 'prev_successful_realizations' in self._simulations_argments: - self._simulations_argments['prev_successful_realizations'] = 0 self._run_model.reset() def run(): @@ -337,6 +334,7 @@ def restart_failed_realizations(self): self.done_button.setVisible(False) active_realizations = self.create_mask_from_failed_realizations() self._simulations_argments['active_realizations'] = active_realizations + self._simulations_argments['prev_successful_realizations'] = self._simulations_argments.get('prev_successful_realizations', 0) self._simulations_argments['prev_successful_realizations'] += self.count_successful_realizations() self.startSimulation(self._simulations_argments) diff --git a/tests/global/test_cli.py b/tests/global/test_cli.py index e80d1e43a6b..f861d797bc9 100644 --- a/tests/global/test_cli.py +++ b/tests/global/test_cli.py @@ -2,14 +2,120 @@ from res.test import ErtTestContext import os import subprocess +from res.enkf import EnKFMain, ResConfig +from ecl.util.util import BoolVector +from ert_gui import cli +from ert_gui import ERT +from ert_gui.cli import ErtCliNotifier +from argparse import Namespace class EntryPointTest(ErtTest): - def test_single_realization(self): - config_file = self.createTestPath('local/poly_example/poly.ert') - exec_path = os.path.join(self.SOURCE_ROOT, "ert_gui/bin/ert") - try: - subprocess.check_call([exec_path, "cli", config_file, "--mode", "test_run", - "--target-case", "default"]) - except subprocess.CalledProcessError as e: - self.fail("%s: %s" % (e.__class__.__name__, e)) + + def test_custom_target_case_name(self): + config_file = self.createTestPath('local/poly_example/poly.ert') + with ErtTestContext('test_custom_target_case_name', config_file) as work_area: + ert = work_area.getErt() + notifier = ErtCliNotifier(ert, config_file) + ERT.adapt(notifier) + + custom_name = "test" + args = Namespace(target_case=custom_name) + res = cli._target_case_name(args) + self.assertEqual(custom_name, res) + + def test_default_target_case_name(self): + config_file = self.createTestPath('local/poly_example/poly.ert') + with ErtTestContext('test_default_target_case_name', config_file) as work_area: + ert = work_area.getErt() + notifier = ErtCliNotifier(ert, config_file) + ERT.adapt(notifier) + + args = Namespace(target_case=None) + res = cli._target_case_name(args) + self.assertEqual("default_smoother_update", res) + + def test_default_target_case_name_format_mode(self): + config_file = self.createTestPath('local/poly_example/poly.ert') + with ErtTestContext('test_default_target_case_name_format_mode', config_file) as work_area: + ert = work_area.getErt() + notifier = ErtCliNotifier(ert, config_file) + ERT.adapt(notifier) + + args = Namespace(target_case=None) + res = cli._target_case_name(args, format_mode=True) + self.assertEqual("default_%d", res) + + def test_default_realizations(self): + config_file = self.createTestPath('local/poly_example/poly.ert') + with ErtTestContext('test_default_realizations', config_file) as work_area: + ert = work_area.getErt() + notifier = ErtCliNotifier(ert, config_file) + ERT.adapt(notifier) + + args = Namespace(realizations=None) + res = cli._realizations(args) + ensemble_size = ERT.ert.getEnsembleSize() + mask = BoolVector(default_value=False, initial_size=ensemble_size) + mask.updateActiveMask("0-99") + self.assertEqual(mask, res) + + def test_custom_realizations(self): + config_file = self.createTestPath('local/poly_example/poly.ert') + with ErtTestContext('test_custom_realizations', config_file) as work_area: + ert = work_area.getErt() + notifier = ErtCliNotifier(ert, config_file) + ERT.adapt(notifier) + + args = Namespace(realizations="0-4,7,8") + res = cli._realizations(args) + ensemble_size = ERT.ert.getEnsembleSize() + mask = BoolVector(default_value=False, initial_size=ensemble_size) + mask.updateActiveMask("0-4,7,8") + self.assertEqual(mask, res) + + def test_analysis_module_name_iterable(self): + + active_name = "STD_ENKF" + modules = ["RML_ENKF"] + name = cli._get_analysis_module_name( + active_name, modules, iterable=True) + + self.assertEqual(name, "RML_ENKF") + + def test_analysis_module_name_not_iterable(self): + + active_name = "STD_ENKF" + modules = ['BOOTSTRAP_ENKF', 'CV_ENKF', 'FWD_STEP_ENKF', + 'NULL_ENKF', 'SQRT_ENKF', 'STD_ENKF'] + name = cli._get_analysis_module_name( + active_name, modules, iterable=True) + + self.assertEqual(name, "STD_ENKF") + + def test_analysis_module_name_in_module(self): + + active_name = "STD_ENKF" + modules = ['STD_ENKF'] + name = cli._get_analysis_module_name( + active_name, modules, iterable=True) + + self.assertEqual(name, "STD_ENKF") + + def test_analysis_module_items_in_module(self): + + active_name = "FOO" + modules = ["BAR"] + name = cli._get_analysis_module_name( + active_name, modules, iterable=True) + + self.assertEqual(name, "BAR") + + def test_analysis_module_no_hit(self): + + active_name = "FOO" + modules = [] + name = cli._get_analysis_module_name( + active_name, modules, iterable=True) + + self.assertIsNone(name) diff --git a/tests/global/test_main.py b/tests/global/test_main.py index 73e579f4fd5..8fdf160a03b 100644 --- a/tests/global/test_main.py +++ b/tests/global/test_main.py @@ -2,78 +2,122 @@ import sys import unittest -from ert_gui.main import ert_parser, main - -if sys.version_info >= (3, 3): - from unittest.mock import Mock, patch -else: - from mock import Mock, patch +from ert_gui.main import ert_parser +from argparse import ArgumentParser class MainTest(unittest.TestCase): - def __init__(self, *args, **kwargs): - super(MainTest, self).__init__(*args, **kwargs) - - # argv is inconsequential for this case, as parsing is stubbed out. - # Tests still needs to assert argv was passed, and since it may vary - # (based on environment) it is unset. - sys.argv = list() - - # main is essentially a call to ArgumentParser.parse_args, so it is mocked. - self.arg_parser_mock = None - - # ArgumentParser.parse_args returns a Namespace, which must be mocked as - # parse_args is stubbed out. - self.namespace_mock = None - - def mock_parser(self): - """Returns a mocked ArgumentParser.""" - return self.arg_parser_mock - - def setUp(self): - unittest.TestCase.setUp(self) - - self.namespace_mock = Mock(spec=argparse.Namespace) - self.namespace_mock.func = Mock() - - self.arg_parser_mock = Mock(spec=argparse.ArgumentParser) - self.arg_parser_mock.parse_args = Mock(return_value=self.namespace_mock) - - def test_run_gui(self): - self.namespace_mock.interface = 'gui' - - with patch('ert_gui.main.ert_parser', new=self.mock_parser): - main() - - self.arg_parser_mock.parse_args.assert_called_once_with(sys.argv) - self.namespace_mock.func.assert_called_once_with(self.namespace_mock) - - def test_run_cli_with_valid_arguments(self): - self.namespace_mock.interface = 'cli' - self.namespace_mock.mode = 'ensemble_experiment' - self.namespace_mock.target_case = 'some case' - - with patch('ert_gui.main.ert_parser', new=self.mock_parser): - main() - - self.arg_parser_mock.parse_args.assert_called_once_with(sys.argv) - self.namespace_mock.func.assert_called_once_with(self.namespace_mock) - - def test_run_cli_with_invalid_arguments(self): - self.namespace_mock.interface = 'cli' - self.namespace_mock.mode = 'ensemble_smoother' - self.namespace_mock.target_case = 'default' - self.arg_parser_mock.error = Mock() - - with patch('ert_gui.main.ert_parser', new=self.mock_parser): - main() - - self.arg_parser_mock.parse_args.assert_called_once_with(sys.argv) - self.arg_parser_mock.error.assert_called_once_with("Target file system and source file system can not be the same. " - "They were both: . Please set using --target-case on " - "the command line.") - self.namespace_mock.func.assert_called_once_with(self.namespace_mock) + def test_argparse_exec_gui(self): + parser = ArgumentParser(prog="test_main") + parsed = ert_parser(parser, ['gui', 'test-data/local/poly_example/poly.ert']) + self.assertEquals(parsed.func.__name__, "runGui") + + def test_argparse_exec_test_run_valid_case(self): + parser = ArgumentParser(prog="test_main") + parsed = ert_parser( + parser, ['test_run', "--verbose", 'test-data/local/poly_example/poly.ert']) + self.assertEquals(parsed.mode, "test_run") + self.assertEquals( + parsed.config, "test-data/local/poly_example/poly.ert") + self.assertEquals(parsed.func.__name__, "run_cli") + self.assertTrue(parsed.verbose) + + def test_argparse_exec_ensemble_experiment_valid_case(self): + parser = ArgumentParser(prog="test_main") + parsed = ert_parser(parser, ['ensemble_experiment', "--realizations", "1-4,7,8", + 'test-data/local/poly_example/poly.ert']) + self.assertEquals(parsed.mode, "ensemble_experiment") + self.assertEquals( + parsed.config, "test-data/local/poly_example/poly.ert") + self.assertEquals(parsed.realizations, "1-4,7,8") + self.assertEquals(parsed.func.__name__, "run_cli") + self.assertFalse(parsed.verbose) + + def test_argparse_exec_ensemble_experiment_faulty_realizations(self): + parser = ArgumentParser(prog="test_main") + with self.assertRaises(SystemExit): + ert_parser(parser, ['ensemble_experiment', "--realizations", "1~4,7," + 'test-data/local/poly_example/poly.ert']) + + def test_argparse_exec_ensemble_smoother_valid_case(self): + parser = ArgumentParser(prog="test_main") + parsed = ert_parser(parser, [ + 'ensemble_smoother', "--target-case", "some_case%d", 'test-data/local/poly_example/poly.ert']) + self.assertEquals(parsed.mode, "ensemble_smoother") + self.assertEquals( + parsed.config, "test-data/local/poly_example/poly.ert") + self.assertEquals(parsed.target_case, "some_case%d") + self.assertEquals(parsed.func.__name__, "run_cli") + self.assertFalse(parsed.verbose) + + def test_argparse_exec_ensemble_smoother_default_target_case(self): + parser = ArgumentParser(prog="test_main") + with self.assertRaises(SystemExit): + ert_parser(parser, ['ensemble_smoother', "--target-case", 'default', + 'test-data/local/poly_example/poly.ert']) + + def test_argparse_exec_ensemble_smoother_no_target_case(self): + parser = ArgumentParser(prog="test_main") + with self.assertRaises(SystemExit): + ert_parser(parser, ['ensemble_smoother', + 'test-data/local/poly_example/poly.ert']) + + def test_argparse_exec_es_mda_valid_case(self): + parser = ArgumentParser(prog="test_main") + parsed = ert_parser(parser, ['es_mda', "--target-case", "some_case%d", "--realizations", + "1-10", "--verbose", "--weights", "1, 2, 4", 'test-data/local/poly_example/poly.ert']) + self.assertEquals(parsed.mode, "es_mda") + self.assertEquals( + parsed.config, "test-data/local/poly_example/poly.ert") + self.assertEquals(parsed.target_case, "some_case%d") + self.assertEquals(parsed.realizations, "1-10") + self.assertEquals(parsed.weights, "1, 2, 4") + self.assertEquals(parsed.func.__name__, "run_cli") + self.assertTrue(parsed.verbose) + + def test_argparse_exec_es_mda_default_weights(self): + parser = ArgumentParser(prog="test_main") + parsed = ert_parser( + parser, ['es_mda', 'test-data/local/poly_example/poly.ert']) + self.assertEquals(parsed.mode, "es_mda") + self.assertEquals( + parsed.config, "test-data/local/poly_example/poly.ert") + self.assertEquals(parsed.weights, "3, 2, 1") + self.assertEquals(parsed.func.__name__, "run_cli") + self.assertFalse(parsed.verbose) + + def test_argparse_exec_iterated_ensemble_smoother_valid_case(self): + parser = ArgumentParser(prog="test_main") + parsed = ert_parser(parser, ['iterated_ensemble_smoother', "--iterations", "40", + 'test-data/local/poly_example/poly.ert']) + self.assertEquals(parsed.mode, "iterated_ensemble_smoother") + self.assertEquals( + parsed.config, "test-data/local/poly_example/poly.ert") + self.assertEquals(parsed.iterations, 40) + self.assertEquals(parsed.func.__name__, "run_cli") + self.assertFalse(parsed.verbose) + + def test_argparse_exec_iterated_ensemble_smoother_default_iterations(self): + parser = ArgumentParser(prog="test_main") + parsed = ert_parser(parser, [ + 'iterated_ensemble_smoother', 'test-data/local/poly_example/poly.ert']) + self.assertEquals(parsed.mode, "iterated_ensemble_smoother") + self.assertEquals( + parsed.config, "test-data/local/poly_example/poly.ert") + self.assertEquals(parsed.iterations, 4) + self.assertEquals(parsed.func.__name__, "run_cli") + self.assertFalse(parsed.verbose) + + def test_argparse_exec_iterated_ensemble_smoother_faulty_iterations(self): + parser = ArgumentParser(prog="test_main") + with self.assertRaises(SystemExit): + ert_parser(parser, ['iterated_ensemble_smoother', "--iterations", "100" + 'test-data/local/poly_example/poly.ert']) + + with self.assertRaises(SystemExit): + ert_parser(parser, ['iterated_ensemble_smoother', "--iterations", "0", + 'test-data/local/poly_example/poly.ert']) if __name__ == '__main__':