Skip to content

Commit

Permalink
Merge pull request #1 from jpvantassel/dev
Browse files Browse the repository at this point in the history
code quality improvements in preparation for v0.3.1 release
  • Loading branch information
jpvantassel authored Jun 20, 2020
2 parents 1027b2f + a812b3c commit cb6ed18
Show file tree
Hide file tree
Showing 23 changed files with 279 additions and 172 deletions.
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
[![DOI](https://zenodo.org/badge/222287042.svg)](https://zenodo.org/badge/latestdoi/222287042)
[![CircleCI](https://circleci.com/gh/jpvantassel/swprepost.svg?style=svg)](https://circleci.com/gh/jpvantassel/swprepost)
[![Documentation Status](https://readthedocs.org/projects/swprepost/badge/?version=latest)](https://swprepost.readthedocs.io/en/latest/?badge=latest)
[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/jpvantassel/swprepost.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/jpvantassel/swprepost/context:python)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/150eb75dee3848f5bbfac0d9f2c33644)](https://www.codacy.com/manual/jpvantassel/swprepost?utm_source=github.com&utm_medium=referral&utm_content=jpvantassel/swprepost&utm_campaign=Badge_Grade)
[![codecov](https://codecov.io/gh/jpvantassel/swprepost/branch/master/graph/badge.svg)](https://codecov.io/gh/jpvantassel/swprepost)

## Table of Contents

---

- [About _SWprepost_](#About-SWprepost)
- [A Few Examples](#A-Few-Examples)
- [Getting Started](#Getting-Started)
- [About _SWprepost_](#About-SWprepost)
- [A Few Examples](#A-Few-Examples)
- [Getting Started](#Getting-Started)

## About _SWprepost_

Expand All @@ -32,19 +35,20 @@ Geophysical fields, but who do not perform surface wave inversions.
If you use `SWprepost` in your research or consulting we ask you please cite the
following:

> Joseph Vantassel. (2020). jpvantassel/swprepost: latest (Concept). Zenodo. http://doi.org/10.5281/zenodo.3839998
> Joseph Vantassel. (2020). jpvantassel/swprepost: latest (Concept). Zenodo.
> http://doi.org/10.5281/zenodo.3839998
_Note: For software, version specific citations should be preferred to general
concept citations, such as that listed above. To generate a version specific
citation for `SWprepost`, please use the citation tool for that specific version
on the `SWprepost` archive._
_Note: For software, version specific citations should be preferred to general_
_concept citations, such as that listed above. To generate a version specific_
_citation for `SWprepost`, please use the citation tool for that specific_
_version on the `SWprepost` [archive](https://doi.org/10.5281/zenodo.3839998)._

For the motivation behind the development of `SWprepost` and its role in a
larger project focused on developing a complete workflow for surface wave
inversion please refer to and consider citing the following:

> Joseph P. Vantassel and Brady R. Cox (2020) SWinvert: A workflow for
performing rigorous surface wave inversion. (Submitted).
> performing rigorous surface wave inversion. (Submitted).
## A Few Examples

Expand Down
2 changes: 1 addition & 1 deletion examples/basic/ReadmeExamples.ipynb

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions examples/basic/Targets.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@
"source": [
"# Perform resampling in log-wavelength\n",
"tar = swprepost.Target.from_target(\"inputs/from_tar_complex\")\n",
"new_tar = tar.resample(pmin=5, pmax=50, pn=20, res_type=\"log\", domain=\"wavelength\", inplace=False)\n",
"new_tar = tar.easy_resample(pmin=5, pmax=50, pn=20, res_type=\"log\", domain=\"wavelength\", inplace=False)\n",
"\n",
"# Plot\n",
"fig, ax = plt.subplots(figsize=(4,3), dpi=200)\n",
Expand Down Expand Up @@ -247,7 +247,7 @@
"source": [
"# Perform resampling in log-frequency\n",
"tar = swprepost.Target.from_target(\"inputs/from_tar_complex\")\n",
"new_tar = tar.resample(pmin=5, pmax=50, pn=20, res_type=\"log\", domain=\"frequency\", inplace=False)\n",
"new_tar = tar.easy_resample(pmin=5, pmax=50, pn=20, res_type=\"log\", domain=\"frequency\", inplace=False)\n",
"\n",
"# Plot\n",
"fig, ax = plt.subplots(figsize=(4,3), dpi=200)\n",
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setup(
name='swprepost',
version='0.3.0',
version='0.3.1',
description='A Python Package for Surface-Wave Inversion Pre- and Post-Processing',
long_description=long_description,
long_description_content_type='text/markdown',
Expand Down
36 changes: 31 additions & 5 deletions swprepost/curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def check_input(cls, x, y, check_fxn=None):
return (x, y)

def __init__(self, x, y, check_fxn=None):
"""Intialize a curve object from x, y coordinates.
"""Initialize a curve object from x, y coordinates.
Parameters
----------
Expand Down Expand Up @@ -129,8 +129,7 @@ def resample_function(cls, x, y, **kwargs):
"""Wrapper for `interp1d` from `scipy`."""
return sp.interp1d(x, y, **kwargs)

def resample(self, xx, inplace=False,
interp1d_kwargs={"kind": "cubic"}, res_fxn=None):
def resample(self, xx, inplace=False, interp1d_kwargs=None, res_fxn=None):
"""Resample Curve at select x values.
Parameters
Expand All @@ -150,8 +149,8 @@ def resample(self, xx, inplace=False,
for details.
res_fxn : function, optional
Define a custom resampling function. It should accept an
ndarray of resampling locations and return the
interpolated y-coordinates as an iterable.
ndarray of resampling x-coordinates and return the
interpolated y-coordinates as an ndarray.
Returns
-------
Expand All @@ -163,6 +162,8 @@ def resample(self, xx, inplace=False,
xx = np.array(xx, dtype=np.double)

if res_fxn is None:
if interp1d_kwargs is None:
interp1d_kwargs = {"kind": "cubic"}
res_fxn = self.resample_function(self._x,
self._y,
**interp1d_kwargs)
Expand All @@ -172,3 +173,28 @@ def resample(self, xx, inplace=False,
self._x, self._y = xx, yy
else:
return (xx, yy)

def __eq__(self, other):
"""Compare whether two curve objects are equal."""
if not isinstance(other, Curve):
return False

for attr in ["_x", "_y"]:
my = getattr(self, attr)
ur = getattr(other, attr)

if my.size != ur.size:
return False

if not np.allclose(my, ur):
return False

return True

def __repr__(self):
"""Unambiguous representation of a `Curve` object."""
return f"Curve(x={self._x}, y={self._y})"

def __str__(self):
"""Human-readable representation of a `Curve` object."""
return f"Curve with {self._x.size} points."
79 changes: 52 additions & 27 deletions swprepost/curveuncertain.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@


class CurveUncertain(Curve):
"""Curve object with aribtrary uncertainty in terms of x and y.
"""Curve object with arbitrary uncertainty in terms of x and y.
Attributes
----------
Expand All @@ -34,6 +34,22 @@ class CurveUncertain(Curve):
"""

@staticmethod
def _check_error(error, npts):
"""Check error is compatable, specifically:
1. Can be cast to a ndarray.
2. Error has same length of the curve.
"""
error = np.array(error, dtype=np.double)

if error.size != npts:
msg = f"Size of error and curve must match exactly. {error.size} != {npts}."
raise IndexError(msg)

return error

def __init__(self, x, y, yerr=None, xerr=None):
"""Initialize a new `CurveUncertain` object.
Expand All @@ -58,25 +74,17 @@ def __init__(self, x, y, yerr=None, xerr=None):
provided) are inconsistent.
"""
# Pass x, y to `Curve` constuctor.
# Pass x, y to `Curve`.
super().__init__(x, y)

# Handle x-error and y-error.
for attr, attr_name, attr_bool in zip([yerr, xerr],
["_yerr", "_xerr"],
["_isyerr", "_isxerr"]):
if attr is None:
setattr(self, attr_bool, False)
else:
setattr(self, attr_bool, True)
setattr(self, attr_name, np.array(attr))

if self._x.size != getattr(self, attr_name).size:
msg = "Size of the curve's attributes must be consistent."
raise IndexError(msg)
npts = self._x.size
self._yerr = None if yerr is None else self._check_error(yerr, npts)
self._xerr = None if xerr is None else self._check_error(xerr, npts)
self._isyerr = False if yerr is None else True
self._isxerr = False if xerr is None else True

def resample(self, xx, inplace=False,
res_fxn=None, res_fxn_xerr=None, res_fxn_yerr=None):
def resample(self, xx, inplace=False, interp1d_kwargs=None, res_fxn=None):
"""Resample curve and its associated uncertainty.
Parameters
Expand All @@ -85,42 +93,59 @@ def resample(self, xx, inplace=False,
Desired x values after resampling.
inplace : bool, optional
Indicates whether resampling is performed inplace and
the objects attributes are updated or if calculated
the objects attributes are updated or if calculated
values are returned.
res_fxn, res_fxn_xerr, res_fxn_yerr : function, optional
interp1d_settings : dict, optional
Settings for use with the `interp1d` function from `scipy`.
See documentation `here
<https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html>`_
for details.
res_fxn : tuple of functions, optional
Functions to define the resampling of the central
x and y values, xerr and yerr respectively, default is
`None` indicating default resampling function is used.
Returns
-------
-------
None or Tuple
If `inplace=True`, returns `None`, instead update
attributes `_x`, `_y`, `_xerr`, and `_yerr` if they exist.
If `inplace=False`, returns `Tuple` of the form
`(xx, yy, yyerr, xxerr)`. If `xerr` and/or `yerr` are not
defined they are not resampled and ommited from the return
defined they are not resampled and omitted from the return
statement.
"""
# Create resample functions before resampling mean curve
# Unpack res_fxn
if res_fxn is None:
res_fxn_xerr, res_fxn_yerr = None, None
else:
res_fxn, res_fxn_xerr, res_fxn_yerr = res_fxn

# Default interpolation kwargs
if interp1d_kwargs is None:
interp1d_kwargs = {"kind": "cubic"}

# Define error resampling first.
if self._isyerr and res_fxn_yerr is None:
res_fxn_yerr = super().resample_function(self._x, self._yerr,
kind="cubic")
res_fxn_yerr = super().resample_function(self._x,
self._yerr,
**interp1d_kwargs)
if self._isxerr and res_fxn_xerr is None:
res_fxn_xerr = super().resample_function(self._x, self._xerr,
kind="cubic")
res_fxn_xerr = super().resample_function(self._x,
self._xerr,
**interp1d_kwargs)

# Resample mean curve
new_mean_curve = super().resample(xx=xx, inplace=inplace,
interp1d_kwargs=interp1d_kwargs,
res_fxn=res_fxn)

# Resample error
if inplace:
xx = self._x
else:
xx, yy = new_mean_curve

# Resample error
if self._isyerr:
yerr = res_fxn_yerr(xx)
if self._isxerr:
Expand Down
2 changes: 2 additions & 0 deletions swprepost/dispersionset.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ def _parse_dcs(cls, dcs_data, nmodes="all"):

if nmodes == "all":
modes = modes[1:]
elif nmodes == 0:
return None
else:
modes = modes[1:nmodes+1]

Expand Down
31 changes: 9 additions & 22 deletions swprepost/dispersionsuite.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,7 @@ def append(self, dispersionset, sort=True):
"""
self.check_input(dispersionset, DispersionSet)
super().append(dispersionset, sort=sort)

@property
def size(self):
return len(self._items)

# @property
# def ids(self):
# """Return the ids corresponding to `sets`."""
# return [cset.identifier for cset in self.sets]

# @property
# def misfits(self):
# """Return the misfits corresponding to `sets`."""
# return [cset.misfit for cset in self.sets]
super()._append(dispersionset, sort=sort)

@classmethod
def from_geopsy(cls, fname, nsets="all", nrayleigh="all", nlove="all",
Expand Down Expand Up @@ -144,25 +130,26 @@ def from_geopsy(cls, fname, nsets="all", nrayleigh="all", nlove="all",
for model_info in regex.dcset.finditer(lines):
identifier, misfit, wave_type, data = model_info.groups()

# Encountered new model, save previous and reset.
if identifier != previous_id and previous_id != "start":
if model_count+1 == nsets:
break

dc_sets.append(cls._dcset()(previous_id,
float(previous_misfit),
rayleigh=rayleigh, love=love))
model_count += 1
rayleigh, love = None, None

# Parse data.
if wave_type == "Rayleigh":
rayleigh = cls._dcset()._parse_dcs(data, nmodes=nrayleigh)
elif wave_type == "Love":
love = cls._dcset()._parse_dcs(data, nmodes=nlove)
else:
raise NotImplementedError

previous_id = identifier
previous_misfit = misfit

if model_count + 1 == nsets:
break
previous_id, previous_misfit = identifier, misfit

dc_sets.append(cls._dcset()(previous_id,
float(previous_misfit),
Expand Down Expand Up @@ -190,7 +177,7 @@ def from_list(cls, dc_sets, sort=True):
Returns
-------
DipsersionSuite
Instatiated `DispersionSuite` object.
Instantiated `DispersionSuite` object.
"""
obj = cls(dc_sets[0])
Expand Down Expand Up @@ -218,7 +205,7 @@ def write_to_txt(self, fname, nbest="all"):
"""
nbest = self._handle_nbest(nbest)
with open(fname, "w") as f:
f.write("# File written by swipp\n")
f.write("# File written by swprepost\n")
for cit in self.sets[:nbest]:
cit.write_set(f)

Expand Down
Loading

0 comments on commit cb6ed18

Please sign in to comment.