Skip to content

Commit

Permalink
Remove optimize (#245)
Browse files Browse the repository at this point in the history
* Remove optimize
* Move `expand_model` and `estimate_gridding_options` out from simulations
* `Model.interpolate_to_grid()`: Don't interpolate if same grid
  • Loading branch information
prisae authored Aug 22, 2021
1 parent 39873c9 commit fd06067
Show file tree
Hide file tree
Showing 11 changed files with 988 additions and 950 deletions.
23 changes: 16 additions & 7 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,27 @@ Changelog
""""""""""


latest
------
v1.2.1: Remove optimize & bug fix
---------------------------------

- Simulation:
**2021-08-22**

- ``io``: Adjustment so that hdf5 tracks the order of dicts.

- ``simulations``:

- Adjust printing: correct simulation results for adjusted solver printing
levels; *default solver verbosity is new 1*; ``log`` can now be overwritten
in ``solver_opts`` (mainly for debugging).
levels; **default solver verbosity is new 1**; ``log`` can now be
overwritten in ``solver_opts`` (mainly for debugging).

- Bug fixes:
- Functions moved out of ``simulations``: ``expand_grid_model`` moved to
``models`` and ``estimate_gridding_options`` to ``meshes``. The
availability of these functions through ``simulations`` will be removed in
v1.4.0.

- Track order when saving to hdf5.
- ``optimize``: the module is deprecated and will be removed in v1.4.0. The two
functions ``optimize.{misfit;gradient}`` are embedded directly in
``Simulation.{misfit;gradient}``.


v1.2.0: White noise
Expand Down
1 change: 0 additions & 1 deletion docs/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ API reference
maps
meshes
models
optimize
simulations
solver
surveys
Expand Down
6 changes: 0 additions & 6 deletions docs/api/optimize.rst

This file was deleted.

264 changes: 263 additions & 1 deletion emg3d/meshes.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

__all__ = ['TensorMesh', 'BaseMesh', 'construct_mesh', 'origin_and_widths',
'good_mg_cell_nr', 'skin_depth', 'wavelength', 'cell_width',
'check_mesh']
'check_mesh', 'estimate_gridding_opts']


class BaseMesh:
Expand Down Expand Up @@ -1054,3 +1054,265 @@ def check_mesh(mesh):
f"MG solver. Good numbers are:\n{good_mg_cell_nr(max_nr=5000)}"
)
warnings.warn(msg, UserWarning)


def estimate_gridding_opts(gridding_opts, model, survey, input_sc2=None):
"""Estimate parameters for automatic gridding.
Automatically determines the required gridding options from the provided
model, and survey, if they are not provided in ``gridding_opts``.
The dict ``gridding_opts`` can contain any input parameter taken by
:func:`emg3d.meshes.construct_mesh`, see the corresponding documentation
for more details with regards to the possibilities.
Different keys of ``gridding_opts`` are treated differently:
- The following parameters are estimated from the ``model`` if not
provided:
- ``properties``: lowest conductivity / highest resistivity in the
outermost layer in a given direction. This is usually air in x/y and
positive z. Note: This is very conservative. If you go into deeper
water you could provide less conservative values.
- ``mapping``: taken from model.
- The following parameters are estimated from the ``survey`` if not
provided:
- ``frequency``: average (on log10-scale) of all frequencies.
- ``center``: center of all sources.
- ``domain``: from ``vector`` or ``distance``, if provided, or
- in x/y-directions: extent of sources and receivers plus 10% on each
side, ensuring ratio of 3.
- in z-direction: extent of sources and receivers, ensuring ratio of 2
to horizontal dimension; 1/10 tenth up, 9/10 down.
The ratio means that it is enforced that the survey dimension in x or
y-direction is not smaller than a third of the survey dimension in the
other direction. If not, the smaller dimension is expanded
symmetrically. Similarly in the vertical direction, which must be at
least half the dimension of the maximum horizontal dimension or 5 km,
whatever is smaller. Otherwise it is expanded in a ratio of 9 parts
downwards, one part upwards.
- The following parameter is taken from the ``grid`` if provided as a
string:
- ``vector``: This is the only real "difference" to the inputs of
:func:`emg3d.meshes.construct_mesh`. The normal input is accepted, but
it can also be a string containing any combination of ``'x'``, ``'y'``,
and ``'z'``. All directions contained in this string are then taken
from the provided grid. E.g., if ``gridding_opts['vector']='xz'`` it
will take the x- and z-directed vectors from the grid.
- The following parameters are simply passed along if they are provided,
nothing is done otherwise:
- ``vector``
- ``distance``
- ``stretching``
- ``seasurface``
- ``cell_numbers``
- ``lambda_factor``
- ``lambda_from_center``
- ``max_buffer``
- ``min_width_limits``
- ``min_width_pps``
- ``verb``
Parameters
----------
gridding_opts : dict
Containing input parameters to provide to
:func:`emg3d.meshes.construct_mesh`. See the corresponding
documentation and the explanations above.
model : Model
The model; a :class:`emg3d.models.Model` instance.
survey : Survey
The survey; a :class:`emg3d.surveys.Survey` instance.
input_sc2 : int, default: None
If :func:`emg3d.models.expand_grid_model` was used, ``input_sc2``
corresponds to the original ``grid.shape_cells[2]``.
Returns
-------
gridding_opts : dict
Dict to provide to :func:`emg3d.meshes.construct_mesh`.
"""
# Initiate new gridding_opts.
gopts = {}
grid = model.grid

# Optional values that we only include if provided.
for name in ['seasurface', 'cell_numbers', 'lambda_factor',
'lambda_from_center', 'max_buffer', 'verb']:
if name in gridding_opts.keys():
gopts[name] = gridding_opts.pop(name)
for name in ['stretching', 'min_width_limits', 'min_width_pps']:
if name in gridding_opts.keys():
value = gridding_opts.pop(name)
if isinstance(value, (list, tuple)) and len(value) == 3:
value = {'x': value[0], 'y': value[1], 'z': value[2]}
gopts[name] = value

# Mapping defaults to model map.
gopts['mapping'] = gridding_opts.pop('mapping', model.map)
if not isinstance(gopts['mapping'], str):
gopts['mapping'] = gopts['mapping'].name

# Frequency defaults to average frequency (log10).
frequency = 10**np.mean(np.log10([v for v in survey.frequencies.values()]))
gopts['frequency'] = gridding_opts.pop('frequency', frequency)

# Center defaults to center of all sources.
center = np.array([s.center for s in survey.sources.values()]).mean(0)
gopts['center'] = gridding_opts.pop('center', center)

# Vector.
vector = gridding_opts.pop('vector', None)
if isinstance(vector, str):
# If vector is a string we take the corresponding vectors from grid.
vector = (
grid.nodes_x if 'x' in vector.lower() else None,
grid.nodes_y if 'y' in vector.lower() else None,
grid.nodes_z[:input_sc2] if 'z' in vector.lower() else None,
)
gopts['vector'] = vector
if isinstance(vector, dict):
vector = (vector['x'], vector['y'], vector['z'])
elif vector is not None and len(vector) == 3:
gopts['vector'] = {'x': vector[0], 'y': vector[1], 'z': vector[2]}

# Distance.
distance = gridding_opts.pop('distance', None)
gopts['distance'] = distance
if isinstance(distance, dict):
distance = (distance['x'], distance['y'], distance['z'])
elif distance is not None and len(distance) == 3:
gopts['distance'] = {'x': distance[0], 'y': distance[1],
'z': distance[2]}

# Properties defaults to lowest conductivities (AFTER model expansion).
properties = gridding_opts.pop('properties', None)
if properties is None:

# Get map (in principle the map in gridding_opts could be different
# from the map in the model).
m = gopts['mapping']
if isinstance(m, str):
m = getattr(maps, 'Map'+m)()

# Minimum conductivity of all values (x, y, z).
def get_min(ix, iy, iz):
"""Get minimum: very conservative/costly, but avoiding problems."""

# Collect all x (y, z) values.
data = np.array([])
for p in ['x', 'y', 'z']:
prop = getattr(model, 'property_'+p)
if prop is not None:
prop = model.map.backward(prop[ix, iy, iz])
data = np.r_[data, np.min(prop)]

# Return minimum conductivity (on mapping).
return m.forward(min(data))

# Buffer properties.
xneg = get_min(0, slice(None), slice(None))
xpos = get_min(-1, slice(None), slice(None))
yneg = get_min(slice(None), 0, slice(None))
ypos = get_min(slice(None), -1, slice(None))
zneg = get_min(slice(None), slice(None), 0)
zpos = get_min(slice(None), slice(None), -1)

# Source property.
ix = np.argmin(abs(grid.nodes_x - gopts['center'][0]))
iy = np.argmin(abs(grid.nodes_y - gopts['center'][1]))
iz = np.argmin(abs(grid.nodes_z - gopts['center'][2]))
source = get_min(ix, iy, iz)

properties = [source, xneg, xpos, yneg, ypos, zneg, zpos]

gopts['properties'] = properties

# Domain; default taken from survey.
domain = gridding_opts.pop('domain', None)
if isinstance(domain, dict):
domain = (domain['x'], domain['y'], domain['z'])

def get_dim_diff(i):
"""Return ([min, max], dim) of inp.
Take it from domain if provided, else from vector if provided, else
from survey, adding 10% on each side).
"""
if domain is not None and domain[i] is not None:
# domain is provided.
dim = domain[i]
diff = np.diff(dim)[0]
get_it = False

elif vector is not None and vector[i] is not None:
# vector is provided.
dim = [np.min(vector[i]), np.max(vector[i])]
diff = np.diff(dim)[0]
get_it = False

elif distance is not None and distance[i] is not None:
# distance is provided.
dim = None
diff = abs(distance[i][0]) + abs(distance[i][1])
get_it = False

else:
# Get it from survey, add 5 % on each side.
inp = np.array([s.center[i] for s in survey.sources.values()])
for s in survey.sources.values():
inp = np.r_[inp, [r.center_abs(s)[i]
for r in survey.receivers.values()]]
dim = [min(inp), max(inp)]
diff = np.diff(dim)[0]
dim = [min(inp)-diff/10, max(inp)+diff/10]
diff = np.diff(dim)[0]
get_it = True

diff = np.where(diff > 1e-9, diff, 1e-9) # Avoid division by 0 later
return dim, diff, get_it

xdim, xdiff, get_x = get_dim_diff(0)
ydim, ydiff, get_y = get_dim_diff(1)
zdim, zdiff, get_z = get_dim_diff(2)

# Ensure the ratio xdim:ydim is at most 3.
if get_y and xdiff/ydiff > 3:
diff = round((xdiff/3.0 - ydiff)/2.0)
ydim = [ydim[0]-diff, ydim[1]+diff]
elif get_x and ydiff/xdiff > 3:
diff = round((ydiff/3.0 - xdiff)/2.0)
xdim = [xdim[0]-diff, xdim[1]+diff]

# Ensure the ratio zdim:horizontal is at most 2.
hdist = min(10000, max(xdiff, ydiff))
if get_z and hdist/zdiff > 2:
diff = round((hdist/2.0 - zdiff)/10.0)
zdim = [zdim[0]-9*diff, zdim[1]+diff]

# Collect
gopts['domain'] = {'x': xdim, 'y': ydim, 'z': zdim}

# Ensure no gridding_opts left.
if gridding_opts:
raise TypeError(
f"Unexpected gridding_opts: {list(gridding_opts.keys())}."
)

# Return gridding_opts.
return gopts
Loading

0 comments on commit fd06067

Please sign in to comment.