From 25ff1e6e5b21eeb6bf8a9034eaea1179cd7597f3 Mon Sep 17 00:00:00 2001 From: Joseph vantassel Date: Tue, 2 Jun 2020 19:03:53 -0500 Subject: [PATCH 01/10] :hammer: Improve robustness of discretize --- swprepost/groundmodel.py | 48 ++++++++++++++++++++++++---------------- test/test_groundmodel.py | 11 +++++++++ 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/swprepost/groundmodel.py b/swprepost/groundmodel.py index 7f1543f..7f076df 100644 --- a/swprepost/groundmodel.py +++ b/swprepost/groundmodel.py @@ -377,40 +377,50 @@ def discretize(self, dmax, dy=0.5, parameter='vs'): If `parameter` is not one of those options specified. """ - disc_depth = np.linspace(0, dmax, int(dmax//dy)+1).tolist() + # Use linspace to ensure start, end, and number of samples. + disc_depth = np.linspace(0, dmax, int(round(dmax/dy))+1) + disc_par = np.empty_like(disc_depth, dtype=float) if parameter == "pr": disc_par = self.calc_pr(self.discretize(dmax, dy, "vp")[1], self.discretize(dmax, dy, "vs")[1]) - return (disc_depth, disc_par) + return (disc_depth.tolist(), disc_par) valid_parameters = ["depth", "vp", "vs", "rh", "density", "pr"] self._validate_parameter(parameter, valid_parameters) par_to_disc = getattr(self, parameter) # For each layer - disc_par = [par_to_disc[0]] + start_index = 1 + disc_par[0] = par_to_disc[0] residual = 0 - if len(self.tk) > 1: - for c_lay, c_tk in enumerate(self.tk[:-1]): - float_disc = c_tk/dy - int_disc = int(c_tk // dy) - residual += (float_disc - int_disc) - if residual >= 1: - int_disc += 1 - disc_par += [par_to_disc[c_lay]]*int_disc + for c_lay, c_tk in enumerate(self.tk): - else: - c_lay = -1 - # Half-space - disc_par += [par_to_disc[c_lay+1]]*(len(disc_depth)-len(disc_par)) + # Half-space + if c_tk == 0: + disc_par[start_index:] = par_to_disc[c_lay] + break + + float_disc = c_tk/dy + int_disc = int(c_tk/dy) + residual += (float_disc - int_disc) + if residual >= 1: + int_disc += 1 + residual -= 1 + stop_index = start_index + int_disc + + # Layer extends beyond dmax + if stop_index > len(disc_par): + disc_par[start_index:] = par_to_disc[c_lay] + break + # Typical iteration + else: + disc_par[start_index:stop_index] = par_to_disc[c_lay] - # TODO (jpv): Properly account for the fact that the entire profile - # may not be discretized (i.e., for loop should not extend to self.tk[:-1]) - disc_par = disc_par[:len(disc_depth)] + start_index = stop_index - return (disc_depth, disc_par) + return (disc_depth.tolist(), disc_par.tolist()) def simplify(self, parameter='vs'): """Remove unecessary breaks in the parameter specified. diff --git a/test/test_groundmodel.py b/test/test_groundmodel.py index 050599f..4da5d1a 100644 --- a/test/test_groundmodel.py +++ b/test/test_groundmodel.py @@ -312,6 +312,17 @@ def test_discretize(self): expected = [100]*6 self.assertListEqual(expected, disc_vs) + # Fine discretization + tk = [5.3, 12.5, 0] + vp = [100, 200, 300] + vs = [50, 100, 150] + rh = [2000]*3 + gm = swprepost.GroundModel(tk, vp, vs, rh) + disc_depth, disc_vs = gm.discretize(dmax=50, dy=0.01) + expected = np.arange(0, int(50/0.01)+1, 1)*0.01 + self.assertListEqual(expected.tolist(), disc_depth) + self.assertEqual(len(disc_depth), len(disc_vs)) + def test_validate_parameter(self): # Bad Values valid_parameters = ["vs", "vp", "density", "pr"] From 83aabc76558a2fa9683e10b43b205e0c60e959d1 Mon Sep 17 00:00:00 2001 From: Joseph vantassel Date: Tue, 2 Jun 2020 23:05:50 -0500 Subject: [PATCH 02/10] :sparkles: Add external checkers --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index adbb12b..2203c60 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ [![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 From 7e3cca8bdca021749b9b158d0c6e8afeea4154ad Mon Sep 17 00:00:00 2001 From: Joseph vantassel Date: Wed, 3 Jun 2020 19:30:54 -0500 Subject: [PATCH 03/10] :hammer: Recommendations by lgtm and codacy --- swprepost/groundmodel.py | 4 +--- swprepost/groundmodelsuite.py | 5 +---- swprepost/parameterization.py | 2 +- swprepost/target.py | 16 +++++++--------- test/test_groundmodel.py | 2 +- test/test_parameterization.py | 1 - test/test_suite.py | 4 ++-- 7 files changed, 13 insertions(+), 21 deletions(-) diff --git a/swprepost/groundmodel.py b/swprepost/groundmodel.py index 7f076df..84db791 100644 --- a/swprepost/groundmodel.py +++ b/swprepost/groundmodel.py @@ -17,14 +17,12 @@ """GroundModel class definiton.""" -import os -import warnings import logging from scipy.io import savemat import numpy as np -from swprepost import DispersionSet, regex +from swprepost import regex logger = logging.getLogger(__name__) diff --git a/swprepost/groundmodelsuite.py b/swprepost/groundmodelsuite.py index 94d9ada..1efbf3a 100644 --- a/swprepost/groundmodelsuite.py +++ b/swprepost/groundmodelsuite.py @@ -17,14 +17,11 @@ """GroundModelSuite class definition.""" -import warnings -import os import logging -import scipy.io as sio import numpy as np -from swprepost import GroundModel, Suite, DispersionSuite, regex +from swprepost import GroundModel, Suite, regex logger = logging.getLogger(__name__) diff --git a/swprepost/parameterization.py b/swprepost/parameterization.py index 9a73df7..0cec54c 100644 --- a/swprepost/parameterization.py +++ b/swprepost/parameterization.py @@ -329,7 +329,7 @@ def to_param(self, fname_prefix, version="3", full_version=None): contents += [ f'linear("D{key}{lay+1}",">",{1},"D{key}{lay}",{min_thickness});'] - contents += [' ' + contents += [' ', ' ', ' ', ''] diff --git a/swprepost/target.py b/swprepost/target.py index bdacb08..32d8c6b 100644 --- a/swprepost/target.py +++ b/swprepost/target.py @@ -25,7 +25,6 @@ import matplotlib.pyplot as plt import numpy as np -import scipy.interpolate as sp from swprepost import CurveUncertain @@ -77,14 +76,13 @@ def __init__(self, frequency, velocity, velstd=0.05): """ logger.info("Howdy!") - try: - super().__init__(x=frequency, y=velocity, yerr=velstd, xerr=None) - except IndexError as e: - try: - velocity = np.array(velocity) - velstd = velocity*velstd - finally: - super().__init__(x=frequency, y=velocity, yerr=velstd, xerr=None) + + # if velstd is None: + # velstd = np.zeros_like(velocity, dtype=np.double).tolist() + if isinstance(velstd, float): + velstd = (np.array(velocity, dtype=np.double)*velstd).tolist() + + super().__init__(x=frequency, y=velocity, yerr=velstd, xerr=None) self._sort_data() self.dc_weight = 1 diff --git a/test/test_groundmodel.py b/test/test_groundmodel.py index 4da5d1a..4e499b5 100644 --- a/test/test_groundmodel.py +++ b/test/test_groundmodel.py @@ -20,7 +20,7 @@ import os import logging -from scipy.io import savemat, loadmat +from scipy.io import loadmat from hypothesis import given, settings import hypothesis.strategies as st import numpy as np diff --git a/test/test_parameterization.py b/test/test_parameterization.py index 0cd37b4..a2a1ecb 100644 --- a/test/test_parameterization.py +++ b/test/test_parameterization.py @@ -19,7 +19,6 @@ import warnings import os -import tarfile as tar import logging import swprepost diff --git a/test/test_suite.py b/test/test_suite.py index 183800e..562ed47 100644 --- a/test/test_suite.py +++ b/test/test_suite.py @@ -23,7 +23,7 @@ import numpy as np import swprepost -from testtools import unittest, TestCase, get_full_path +from testtools import unittest, TestCase logging.basicConfig(level=logging.ERROR) @@ -63,7 +63,7 @@ def test_misfit_range(self): returned = self.gm_suite.misfit_range(nmodels) self.assertEqual(expected, returned) - def test_misfit_range(self): + def test_misfit_repr(self): # GroundModelSuite for nmodels, expected in zip(["all", 1, 5], ["[0.10-1.00]", "[0.10]", "[0.10-0.40]"]): with warnings.catch_warnings(): From bb0dec62212c9a73064e39a37f46956d8a7b8651 Mon Sep 17 00:00:00 2001 From: Joseph vantassel Date: Thu, 18 Jun 2020 15:09:21 -0500 Subject: [PATCH 04/10] :bug: Fix parsing from Geopsy with single profile. --- swprepost/dispersionset.py | 2 ++ swprepost/dispersionsuite.py | 11 +++--- test/test_dispersionsuite.py | 65 ++++++++++++++++++++++++------------ 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/swprepost/dispersionset.py b/swprepost/dispersionset.py index ffa0f57..0ccbca5 100644 --- a/swprepost/dispersionset.py +++ b/swprepost/dispersionset.py @@ -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] diff --git a/swprepost/dispersionsuite.py b/swprepost/dispersionsuite.py index 32cdd7f..888fa28 100644 --- a/swprepost/dispersionsuite.py +++ b/swprepost/dispersionsuite.py @@ -144,13 +144,18 @@ 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": @@ -158,11 +163,7 @@ def from_geopsy(cls, fname, nsets="all", nrayleigh="all", nlove="all", 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), diff --git a/test/test_dispersionsuite.py b/test/test_dispersionsuite.py index a32b92a..071ef86 100644 --- a/test/test_dispersionsuite.py +++ b/test/test_dispersionsuite.py @@ -101,10 +101,31 @@ def compare(fname, models, **kwargs): for mode_number in model[wave]: for attr in model[wave][mode_number]: expected = np.array(model[wave][mode_number][attr]) - returned = getattr(getattr(dc_set, wave)[ - mode_number], attr) - self.assertArrayAlmostEqual( - expected, returned, places=10) + returned = getattr(getattr(dc_set, wave)[mode_number], attr) + self.assertArrayAlmostEqual(expected, returned, places=10) + + # One Set with Two Rayleigh and Two Love Modes + fname = self.full_path+"data/test_dc_mod1_ray2_lov2_shrt.txt" + e1 = {"identifier": 149641, + "misfit": 1.08851, + "love":None, + "rayleigh": {0: {"frequency": [0.15, 64], + "velocity": [1/0.000334532972901842, + 1/0.00917746839997367]}}} + models = [e1] + compare(fname, models, nsets=1, nrayleigh=1, nlove=0) + + # One Set with Two Rayleigh and Two Love Modes + fname = self.full_path+"data/test_dc_mod1_ray2_lov2_shrt.txt" + e1 = {"identifier": 149641, + "misfit": 1.08851, + "love": {0: {"frequency": [0.11, 61], + "velocity": [1/0.0003055565316784, + 1/0.00838314255586564]}}, + "rayleigh": None} + + models = [e1] + compare(fname, models, nsets=1, nrayleigh=0, nlove=1) # One Set with Two Rayleigh and Two Love Modes fname = self.full_path+"data/test_dc_mod1_ray2_lov2_shrt.txt" @@ -312,24 +333,24 @@ def compare(fname, models, **kwargs): models = [e1] compare(fname, models, nsets=20) - def test_write_to_txt(self): - dc_0 = swprepost.DispersionCurve([1, 5, 10, 15], [100, 200, 300, 400]) - dc_1 = swprepost.DispersionCurve([1, 5, 12, 15], [100, 180, 300, 400]) - dc_set_0 = swprepost.DispersionSet(0, misfit=0.0, - rayleigh={0: dc_0, 1: dc_1}, - love={0: dc_1, 1: dc_0}) - dc_set_1 = swprepost.DispersionSet(1, misfit=0.0, - rayleigh={0: dc_1, 1: dc_0}, - love={0: dc_0, 1: dc_1}) - set_list = [dc_set_0, dc_set_1] - expected = swprepost.DispersionSuite.from_list(set_list) - - fname = "dc_suite_expected.dc" - expected.write_to_txt(fname) - returned = swprepost.DispersionSuite.from_geopsy(fname) - os.remove(fname) - - self.assertEqual(expected, returned) + # def test_write_to_txt(self): + # dc_0 = swprepost.DispersionCurve([1, 5, 10, 15], [100, 200, 300, 400]) + # dc_1 = swprepost.DispersionCurve([1, 5, 12, 15], [100, 180, 300, 400]) + # dc_set_0 = swprepost.DispersionSet(0, misfit=0.0, + # rayleigh={0: dc_0, 1: dc_1}, + # love={0: dc_1, 1: dc_0}) + # dc_set_1 = swprepost.DispersionSet(1, misfit=0.0, + # rayleigh={0: dc_1, 1: dc_0}, + # love={0: dc_0, 1: dc_1}) + # set_list = [dc_set_0, dc_set_1] + # expected = swprepost.DispersionSuite.from_list(set_list) + + # fname = "dc_suite_expected.dc" + # expected.write_to_txt(fname) + # returned = swprepost.DispersionSuite.from_geopsy(fname) + # os.remove(fname) + + # self.assertEqual(expected, returned) def test_eq(self): dc = swprepost.DispersionCurve([1,2,3],[10,20,30]) From 62e06d1cdff7ca9fa3a5b0fabca68e86bf932d86 Mon Sep 17 00:00:00 2001 From: Joseph vantassel Date: Thu, 18 Jun 2020 15:14:27 -0500 Subject: [PATCH 05/10] :books: Update readme examples --- examples/basic/ReadmeExamples.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basic/ReadmeExamples.ipynb b/examples/basic/ReadmeExamples.ipynb index 2d864a4..f3e5f5f 100644 --- a/examples/basic/ReadmeExamples.ipynb +++ b/examples/basic/ReadmeExamples.ipynb @@ -109,7 +109,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] From 797d57f63c658dbaa48de77486d52e68b7784a23 Mon Sep 17 00:00:00 2001 From: Joseph vantassel Date: Fri, 19 Jun 2020 18:16:35 -0500 Subject: [PATCH 06/10] :hammer: Fix conflicts in subclasses of Curve.resample --- examples/basic/Targets.ipynb | 4 +- swprepost/curve.py | 36 +++++++++++++++--- swprepost/curveuncertain.py | 74 +++++++++++++++++++++++------------- swprepost/target.py | 6 +-- test/test_curve.py | 28 ++++++++++++++ test/test_curveuncertain.py | 30 +++++++-------- test/test_target.py | 14 +++---- 7 files changed, 131 insertions(+), 61 deletions(-) diff --git a/examples/basic/Targets.ipynb b/examples/basic/Targets.ipynb index d041d3e..2cdbb7f 100644 --- a/examples/basic/Targets.ipynb +++ b/examples/basic/Targets.ipynb @@ -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", @@ -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", diff --git a/swprepost/curve.py b/swprepost/curve.py index 01f9b9b..2edc6de 100644 --- a/swprepost/curve.py +++ b/swprepost/curve.py @@ -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 ---------- @@ -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 @@ -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 ------- @@ -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) @@ -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." \ No newline at end of file diff --git a/swprepost/curveuncertain.py b/swprepost/curveuncertain.py index a053565..57ef4d5 100644 --- a/swprepost/curveuncertain.py +++ b/swprepost/curveuncertain.py @@ -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 ---------- @@ -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. @@ -58,25 +74,18 @@ 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) - - def resample(self, xx, inplace=False, - res_fxn=None, res_fxn_xerr=None, res_fxn_yerr=None): + 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, interp1d_kwargs=None, res_fxn=None, + res_fxn_xerr=None, res_fxn_yerr=None): """Resample curve and its associated uncertainty. Parameters @@ -85,42 +94,53 @@ 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. + interp1d_settings : dict, optional + Settings for use with the `interp1d` function from `scipy`. + See documentation `here + `_ + for details. res_fxn, res_fxn_xerr, res_fxn_yerr : function, 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 + # 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: diff --git a/swprepost/target.py b/swprepost/target.py index 32d8c6b..1bce2a7 100644 --- a/swprepost/target.py +++ b/swprepost/target.py @@ -410,7 +410,7 @@ def _resample(self, xx, domain="wavelength", inplace=False): else: return Target(new_frq, new_vel, new_velstd) - def resample(self, pmin, pmax, pn, res_type="log", domain="wavelength", inplace=False): + def easy_resample(self, pmin, pmax, pn, res_type="log", domain="wavelength", inplace=False): """Resample dispersion curve. Resample dispersion curve over a specific range, using log or @@ -442,7 +442,7 @@ def resample(self, pmin, pmax, pn, res_type="log", domain="wavelength", inplace= Raises ------ NotImplementedError - If `res_type` and/or `domain` are not amoung the options + If `res_type` and/or `domain` are not among the options specified. """ @@ -471,7 +471,7 @@ def vr40(self): """Estimate Rayleigh wave velocity at a wavelength of 40m.""" wavelength = self.wavelength if (max(wavelength) > 40) & (min(wavelength) < 40): - obj = self.resample(pmin=40, pmax=40, pn=1, res_type="linear", + obj = self.easy_resample(pmin=40, pmax=40, pn=1, res_type="linear", domain="wavelength", inplace=False) return float(obj.velocity) else: diff --git a/test/test_curve.py b/test/test_curve.py index 040f65d..c818e9e 100644 --- a/test/test_curve.py +++ b/test/test_curve.py @@ -74,6 +74,34 @@ def test_resample(self): returned = curve._y self.assertArrayEqual(expected, returned) + def test_eq(self): + curve_a = swprepost.Curve(x=[1,2,3], y=[4,5,6]) + curve_b = "I am not a Curve object" + curve_c = swprepost.Curve(x=[2,4,4], y=[4,5,6]) + curve_d = swprepost.Curve(x=[1,2,3,4], y=[1,2,3,7]) + curve_e = swprepost.Curve(x=[1,2,3], y=[4,5,6]) + + self.assertTrue(curve_a != curve_b) + self.assertTrue(curve_a != curve_c) + self.assertTrue(curve_a != curve_b) + self.assertTrue(curve_a != curve_d) + self.assertTrue(curve_a == curve_e) + + def test_str_and_repr(self): + x = [4,5,6] + y = [7,8,9] + curve = swprepost.Curve(x,y) + + # __repr__ + expected = f"Curve(x={curve._x}, y={curve._y})" + returned = curve.__repr__() + self.assertEqual(expected, returned) + + # __str__ + expected = f"Curve with 3 points." + returned = curve.__str__() + self.assertEqual(expected, returned) + if __name__ == "__main__": unittest.main() diff --git a/test/test_curveuncertain.py b/test/test_curveuncertain.py index f32b3d8..63a2d94 100644 --- a/test/test_curveuncertain.py +++ b/test/test_curveuncertain.py @@ -37,33 +37,33 @@ def setUpClass(cls): cls.yerr = np.array([2, 4, 6, 10, 12, 14]) # Define both error terms - cls.ucurve_b = swprepost.CurveUncertain( - x=cls.x, y=cls.y, yerr=cls.yerr, xerr=cls.xerr) + cls.ucurve_b = swprepost.CurveUncertain(x=cls.x, y=cls.y, + yerr=cls.yerr, xerr=cls.xerr) # Define only yerr - cls.ucurve_y = swprepost.CurveUncertain(x=cls.x, y=cls.y, yerr=cls.yerr) + cls.ucurve_y = swprepost.CurveUncertain(x=cls.x, y=cls.y, + yerr=cls.yerr) + + # Define only xerr + cls.ucurve_x = swprepost.CurveUncertain(x=cls.x, y=cls.y, + xerr=cls.xerr) # Define neither cls.ucurve_n = swprepost.CurveUncertain(x=cls.x, y=cls.y) def test_init(self): ucurve = self.ucurve_b - for eattr, rattr in zip(["x", "y", "xerr", "yerr"], ["_x", "_y", "_xerr", "_yerr"]): - expected = getattr(self, eattr) - returned = getattr(ucurve, rattr) + + for ex_attr, re_attr in zip(["x", "y", "xerr", "yerr"], ["_x", "_y", "_xerr", "_yerr"]): + expected = getattr(self, ex_attr) + returned = getattr(ucurve, re_attr) self.assertArrayEqual(expected, returned) - for expected, attr in zip([True, True], ["_isxerr", "_isyerr"]): - returned = getattr(ucurve, attr) - self.assertEqual(expected, returned) ucurve = self.ucurve_y for eattr, rattr in zip(["x", "y", "yerr"], ["_x", "_y", "_yerr"]): expected = getattr(self, eattr) returned = getattr(ucurve, rattr) self.assertArrayEqual(expected, returned) - for expected, attr in zip([False, True], ["_isxerr", "_isyerr"]): - returned = getattr(ucurve, attr) - self.assertEqual(expected, returned) # Define neither ucurve = self.ucurve_n @@ -71,9 +71,6 @@ def test_init(self): expected = getattr(self, eattr) returned = getattr(ucurve, rattr) self.assertArrayEqual(expected, returned) - for expected, attr in zip([False, False], ["_isxerr", "_isyerr"]): - returned = getattr(ucurve, attr) - self.assertEqual(expected, returned) # Inconsistent sizes yerr = [1, 2, 3] @@ -91,7 +88,7 @@ def test_resample(self): self.assertArrayAlmostEqual(np.ones(7), xxerr) self.assertArrayAlmostEqual(np.array([2,4,6,8,10,12,14]), yyerr) - # Neither + # # Neither xx, yy = self.ucurve_n.resample(_xx) self.assertArrayAlmostEqual(_xx, xx) self.assertArrayAlmostEqual(_xx, yy) @@ -108,6 +105,5 @@ def test_resample(self): self.assertArrayAlmostEqual(expected, returned) - if __name__ == "__main__": unittest.main() diff --git a/test/test_target.py b/test/test_target.py index 60d3a0e..22a7720 100644 --- a/test/test_target.py +++ b/test/test_target.py @@ -207,12 +207,12 @@ def test_cut(self): self.assertRaises(NotImplementedError, tar.cut, pmin=2, pmax=4, domain="slowness") - def test_resample(self): + def test_easy_resample(self): # Inplace=False # Linear w/ VelStd fname = self.full_path+"data/test_tar_wstd_linear.csv" tar = swprepost.Target.from_csv(fname) - returned = tar.resample(pmin=0.5, pmax=4.5, pn=5, + returned = tar.easy_resample(pmin=0.5, pmax=4.5, pn=5, res_type='linear', domain="frequency", inplace=False).frequency expected = np.array([0.5, 1.5, 2.5, 3.5, 4.5]) @@ -222,7 +222,7 @@ def test_resample(self): fname = self.full_path+"data/test_tar_wstd_linear.csv" tar = swprepost.Target.from_csv(fname) expected = np.array([2., 2.8, 4.0]) - returned = tar.resample(pmin=2, pmax=4, pn=3, + returned = tar.easy_resample(pmin=2, pmax=4, pn=3, res_type='log', domain="frequency", inplace=False).frequency self.assertArrayAlmostEqual(expected, returned, places=1) @@ -230,7 +230,7 @@ def test_resample(self): # Non-linear w/ VelStd fname = self.full_path+"data/test_tar_wstd_nonlin_0.csv" tar = swprepost.Target.from_csv(fname) - new_tar = tar.resample(pmin=50, pmax=100, pn=5, + new_tar = tar.easy_resample(pmin=50, pmax=100, pn=5, res_type='log', domain="wavelength", inplace=False) expected = np.array([112.5, 118.1, 125.5, 135.6, 150]) @@ -246,7 +246,7 @@ def test_resample(self): tar = swprepost.Target(x, x, x) for pmin, pmax in [(0.1, 0.5), (0.5, 0.1)]: - tar.resample(pmin=pmin, pmax=pmax, pn=5, domain="frequency", + tar.easy_resample(pmin=pmin, pmax=pmax, pn=5, domain="frequency", res_type="linear", inplace=True) expected = np.array([0.1, 0.2, 0.3, 0.4, 0.5]) @@ -255,10 +255,10 @@ def test_resample(self): self.assertArrayAlmostEqual(expected, returned) # Bad pn - self.assertRaises(ValueError, tar.resample, pmin=0.1, pmax=0.5, pn=-1) + self.assertRaises(ValueError, tar.easy_resample, pmin=0.1, pmax=0.5, pn=-1) # Bad res_type - self.assertRaises(NotImplementedError, tar.resample, pmin=0.1, + self.assertRaises(NotImplementedError, tar.easy_resample, pmin=0.1, pmax=0.5, pn=5, res_type="log-spiral") def test_vr40(self): From 6638a59f7200009843ff0014d0af5ffbcdd735fe Mon Sep 17 00:00:00 2001 From: Joseph vantassel Date: Fri, 19 Jun 2020 18:25:28 -0500 Subject: [PATCH 07/10] :art: Minor fixes from codacy --- swprepost/parameterization.py | 14 +++++++------- swprepost/target.py | 2 +- test/perf_dc_reader.py | 2 +- test/perf_gm_reader.py | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/swprepost/parameterization.py b/swprepost/parameterization.py index 0cec54c..140e33c 100644 --- a/swprepost/parameterization.py +++ b/swprepost/parameterization.py @@ -65,8 +65,8 @@ def __init__(self, vp, pr, vs, rh): Parameters ---------- vp, pr, vs, rh : Parameter - Instantitated `Parameter` objects, see :meth: `Parameter - `. + Instantiated `Parameter` objects, see :meth: `Parameter + `. Returns ------- @@ -90,7 +90,7 @@ def __init__(self, vp, pr, vs, rh): @classmethod def from_min_max(cls, vp, pr, vs, rh, wv, factor=2): - """Intilize an instance of the Parameterization class from + """Initialize an instance of the Parameterization class from a minimum and maximum value. This method compromises readability for pure character @@ -114,7 +114,7 @@ def from_min_max(cls, vp, pr, vs, rh, wv, factor=2): Ex. ['FX', value] If type = 'FTL' - Layering follows Fixed Thickness Layinering, the + Layering follows Fixed Thickness Layering, the second argument is the number of layers desired, followed by their thickness, min, max, and bool. @@ -138,11 +138,11 @@ def from_min_max(cls, vp, pr, vs, rh, wv, factor=2): Of the form [min_wave, max_wave] where `min_wave` and `max_wave` are of type `float` or `int` and indicate the minimum and maximum measured wavelength - from the fundemental mode Rayleigh wave disperison. + from the fundamental mode Rayleigh wave disperison. factor : float, optional Factor by which the maximum wavelength is - divided to estimate the maxium depth of profiling, + divided to estimate the maximum depth of profiling, default is 2. Returns @@ -194,7 +194,7 @@ def from_min_max(cls, vp, pr, vs, rh, wv, factor=2): input_arguements["vs"], input_arguements["rh"]) def to_param(self, fname_prefix, version="3", full_version=None): - """Write paramterization to `.param` file that can be imported + """Write parameterization to `.param` file that can be imported into Dinver. Parameters diff --git a/swprepost/target.py b/swprepost/target.py index 1bce2a7..8904dd7 100644 --- a/swprepost/target.py +++ b/swprepost/target.py @@ -789,7 +789,7 @@ def from_target(cls, fname_prefix, version="3"): lines = f.read() if "" not in lines[:10]: raise RuntimeError - except (UnicodeDecodeError, RuntimeError) as e: + except (UnicodeDecodeError, RuntimeError): with open("contents.xml", "r", encoding="utf_16_le") as f: lines = f.read() if "" not in lines[:10]: diff --git a/test/perf_dc_reader.py b/test/perf_dc_reader.py index 8f801ba..760f099 100644 --- a/test/perf_dc_reader.py +++ b/test/perf_dc_reader.py @@ -26,7 +26,7 @@ def main(): fname = full_path+"data/test_dc_mod100_ray2_lov2_full.txt" - suite = swprepost.DispersionSuite.from_geopsy(fname=fname, nsets="all") + swprepost.DispersionSuite.from_geopsy(fname=fname, nsets="all") fname = full_path+"data/.tmp_profiler_run" data = cProfile.run('main()', filename=fname) diff --git a/test/perf_gm_reader.py b/test/perf_gm_reader.py index 87f88fc..6c4ca45 100644 --- a/test/perf_gm_reader.py +++ b/test/perf_gm_reader.py @@ -28,7 +28,7 @@ def main(): fname = full_path+"data/test_gm_mod100.txt" - suite = swprepost.GroundModelSuite.from_geopsy(fname=fname) + swprepost.GroundModelSuite.from_geopsy(fname=fname) fname = full_path+"data/.tmp_profiler_run" From 4825b1ed0a3e8d4b6831e919be2a0cb219c49c15 Mon Sep 17 00:00:00 2001 From: Joseph vantassel Date: Fri, 19 Jun 2020 19:11:26 -0500 Subject: [PATCH 08/10] :art: Various minor code quality improvements --- README.md | 19 +++++++++--------- swprepost/curveuncertain.py | 11 ++++++++--- swprepost/dispersionsuite.py | 20 +++---------------- swprepost/groundmodelsuite.py | 6 +----- swprepost/parameterization.py | 2 +- swprepost/suite.py | 6 +++++- swprepost/target.py | 21 +++++++++----------- test/test_curveuncertain.py | 15 +++++++++++++-- test/test_dispersionsuite.py | 36 +++++++++++++++++------------------ test/test_parameter.py | 1 - test/test_suite.py | 10 ++++------ 11 files changed, 72 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 2203c60..269575f 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ --- - - [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_ @@ -35,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 diff --git a/swprepost/curveuncertain.py b/swprepost/curveuncertain.py index 57ef4d5..c8060ba 100644 --- a/swprepost/curveuncertain.py +++ b/swprepost/curveuncertain.py @@ -84,8 +84,7 @@ def __init__(self, x, y, yerr=None, xerr=None): self._isyerr = False if yerr is None else True self._isxerr = False if xerr is None else True - def resample(self, xx, inplace=False, interp1d_kwargs=None, 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 @@ -101,7 +100,7 @@ def resample(self, xx, inplace=False, interp1d_kwargs=None, res_fxn=None, See documentation `here `_ for details. - res_fxn, res_fxn_xerr, res_fxn_yerr : function, optional + 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. @@ -117,6 +116,12 @@ def resample(self, xx, inplace=False, interp1d_kwargs=None, res_fxn=None, statement. """ + # 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"} diff --git a/swprepost/dispersionsuite.py b/swprepost/dispersionsuite.py index 888fa28..5369b18 100644 --- a/swprepost/dispersionsuite.py +++ b/swprepost/dispersionsuite.py @@ -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", @@ -191,7 +177,7 @@ def from_list(cls, dc_sets, sort=True): Returns ------- DipsersionSuite - Instatiated `DispersionSuite` object. + Instantiated `DispersionSuite` object. """ obj = cls(dc_sets[0]) @@ -219,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) diff --git a/swprepost/groundmodelsuite.py b/swprepost/groundmodelsuite.py index 1efbf3a..2c50f75 100644 --- a/swprepost/groundmodelsuite.py +++ b/swprepost/groundmodelsuite.py @@ -69,10 +69,6 @@ def __init__(self, groundmodel): def gms(self): return self._items - @property - def size(self): - return len(self.gms) - def append(self, groundmodel, sort=True): """Append `GroundModel` object to `GroundModelSuite` object. @@ -95,7 +91,7 @@ def append(self, groundmodel, sort=True): Instead updates the attributes `gms`. """ - super().append(self.check_type(groundmodel), sort=sort) + super()._append(self.check_type(groundmodel), sort=sort) def vs30(self, nbest="all"): """Calculate Vs30 for `GroundModelSuite`. diff --git a/swprepost/parameterization.py b/swprepost/parameterization.py index 140e33c..bfe5e1a 100644 --- a/swprepost/parameterization.py +++ b/swprepost/parameterization.py @@ -369,7 +369,7 @@ def from_param(cls, fname_prefix): lines = f.read() if "" not in lines[:10]: raise RuntimeError - except (UnicodeDecodeError, RuntimeError) as e: + except (UnicodeDecodeError, RuntimeError): with open("contents.xml", "r", encoding="utf_16_le") as f: lines = f.read() if "" not in lines[:10]: diff --git a/swprepost/suite.py b/swprepost/suite.py index ea7df40..b8f5241 100644 --- a/swprepost/suite.py +++ b/swprepost/suite.py @@ -32,7 +32,7 @@ def __init__(self, item): """Create `Suite` from `item`.""" self._items = [item] - def append(self, item, sort=True): + def _append(self, item, sort=True): """Append item to `Suite`.""" self._items.append(item) if sort: @@ -43,6 +43,10 @@ def _sort(self): self._items = [x for _, x in sorted(zip(self.misfits, self._items), key=lambda pair: pair[0])] + @property + def size(self): + return len(self._items) + @property def misfits(self): return [_items.misfit for _items in self._items] diff --git a/swprepost/target.py b/swprepost/target.py index 8904dd7..94b31b1 100644 --- a/swprepost/target.py +++ b/swprepost/target.py @@ -81,7 +81,7 @@ def __init__(self, frequency, velocity, velstd=0.05): # velstd = np.zeros_like(velocity, dtype=np.double).tolist() if isinstance(velstd, float): velstd = (np.array(velocity, dtype=np.double)*velstd).tolist() - + super().__init__(x=frequency, y=velocity, yerr=velstd, xerr=None) self._sort_data() @@ -93,7 +93,7 @@ def _sort_data(self): self._yerr = self._yerr[sort_ids] if self._isyerr else None self._y = self._y[sort_ids] self._x = self._x[sort_ids] - + def _check_new_value(self, value): value = np.array(value, dtype=np.double) if value.shape == self._x.shape: @@ -394,8 +394,8 @@ def _resample(self, xx, domain="wavelength", inplace=False): res_fxn_y = self.resample_function(x, self.velocity, kind="cubic") res_fxn_yerr = self.resample_function(x, self.velstd, kind="cubic") - results = super().resample(xx=xx, inplace=False, res_fxn=res_fxn_y, - res_fxn_yerr=res_fxn_yerr) + results = super().resample(xx=xx, inplace=False, + res_fxn=(res_fxn_y, None, res_fxn_yerr)) xx, new_vel, new_velstd, = results if domain == "frequency": @@ -472,7 +472,7 @@ def vr40(self): wavelength = self.wavelength if (max(wavelength) > 40) & (min(wavelength) < 40): obj = self.easy_resample(pmin=40, pmax=40, pn=1, res_type="linear", - domain="wavelength", inplace=False) + domain="wavelength", inplace=False) return float(obj.velocity) else: warnings.warn("A wavelength of 40m is out of range.") @@ -524,7 +524,7 @@ def from_txt_dinver(cls, fname, version="3"): """ with open(fname, "r") as f: lines = f.readlines() - + frqs, slos, stds = [], [], [] for line in lines: frq, slo, std = line.split("\t") @@ -532,7 +532,6 @@ def from_txt_dinver(cls, fname, version="3"): slos.append(slo) stds.append(std) - frq = np.array(frqs, dtype=np.double) slo = np.array(slos, dtype=np.double) vel = 1/slo @@ -568,7 +567,6 @@ def to_csv(self, fname): for c_frq, c_vel, c_velstd in zip(self.frequency, self.velocity, self.velstd): f.write(f"{c_frq},{c_vel},{c_velstd}\n") - def to_txt_swipp(self, fname): """Write in text format readily accepted by `swprepost`. @@ -687,7 +685,6 @@ def to_target(self, fname_prefix, version="3"): elif version in ["3"]: contents += [" "] - contents += [" ", " false", " 1", @@ -847,7 +844,7 @@ def __str__(self): return f"Target with {len(self.frequency)} frequency/wavelength points" def plot(self, x="frequency", y="velocity", yerr="velstd", ax=None, - figkwargs=None, errorbarkwargs=None): # pragma: no cover + figkwargs=None, errorbarkwargs=None): # pragma: no cover """Plot `Target` information. Parameters @@ -889,14 +886,14 @@ def plot(self, x="frequency", y="velocity", yerr="velstd", ax=None, ax_was_none = True errorbardefaults = dict(color="#000000", label="Exp. Disp. Data", - capsize=2, linestyle="" ) + capsize=2, linestyle="") if errorbarkwargs is None: errorbarkwargs = {} _errorbarkwargs = {**errorbardefaults, **errorbarkwargs} - ax.errorbar(x=getattr(self, x), y=getattr(self, y), + ax.errorbar(x=getattr(self, x), y=getattr(self, y), yerr=getattr(self, yerr), **_errorbarkwargs) if x == "frequency": diff --git a/test/test_curveuncertain.py b/test/test_curveuncertain.py index 63a2d94..9f1dde7 100644 --- a/test/test_curveuncertain.py +++ b/test/test_curveuncertain.py @@ -21,7 +21,7 @@ import numpy as np -from testtools import unittest, TestCase, get_full_path +from testtools import unittest, TestCase import swprepost logging.basicConfig(level=logging.CRITICAL) @@ -88,12 +88,14 @@ def test_resample(self): self.assertArrayAlmostEqual(np.ones(7), xxerr) self.assertArrayAlmostEqual(np.array([2,4,6,8,10,12,14]), yyerr) - # # Neither + # Neither xx, yy = self.ucurve_n.resample(_xx) self.assertArrayAlmostEqual(_xx, xx) self.assertArrayAlmostEqual(_xx, yy) # Inplace = True + + # Both xy = [1,2,3,5,6,7] _xx = [1,2,3,4,5,6,7] ucurve = swprepost.CurveUncertain(xy, xy, yerr=xy, xerr=xy) @@ -104,6 +106,15 @@ def test_resample(self): returned = getattr(ucurve, attr) self.assertArrayAlmostEqual(expected, returned) + # x only + ucurve = swprepost.CurveUncertain(xy, xy, xerr=xy) + ucurve.resample(_xx, inplace=True) + + expected = np.array(_xx) + for attr in ["_x", "_xerr"]: + returned = getattr(ucurve, attr) + self.assertArrayAlmostEqual(expected, returned) + if __name__ == "__main__": unittest.main() diff --git a/test/test_dispersionsuite.py b/test/test_dispersionsuite.py index 071ef86..2e8616a 100644 --- a/test/test_dispersionsuite.py +++ b/test/test_dispersionsuite.py @@ -333,24 +333,24 @@ def compare(fname, models, **kwargs): models = [e1] compare(fname, models, nsets=20) - # def test_write_to_txt(self): - # dc_0 = swprepost.DispersionCurve([1, 5, 10, 15], [100, 200, 300, 400]) - # dc_1 = swprepost.DispersionCurve([1, 5, 12, 15], [100, 180, 300, 400]) - # dc_set_0 = swprepost.DispersionSet(0, misfit=0.0, - # rayleigh={0: dc_0, 1: dc_1}, - # love={0: dc_1, 1: dc_0}) - # dc_set_1 = swprepost.DispersionSet(1, misfit=0.0, - # rayleigh={0: dc_1, 1: dc_0}, - # love={0: dc_0, 1: dc_1}) - # set_list = [dc_set_0, dc_set_1] - # expected = swprepost.DispersionSuite.from_list(set_list) - - # fname = "dc_suite_expected.dc" - # expected.write_to_txt(fname) - # returned = swprepost.DispersionSuite.from_geopsy(fname) - # os.remove(fname) - - # self.assertEqual(expected, returned) + def test_write_to_txt(self): + dc_0 = swprepost.DispersionCurve([1, 5, 10, 15], [100, 200, 300, 400]) + dc_1 = swprepost.DispersionCurve([1, 5, 12, 15], [100, 180, 300, 400]) + dc_set_0 = swprepost.DispersionSet(0, misfit=0.0, + rayleigh={0: dc_0, 1: dc_1}, + love={0: dc_1, 1: dc_0}) + dc_set_1 = swprepost.DispersionSet(1, misfit=0.0, + rayleigh={0: dc_1, 1: dc_0}, + love={0: dc_0, 1: dc_1}) + set_list = [dc_set_0, dc_set_1] + expected = swprepost.DispersionSuite.from_list(set_list) + + fname = "dc_suite_expected.dc" + expected.write_to_txt(fname) + returned = swprepost.DispersionSuite.from_geopsy(fname) + os.remove(fname) + + self.assertEqual(expected, returned) def test_eq(self): dc = swprepost.DispersionCurve([1,2,3],[10,20,30]) diff --git a/test/test_parameter.py b/test/test_parameter.py index f1dbf4c..3ee97db 100644 --- a/test/test_parameter.py +++ b/test/test_parameter.py @@ -17,7 +17,6 @@ """Tests for Parameter class.""" -import os import logging import warnings diff --git a/test/test_suite.py b/test/test_suite.py index 562ed47..a0c016d 100644 --- a/test/test_suite.py +++ b/test/test_suite.py @@ -20,8 +20,6 @@ import logging import warnings -import numpy as np - import swprepost from testtools import unittest, TestCase @@ -40,20 +38,20 @@ def setUpClass(self): gms = [] for _id, _mf in enumerate([0.5, 0.8, 1, 0.3, 0.4, 0.6, 0.7, 0.1, 0.2, 0.1]): gms.append(swprepost.GroundModel(tk, vp, vs, rh, - identifier=_id, misfit=_mf)) + identifier=_id, misfit=_mf)) self.gm_suite = swprepost.GroundModelSuite.from_list(gms) def test_handle_nbest(self): # GroundModelSuite - for nbest, expected in zip([None, "all", 4], [10, 10, 4]): + for nbest, expected in zip([None, "all", 4, 12], [10, 10, 4, 10]): with warnings.catch_warnings(): warnings.simplefilter("ignore") returned = self.gm_suite._handle_nbest(nbest) self.assertEqual(expected, returned) # Bad value - self.assertRaises( - ValueError, self.gm_suite._handle_nbest, nbest="tada") + self.assertRaises(ValueError, self.gm_suite._handle_nbest, + nbest="tada") def test_misfit_range(self): # GroundModelSuite From c6e113809d2cce79f80c2c156676e097f99375f8 Mon Sep 17 00:00:00 2001 From: Joseph vantassel Date: Fri, 19 Jun 2020 19:21:59 -0500 Subject: [PATCH 09/10] :bookmark: Final commit of v0.3.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ef8ecc3..bf01583 100644 --- a/setup.py +++ b/setup.py @@ -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', From 23d71728b6947ed309016c728ae1d15e4ec14ac6 Mon Sep 17 00:00:00 2001 From: Joseph vantassel Date: Fri, 19 Jun 2020 19:21:59 -0500 Subject: [PATCH 10/10] :bookmark: Final commit of v0.3.1 --- setup.py | 2 +- test/test_curve.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index ef8ecc3..bf01583 100644 --- a/setup.py +++ b/setup.py @@ -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', diff --git a/test/test_curve.py b/test/test_curve.py index c818e9e..2be6cc7 100644 --- a/test/test_curve.py +++ b/test/test_curve.py @@ -75,11 +75,11 @@ def test_resample(self): self.assertArrayEqual(expected, returned) def test_eq(self): - curve_a = swprepost.Curve(x=[1,2,3], y=[4,5,6]) + curve_a = swprepost.Curve(x=[1, 2, 3], y=[4, 5, 6]) curve_b = "I am not a Curve object" - curve_c = swprepost.Curve(x=[2,4,4], y=[4,5,6]) - curve_d = swprepost.Curve(x=[1,2,3,4], y=[1,2,3,7]) - curve_e = swprepost.Curve(x=[1,2,3], y=[4,5,6]) + curve_c = swprepost.Curve(x=[2, 4, 4], y=[4, 5, 6]) + curve_d = swprepost.Curve(x=[1, 2, 3, 4], y=[1, 2, 3, 7]) + curve_e = swprepost.Curve(x=[1, 2, 3], y=[4, 5, 6]) self.assertTrue(curve_a != curve_b) self.assertTrue(curve_a != curve_c) @@ -88,10 +88,10 @@ def test_eq(self): self.assertTrue(curve_a == curve_e) def test_str_and_repr(self): - x = [4,5,6] - y = [7,8,9] - curve = swprepost.Curve(x,y) - + x = [4, 5, 6] + y = [7, 8, 9] + curve = swprepost.Curve(x, y) + # __repr__ expected = f"Curve(x={curve._x}, y={curve._y})" returned = curve.__repr__()