From a4ea65eb97cb05860cf1e3f508ee55cb2de18443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dieter=20Werthm=C3=BCller?= Date: Sat, 21 May 2022 13:26:39 +0200 Subject: [PATCH] v1.7.0 CLI-clean; deprecations (#288) * v1.7.0 Changelog * Deprecate noise-parameters in simulation section of config file * Deprecate expand in simulation * Bump change of center_on_edge to v1.9.0 --- CHANGELOG.rst | 17 +++++++++++++++-- docs/manual/cli.rst | 17 +++++++++++------ emg3d/cli/parser.py | 35 ++++++++++++++++++++++++++++++++++- emg3d/cli/run.py | 1 + emg3d/meshes.py | 9 +++++---- emg3d/simulations.py | 6 ++++++ tests/test_cli.py | 33 +++++++++++++++++++++++++++++++-- tests/test_simulations.py | 20 +++++++++++++------- 8 files changed, 116 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 54586a8b..f8be2cd8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,8 +6,10 @@ Changelog """""""""" -*latest* --------- +v1.7.0 : CLI-clean +------------------ + +**2022-05-21** - CLI: @@ -18,16 +20,27 @@ Changelog - New command-line argument ``--cache`` (or as parameter ``cache`` in the configuration file under ``[files]``): Acts as a shortcut for ``--load --save`` using the same file name. + - Parameters for noise generation should new be provided under their own + section ``[noise_opts]``; providing them under ``[simulation]`` is + deprecated and will be removed in v1.9.0. - Simulation: - ``'all'`` is now the same as ``'computed'`` in ``to_file`` and ``to_dict``, meaning the grids are stored as well. + - Deprecation: The ``'expand'``-functionality in the gridding options is + deprecated and will be removed in v1.9.0. A property-complete model has to + be provided. + +- Meshes: Bumped the change of the default value for ``center_on_edge`` from + ``True`` to ``False`` to v1.9.0, coinciding with the above deprecations. v1.6.1 : Max offset ------------------- +**2022-05-11** + - Survey: ``add_noise`` takes new a ``max_offset`` argument; receivers responses at offsets greater than maximum offset are set to NaN (also available through the CLI). diff --git a/docs/manual/cli.rst b/docs/manual/cli.rst index c490e3a2..64bde85e 100644 --- a/docs/manual/cli.rst +++ b/docs/manual/cli.rst @@ -54,15 +54,10 @@ remove the comment signs to use them. # Simulation parameters # --------------------- # Input parameters for the `Simulation` class, except for `solver_opts` - # (defined in their own section), but including the parameter `min_offset` - # for `compute()`. + # (defined in their own section). [simulation] # max_workers = 4 # Also via `-n` or `--nproc`. # gridding = single - # min_offset = 0.0 # off < min_off set to NaN; only if `--forward`. - # max_offset = np.infty # off > max_off set to NaN; only if `--forward`. - # mean_noise = 0.0 # Mean of the noise; only if `--forward`. - # ntype = white_noise # Type of the noise; only if `--forward`. # name = MyTestSimulation # file_dir = None # For file-based comp; absolute or relative path. # receiver_interpolation = cubic # Set it to for the gradient. @@ -121,6 +116,16 @@ remove the comment signs to use them. # verb = # int, e.g.: 0 # lambda_from_center = # bool, e.g.: False + # Noise options + # ------------- + # Only if `--forward`, the noise options are passed to + # `Simulation.compute(observed=True, **noise_opts)`. + [noise_opts] + # min_offset = 0.0 # off < min_off set to NaN. + # max_offset = np.infty # off > max_off set to NaN. + # mean_noise = 0.0 # Mean of the noise. + # ntype = white_noise # Type of the noise. + # Data # ---- # Select which sources, receivers, and frequencies of the survey are used. By diff --git a/emg3d/cli/parser.py b/emg3d/cli/parser.py index 7badbb0f..934865cc 100644 --- a/emg3d/cli/parser.py +++ b/emg3d/cli/parser.py @@ -18,6 +18,7 @@ # the License. import os +import warnings import configparser from pathlib import Path @@ -197,7 +198,7 @@ def parse_config_file(args_dict): # Default is 'cubic' - gradient needs 'linear' simulation[key] = 'linear' - # Check noise parameters + # Check noise parameters => deprecated in ``simulation``, see [noise_opts]! noise_kwargs = {} keys = ['min_offset', 'mean_noise', 'max_offset'] for key in keys: @@ -215,6 +216,38 @@ def parse_config_file(args_dict): f"Unexpected parameter in [simulation]: {list(all_sim.keys())}." ) + # # Noise parameters # # + + if len(noise_kwargs) > 0: + msg = ("emg3d: `min_offset`, `max_offset`, `mean_noise`, and `ntype` " + "belong since v1.7.0 in their own section [noise_opts]; " + "providing them in [simulation] is deprecated and will be " + "removed in v1.9.0.") + warnings.warn(msg, FutureWarning) + + # Check if parameter-file has a noise-section, add it otherwise. + # Note: values in noise_opts overwrite values from simulation. + if 'noise_opts' in cfg.sections(): + + all_noise = dict(cfg.items('noise_opts')) + + keys = ['min_offset', 'max_offset', 'mean_noise'] + for key in keys: + if cfg.has_option('noise_opts', key): + _ = all_noise.pop(key) + noise_kwargs[key] = cfg.getfloat('noise_opts', key) + key = 'ntype' + if cfg.has_option('noise_opts', key): + _ = all_noise.pop(key) + noise_kwargs[key] = cfg.get('noise_opts', key) + + # Ensure no keys are left. + if all_noise: + raise TypeError( + f"Unexpected parameter in [noise_opts]: " + f"{list(all_noise.keys())}." + ) + # # Solver parameters # # # Check if parameter-file has a solver-section, add it otherwise. diff --git a/emg3d/cli/run.py b/emg3d/cli/run.py index ed42415d..6656be64 100644 --- a/emg3d/cli/run.py +++ b/emg3d/cli/run.py @@ -95,6 +95,7 @@ def simulation(args_dict): sim.model = model['model'] # Expand model if necessary. + # Deprecated, will be removed in v1.9.0. gopts = cfg['simulation_options']['gridding_opts'] expand = gopts.pop('expand', None) if expand is not None: diff --git a/emg3d/meshes.py b/emg3d/meshes.py index bace279d..997bc305 100644 --- a/emg3d/meshes.py +++ b/emg3d/meshes.py @@ -347,8 +347,8 @@ def construct_mesh(frequency, properties, center, domain=None, vector=None, center : array_like Center coordinates (x, y, z). The mesh is centered around this point, which means that here is the smallest cell. Usually this is the source - location. Note that from v1.7.0 the default will change: until then, - the center is assumed to be at the edge; from v1.7.0 onwards, it is + location. Note that from v1.9.0 the default will change: until then, + the center is assumed to be at the edge; from v1.9.0 onwards, it is assumed to be at the cell center. It can be changed via the parameter ``center_on_edge``. @@ -633,12 +633,12 @@ def origin_and_widths(frequency, properties, center, domain=None, vector=None, if vector is None: msg = ( "emg3d: `center` will change from being at an edge to " - "being at the cell center in v1.7.0. This behaviour can " + "being at the cell center in v1.9.0. This behaviour can " "be set via at `center_on_edge`. Set `center_on_edge` " "specifically to suppress this warning." ) warnings.warn(msg, FutureWarning) - center_on_edge = True # Backwards compatible, until v1.7.0. + center_on_edge = True # Backwards compatible, until v1.9.0. # Get property map from string. if isinstance(pmap, str): @@ -1378,6 +1378,7 @@ def estimate_gridding_opts(gridding_opts, model, survey, input_sc2=None): input_sc2 : int, default: None If :func:`emg3d.models.expand_grid_model` was used, ``input_sc2`` corresponds to the original ``grid.shape_cells[2]``. + NOTE: Will be removed in v1.9.0. Returns diff --git a/emg3d/simulations.py b/emg3d/simulations.py index 8e73131f..4aa0f45c 100644 --- a/emg3d/simulations.py +++ b/emg3d/simulations.py @@ -137,6 +137,8 @@ class Simulation: water, and an air layer is added. The actual height of the seasurface can be defined with the key ``seasurface``. See :func:`emg3d.models.expand_grid_model`. + NOTE: ``expand`` is deprecated in v1.7.0, and will be removed in + v1.9.0. A property-complete model has to be provided. solver_opts : dict, default: {'verb': 1'} Passed through to :func:`emg3d.solver.solve`. The dict can contain any @@ -1456,6 +1458,10 @@ def _set_model(self, model, kwargs): # Expand model by water and air if required. expand = g_opts.pop('expand', None) if expand is not None: + msg = ("emg3d: `expand` is deprecated and will be removed in " + "v1.9.0. A property-complete model has to be provided.") + warnings.warn(msg, FutureWarning) + try: interface = g_opts['seasurface'] except KeyError as e: diff --git a/tests/test_cli.py b/tests/test_cli.py index d6748a15..497cf846 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -261,7 +261,8 @@ def test_simulation(self, tmpdir): args_dict = self.args_dict.copy() args_dict['config'] = config - cfg, term = cli.parser.parse_config_file(args_dict) + with pytest.warns(FutureWarning, match='in their own section'): + cfg, term = cli.parser.parse_config_file(args_dict) sim_opts = cfg['simulation_options'] noise_kwargs = cfg['noise_kwargs'] assert sim_opts['max_workers'] == 5 @@ -302,6 +303,33 @@ def test_simulation(self, tmpdir): sim_opts = cfg['simulation_options'] assert sim_opts['receiver_interpolation'] == 'cubic' + def test_noise(self, tmpdir): + + # Write a config file. + config = os.path.join(tmpdir, 'emg3d.cfg') + with open(config, 'w') as f: + f.write("[noise_opts]\n") + f.write("min_offset=1320\n") + f.write("max_offset=5320\n") + f.write("mean_noise=1.0\n") + f.write("ntype=gaussian_uncorrelated") + + args_dict = self.args_dict.copy() + args_dict['config'] = config + cfg, term = cli.parser.parse_config_file(args_dict) + noise_kwargs = cfg['noise_kwargs'] + assert noise_kwargs['min_offset'] == 1320.0 + assert noise_kwargs['max_offset'] == 5320.0 + assert noise_kwargs['mean_noise'] == 1.0 + assert noise_kwargs['ntype'] == 'gaussian_uncorrelated' + + with pytest.raises(TypeError, match="Unexpected parameter in"): + with open(config, 'a') as f: + f.write("\nanother=True") + args_dict = self.args_dict.copy() + args_dict['config'] = config + _ = cli.parser.parse_config_file(args_dict) + def test_solver(self, tmpdir): # Write a config file. @@ -692,7 +720,8 @@ def test_expand(self, tmpdir): args_dict['config'] = os.path.join(tmpdir, 'emg3d.cfg') args_dict['path'] = tmpdir args_dict['save'] = 'simulation1.h5' - cli.run.simulation(args_dict.copy()) + with pytest.warns(FutureWarning, match='A property-complete'): + cli.run.simulation(args_dict.copy()) # Replace / add dicts s = emg3d.Simulation.from_file(os.path.join(tmpdir, 'simulation1.h5')) diff --git a/tests/test_simulations.py b/tests/test_simulations.py index 8f5a4121..88476a90 100644 --- a/tests/test_simulations.py +++ b/tests/test_simulations.py @@ -149,9 +149,10 @@ def test_errors(self): # expand without seasurface with pytest.raises(KeyError, match="is required if"): - simulations.Simulation( - self.survey, self.model, name='Test', - gridding='single', gridding_opts={'expand': [1, 2]}) + with pytest.warns(FutureWarning, match='A property-complete'): + simulations.Simulation( + self.survey, self.model, name='Test', + gridding='single', gridding_opts={'expand': [1, 2]}) def test_reprs(self): test = self.simulation.__repr__() @@ -324,11 +325,16 @@ def test_simulation_automatic(self, capsys): }, } - b_sim = simulations.Simulation(name='both', gridding='both', **inp) - f_sim = simulations.Simulation( + with pytest.warns(FutureWarning, match='A property-complete'): + b_sim = simulations.Simulation(name='both', gridding='both', **inp) + with pytest.warns(FutureWarning, match='A property-complete'): + f_sim = simulations.Simulation( name='freq', gridding='frequency', **inp) - t_sim = simulations.Simulation(name='src', gridding='source', **inp) - s_sim = simulations.Simulation( + with pytest.warns(FutureWarning, match='A property-complete'): + t_sim = simulations.Simulation( + name='src', gridding='source', **inp) + with pytest.warns(FutureWarning, match='A property-complete'): + s_sim = simulations.Simulation( name='single', gridding='single', **inp) # Quick repr test.