From 56d5019b945d267a940e9aeb33efa0c95f3136ef Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 24 Nov 2015 22:09:50 -0800 Subject: [PATCH 01/77] Updates to examples and documentation. --- ...DCfwd.py => Forward_BasicDirectCurrent.py} | 4 ++- .../{Linear.py => Inversion_Linear.py} | 23 +++++++------ SimPEG/Examples/Mesh_QuadTree_Create.py | 18 +++++++++++ SimPEG/Examples/Mesh_ThreeMeshes.py | 24 ++++++++++++++ SimPEG/Examples/__init__.py | 10 +++++- SimPEG/Mesh/TreeMesh.py | 12 ++++--- SimPEG/Utils/__init__.py | 1 + SimPEG/Utils/codeutils.py | 32 +++++++++++++++++++ docs/api_Examples.rst | 11 +++++-- docs/api_Mesh.rst | 18 ++--------- docs/examples/Forward_BasicDirectCurrent.rst | 17 ++++++++++ docs/examples/Inversion_Linear.rst | 17 ++++++++++ docs/examples/Mesh_QuadTree_Create.rst | 17 ++++++++++ docs/examples/Mesh_ThreeMeshes.rst | 17 ++++++++++ tests/examples/test_examples.py | 21 +++++++----- 15 files changed, 201 insertions(+), 41 deletions(-) rename SimPEG/Examples/{DCfwd.py => Forward_BasicDirectCurrent.py} (98%) rename SimPEG/Examples/{Linear.py => Inversion_Linear.py} (78%) create mode 100644 SimPEG/Examples/Mesh_QuadTree_Create.py create mode 100644 SimPEG/Examples/Mesh_ThreeMeshes.py create mode 100644 docs/examples/Forward_BasicDirectCurrent.rst create mode 100644 docs/examples/Inversion_Linear.rst create mode 100644 docs/examples/Mesh_QuadTree_Create.rst create mode 100644 docs/examples/Mesh_ThreeMeshes.rst diff --git a/SimPEG/Examples/DCfwd.py b/SimPEG/Examples/Forward_BasicDirectCurrent.py similarity index 98% rename from SimPEG/Examples/DCfwd.py rename to SimPEG/Examples/Forward_BasicDirectCurrent.py index 33e7aad5..a3c9ff97 100644 --- a/SimPEG/Examples/DCfwd.py +++ b/SimPEG/Examples/Forward_BasicDirectCurrent.py @@ -12,7 +12,6 @@ def run(plotIt=True): tM = Mesh.TensorMesh(sz) # Curvilinear Mesh rM = Mesh.CurvilinearMesh(Utils.meshutils.exampleLrmGrid(sz,'rotate')) - # Step2: Direct Current (DC) operator def DCfun(mesh, pts): D = mesh.faceDiv @@ -39,6 +38,7 @@ def run(plotIt=True): phirM = AinvrM*rhsrM if not plotIt: return + #Step4: Making Figure fig, axes = plt.subplots(1,2,figsize=(12*1.2,4*1.2)) label = ["(a)", "(b)"] @@ -69,7 +69,9 @@ def run(plotIt=True): else: axes[i].set_ylabel(" ") axes[i].set_xlabel("x") + plt.show() if __name__ == '__main__': + Utils._makeExample(__file__) run() diff --git a/SimPEG/Examples/Linear.py b/SimPEG/Examples/Inversion_Linear.py similarity index 78% rename from SimPEG/Examples/Linear.py rename to SimPEG/Examples/Inversion_Linear.py index b065682b..2446bd58 100644 --- a/SimPEG/Examples/Linear.py +++ b/SimPEG/Examples/Inversion_Linear.py @@ -23,7 +23,9 @@ class LinearProblem(Problem.BaseProblem): return self.G.T.dot(v) -def run(N, plotIt=True): +def run(N=100, plotIt=True): + np.random.seed(1) + mesh = Mesh.TensorMesh([N]) nk = 20 @@ -52,7 +54,7 @@ def run(N, plotIt=True): reg = Regularization.Tikhonov(mesh) dmis = DataMisfit.l2_DataMisfit(survey) - opt = Optimization.InexactGaussNewton(maxIter=20) + opt = Optimization.InexactGaussNewton(maxIter=35) invProb = InvProblem.BaseInvProblem(dmis, reg, opt) beta = Directives.BetaSchedule() betaest = Directives.BetaEstimate_ByEig() @@ -63,16 +65,19 @@ def run(N, plotIt=True): if plotIt: import matplotlib.pyplot as plt - plt.figure(1) - for i in range(prob.G.shape[0]): - plt.plot(prob.G[i,:]) - plt.figure(2) - plt.plot(M.vectorCCx, survey.mtrue, 'b-') - plt.plot(M.vectorCCx, mrec, 'r-') + fig, axes = plt.subplots(1,2,figsize=(12*1.2,4*1.2)) + for i in range(prob.G.shape[0]): + axes[0].plot(prob.G[i,:]) + axes[0].set_title('Columns of matrix G') + + axes[1].plot(M.vectorCCx, survey.mtrue, 'b-') + axes[1].plot(M.vectorCCx, mrec, 'r-') + axes[1].legend(('True Model', 'Recovered Model')) plt.show() return prob, survey, mesh, mrec if __name__ == '__main__': - run(100) + Utils._makeExample(__file__) + run() diff --git a/SimPEG/Examples/Mesh_QuadTree_Create.py b/SimPEG/Examples/Mesh_QuadTree_Create.py new file mode 100644 index 00000000..f4417855 --- /dev/null +++ b/SimPEG/Examples/Mesh_QuadTree_Create.py @@ -0,0 +1,18 @@ +from SimPEG import * + +def run(plotIt=True): + from SimPEG import Mesh, np + M = Mesh.TreeMesh([32,32]) + M.refine(3) + def function(cell): + xyz = cell.center + for i in range(3): + if np.abs(np.sin(xyz[0]*np.pi*2)*0.5 + 0.5 - xyz[1]) < 0.2*i: + return 6-i + return 0 + M.refine(function); + if plotIt: M.plotGrid(showIt=True) + +if __name__ == '__main__': + Utils._makeExample(__file__) + run() diff --git a/SimPEG/Examples/Mesh_ThreeMeshes.py b/SimPEG/Examples/Mesh_ThreeMeshes.py new file mode 100644 index 00000000..2fc7fa8d --- /dev/null +++ b/SimPEG/Examples/Mesh_ThreeMeshes.py @@ -0,0 +1,24 @@ +from SimPEG import * + +def run(plotIt=True): + sz = [16,16] + tM = Mesh.TensorMesh(sz) + qM = Mesh.TreeMesh(sz) + qM.refine(lambda cell: 4 if np.sqrt(((np.r_[cell.center]-0.5)**2).sum()) < 0.4 else 3) + rM = Mesh.CurvilinearMesh(Utils.meshutils.exampleLrmGrid(sz,'rotate')) + + if plotIt: + import matplotlib.pyplot as plt + fig, axes = plt.subplots(1,3,figsize=(14,5)) + opts = {} + tM.plotGrid(ax=axes[0], **opts) + axes[0].set_title('TensorMesh') + qM.plotGrid(ax=axes[1], **opts) + axes[1].set_title('TreeMesh') + rM.plotGrid(ax=axes[2], **opts) + axes[2].set_title('CurvilinearMesh') + plt.show() + +if __name__ == '__main__': + Utils._makeExample(__file__) + run() diff --git a/SimPEG/Examples/__init__.py b/SimPEG/Examples/__init__.py index 3fec0b99..53b1f9a8 100644 --- a/SimPEG/Examples/__init__.py +++ b/SimPEG/Examples/__init__.py @@ -1 +1,9 @@ -import Linear, DCfwd +# This will import everything in the directory into this file +from os import path as p +from glob import glob +__all__ = [] +for x in glob(p.join(p.dirname(__file__), '*.py')): + if not p.basename(x).startswith('__'): + __import__(p.basename(x)[:-3], globals(), locals()) + __all__ += [p.basename(x)] +del glob, p, x diff --git a/SimPEG/Mesh/TreeMesh.py b/SimPEG/Mesh/TreeMesh.py index 1e6c91c0..69a44fdc 100644 --- a/SimPEG/Mesh/TreeMesh.py +++ b/SimPEG/Mesh/TreeMesh.py @@ -1960,7 +1960,7 @@ class TreeMesh(BaseTensorMesh, InnerProducts): def plotGrid(self, ax=None, showIt=False, grid=True, - cells=True, cellLine=False, + cells=False, cellLine=False, nodes=False, facesX=False, facesY=False, facesZ=False, edgesX=False, edgesY=False, edgesZ=False): @@ -2009,6 +2009,8 @@ class TreeMesh(BaseTensorMesh, InnerProducts): if facesY: ax.plot(self._gridFy[self._hangingFy.keys(),0], self._gridFy[self._hangingFy.keys(),1], 'gs', ms=10, mfc='none', mec='g') ax.plot(self._gridFy[:,0], self._gridFy[:,1], 'g^') + ax.set_xlabel('x1') + ax.set_ylabel('x2') elif self.dim == 3: if cells: ax.plot(self.gridCC[:,0], self.gridCC[:,1], 'r.', zs=self.gridCC[:,2]) @@ -2056,7 +2058,6 @@ class TreeMesh(BaseTensorMesh, InnerProducts): ind = [key, hf[0]] ax.plot(self._gridEx[ind,0], self._gridEx[ind,1], 'k:', zs=self._gridEx[ind,2]) - if edgesY: ax.plot(self._gridEy[:,0], self._gridEy[:,1], 'k<', zs=self._gridEy[:,2]) ax.plot(self._gridEy[self._hangingEy.keys(),0], self._gridEy[self._hangingEy.keys(),1], 'ks', ms=10, mfc='none', mec='k', zs=self._gridEy[self._hangingEy.keys(),2]) @@ -2072,7 +2073,10 @@ class TreeMesh(BaseTensorMesh, InnerProducts): for hf in self._hangingEz[key]: ind = [key, hf[0]] ax.plot(self._gridEz[ind,0], self._gridEz[ind,1], 'k:', zs=self._gridEz[ind,2]) - + ax.set_xlabel('x1') + ax.set_ylabel('x2') + ax.set_zlabel('x3') + ax.grid(True) if showIt:plt.show() def plotImage(self, I, ax=None, showIt=True, grid=False): @@ -2191,7 +2195,7 @@ class Cell(object): @property def center(self): if getattr(self, '_center', None) is None: - self._center = self.mesh._cellC(self._pointer) + self._center = np.array(self.mesh._cellC(self._pointer)) return self._center @property def h(self): return self.mesh._cellH(self._pointer) diff --git a/SimPEG/Utils/__init__.py b/SimPEG/Utils/__init__.py index 5280ae79..67e1b117 100644 --- a/SimPEG/Utils/__init__.py +++ b/SimPEG/Utils/__init__.py @@ -1,5 +1,6 @@ from matutils import * from codeutils import * +from codeutils import _makeExample from meshutils import exampleLrmGrid, meshTensor, closestPoints, readUBCTensorMesh, writeUBCTensorMesh, writeUBCTensorModel, readVTRFile, writeVTRFile from curvutils import volTetra, faceInfo, indexCube from interputils import interpmat diff --git a/SimPEG/Utils/codeutils.py b/SimPEG/Utils/codeutils.py index 4a9a28a7..fef34282 100644 --- a/SimPEG/Utils/codeutils.py +++ b/SimPEG/Utils/codeutils.py @@ -227,3 +227,35 @@ def requires(var): return requiresVarWrapper return requiresVar + +def _makeExample(filePath): + + import os + name = filePath.split(os.path.sep)[-1][:-3] + out = """.. _examples_%s: + +.. ------------------------------ .. +.. THIS FILE IS AUTO GENEREATED .. +.. ------------------------------ .. + +%s +%s + +.. plot:: + + from SimPEG import Examples + Examples.%s.run() + +.. literalinclude:: ../../SimPEG/Examples/%s.py + :language: python + :linenos: +"""%(name,name.replace('_',' '),'='*len(name),name,name) + + rst = os.path.sep.join((filePath.split(os.path.sep)[:-3] + ['docs', 'examples', name + '.rst'])) + + f = open(rst, 'w') + f.write(out) + f.close() + + + diff --git a/docs/api_Examples.rst b/docs/api_Examples.rst index 214dd8e1..68589fb3 100644 --- a/docs/api_Examples.rst +++ b/docs/api_Examples.rst @@ -3,8 +3,15 @@ Examples ******** -Forward problem -=============== +.. toctree:: + :maxdepth: 1 + :glob: + + examples/* + + +External Notebooks +================== * `Example 1: Direct Current `_ * `Example 2: Seismic-Acoustic `_ diff --git a/docs/api_Mesh.rst b/docs/api_Mesh.rst index 7bf398b1..a7f7abae 100644 --- a/docs/api_Mesh.rst +++ b/docs/api_Mesh.rst @@ -23,23 +23,9 @@ the implementations. .. plot:: - from SimPEG import Mesh, Utils, np - import matplotlib.pyplot as plt - sz = [10,10] - tM = Mesh.TensorMesh(sz) - qM = Mesh.TreeMesh(sz) - qM.refine(lambda X: 1 if np.sqrt(((X-0.5)**2).sum()) < 0.3 else 0) - rM = Mesh.CurvilinearMesh(Utils.meshutils.exampleLrmGrid(sz,'rotate')) + from SimPEG import Examples + Examples.Mesh_ThreeMeshes.run() - fig, axes = plt.subplots(1,3,figsize=(14,5)) - opts = {} - tM.plotGrid(ax=axes[0], **opts) - axes[0].set_title('TensorMesh') - qM.plotGrid(ax=axes[1], **opts) - axes[1].set_title('TreeMesh') - rM.plotGrid(ax=axes[2], **opts) - axes[2].set_title('CurvilinearMesh') - plt.show() Variable Locations and Terminology diff --git a/docs/examples/Forward_BasicDirectCurrent.rst b/docs/examples/Forward_BasicDirectCurrent.rst new file mode 100644 index 00000000..6fc816e7 --- /dev/null +++ b/docs/examples/Forward_BasicDirectCurrent.rst @@ -0,0 +1,17 @@ +.. _examples_Forward_BasicDirectCurrent: + +.. ------------------------------ .. +.. THIS FILE IS AUTO GENEREATED .. +.. ------------------------------ .. + +Forward BasicDirectCurrent +========================== + +.. plot:: + + from SimPEG import Examples + Examples.Forward_BasicDirectCurrent.run() + +.. literalinclude:: ../../SimPEG/Examples/Forward_BasicDirectCurrent.py + :language: python + :linenos: diff --git a/docs/examples/Inversion_Linear.rst b/docs/examples/Inversion_Linear.rst new file mode 100644 index 00000000..637315ae --- /dev/null +++ b/docs/examples/Inversion_Linear.rst @@ -0,0 +1,17 @@ +.. _examples_Inversion_Linear: + +.. ------------------------------ .. +.. THIS FILE IS AUTO GENEREATED .. +.. ------------------------------ .. + +Inversion Linear +================ + +.. plot:: + + from SimPEG import Examples + Examples.Inversion_Linear.run() + +.. literalinclude:: ../../SimPEG/Examples/Inversion_Linear.py + :language: python + :linenos: diff --git a/docs/examples/Mesh_QuadTree_Create.rst b/docs/examples/Mesh_QuadTree_Create.rst new file mode 100644 index 00000000..644593d5 --- /dev/null +++ b/docs/examples/Mesh_QuadTree_Create.rst @@ -0,0 +1,17 @@ +.. _examples_Mesh_QuadTree_Create: + +.. ------------------------------ .. +.. THIS FILE IS AUTO GENEREATED .. +.. ------------------------------ .. + +Mesh QuadTree Create +==================== + +.. plot:: + + from SimPEG import Examples + Examples.Mesh_QuadTree_Create.run() + +.. literalinclude:: ../../SimPEG/Examples/Mesh_QuadTree_Create.py + :language: python + :linenos: diff --git a/docs/examples/Mesh_ThreeMeshes.rst b/docs/examples/Mesh_ThreeMeshes.rst new file mode 100644 index 00000000..948398a7 --- /dev/null +++ b/docs/examples/Mesh_ThreeMeshes.rst @@ -0,0 +1,17 @@ +.. _examples_Mesh_ThreeMeshes: + +.. ------------------------------ .. +.. THIS FILE IS AUTO GENEREATED .. +.. ------------------------------ .. + +Mesh ThreeMeshes +================ + +.. plot:: + + from SimPEG import Examples + Examples.Mesh_ThreeMeshes.run() + +.. literalinclude:: ../../SimPEG/Examples/Mesh_ThreeMeshes.py + :language: python + :linenos: diff --git a/tests/examples/test_examples.py b/tests/examples/test_examples.py index 1fcf05f5..baddb6db 100644 --- a/tests/examples/test_examples.py +++ b/tests/examples/test_examples.py @@ -1,17 +1,22 @@ import unittest import sys -from SimPEG.Examples import Linear, DCfwd +from SimPEG import Examples import numpy as np -class TestLinear(unittest.TestCase): - def test_running(self): - Linear.run(100, plotIt=False) +def get_test(test): + def func(self): + print '\nTesting %s.run(plotIt=False)\n'%test + getattr(Examples, test).run(plotIt=False) self.assertTrue(True) + return func +attrs = dict() +tests = [_ for _ in dir(Examples) if not _.startswith('_')] +for test in tests: + attrs['test_'+test] = get_test(test) + + +TestExamples = type('TestExamples', (unittest.TestCase,), attrs) -class TestDCfwd(unittest.TestCase): - def test_running(self): - DCfwd.run(plotIt=False) - self.assertTrue(True) if __name__ == '__main__': unittest.main() From bcfe9040154812fe4ed995038dca865fda387ab3 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Wed, 25 Nov 2015 08:26:27 -0800 Subject: [PATCH 02/77] remove test from the name in examples. --- tests/examples/test_examples.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/examples/test_examples.py b/tests/examples/test_examples.py index baddb6db..48cbaa65 100644 --- a/tests/examples/test_examples.py +++ b/tests/examples/test_examples.py @@ -3,7 +3,7 @@ import sys from SimPEG import Examples import numpy as np -def get_test(test): +def get(test): def func(self): print '\nTesting %s.run(plotIt=False)\n'%test getattr(Examples, test).run(plotIt=False) @@ -12,8 +12,8 @@ def get_test(test): attrs = dict() tests = [_ for _ in dir(Examples) if not _.startswith('_')] for test in tests: - attrs['test_'+test] = get_test(test) - + attrs['test_'+test] = get(test) +del get, tests, _ TestExamples = type('TestExamples', (unittest.TestCase,), attrs) From 06c66f6392ce4bd2e4517833df970f99d1c92982 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Wed, 25 Nov 2015 13:27:35 -0800 Subject: [PATCH 03/77] Addresses #183: Import errors for tree mesh --- SimPEG/Mesh/TreeMesh.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/SimPEG/Mesh/TreeMesh.py b/SimPEG/Mesh/TreeMesh.py index 1e6c91c0..f7a438ce 100644 --- a/SimPEG/Mesh/TreeMesh.py +++ b/SimPEG/Mesh/TreeMesh.py @@ -96,7 +96,13 @@ from mpl_toolkits.mplot3d import Axes3D import matplotlib.colors as colors import matplotlib.cm as cmx -import TreeUtils +try: + import TreeUtils + _IMPORT_TREEUTILS = True +except Exception, e: + _IMPORT_TREEUTILS = False + + from InnerProducts import InnerProducts from TensorMesh import TensorMesh, BaseTensorMesh import time @@ -108,6 +114,8 @@ class TreeMesh(BaseTensorMesh, InnerProducts): _meshType = 'TREE' def __init__(self, h, x0=None, levels=None): + if not _IMPORT_TREEUTILS: + raise Exception('Could not import the Cython code to run the TreeMesh Try:.\n\npython setup.py build_ext --inplace') assert type(h) is list, 'h must be a list' assert len(h) in [2,3], "There is only support for TreeMesh in 2D or 3D." From be3667b4abee8f19cc4ae1a23663ec083bf056c9 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Wed, 25 Nov 2015 16:03:08 -0800 Subject: [PATCH 04/77] New Examples - Put all examples in same directory - Make a single test - use __init__.py to create the docs automatically --- .travis.yml | 1 - SimPEG/EM/Examples/__init__.py | 1 - .../EM_FDEM_1D_Inversion.py} | 7 +++ .../FLOW_Richards_1D_Celia1990.py} | 34 +++++++++++ SimPEG/Examples/Forward_BasicDirectCurrent.py | 1 - SimPEG/Examples/Inversion_Linear.py | 53 +++++++++------- SimPEG/Examples/Mesh_Basic_PlotImage.py | 46 ++++++++++++++ ...esh_ThreeMeshes.py => Mesh_Basic_Types.py} | 8 ++- ...ee_Create.py => Mesh_QuadTree_Creation.py} | 14 ++++- SimPEG/Examples/Mesh_QuadTree_HangingNodes.py | 32 ++++++++++ SimPEG/Examples/Mesh_Tensor_Creation.py | 35 +++++++++++ SimPEG/Examples/__init__.py | 60 ++++++++++++++++++- SimPEG/FLOW/Examples/__init__.py | 1 - SimPEG/Mesh/TreeMesh.py | 39 +++++++----- SimPEG/Utils/__init__.py | 1 - SimPEG/Utils/codeutils.py | 32 ---------- docs/examples/EM_FDEM_1D_Inversion.rst | 26 ++++++++ docs/examples/FLOW_Richards_1D_Celia1990.rst | 52 ++++++++++++++++ docs/examples/Forward_BasicDirectCurrent.rst | 10 +++- docs/examples/Inversion_Linear.rst | 19 ++++-- docs/examples/Mesh_Basic_PlotImage.rst | 27 +++++++++ docs/examples/Mesh_Basic_Types.rst | 26 ++++++++ docs/examples/Mesh_QuadTree_Create.rst | 17 ------ docs/examples/Mesh_QuadTree_Creation.rst | 31 ++++++++++ docs/examples/Mesh_QuadTree_HangingNodes.rst | 31 ++++++++++ docs/examples/Mesh_Tensor_Creation.rst | 43 +++++++++++++ docs/examples/Mesh_ThreeMeshes.rst | 17 ------ tests/em/examples/__init__.py | 11 ---- tests/em/examples/test_Examples.py | 10 ---- tests/flow/test_examples.py | 12 ---- 30 files changed, 543 insertions(+), 154 deletions(-) delete mode 100644 SimPEG/EM/Examples/__init__.py rename SimPEG/{EM/Examples/CylInversion.py => Examples/EM_FDEM_1D_Inversion.py} (96%) rename SimPEG/{FLOW/Examples/Celia1990.py => Examples/FLOW_Richards_1D_Celia1990.py} (54%) create mode 100644 SimPEG/Examples/Mesh_Basic_PlotImage.py rename SimPEG/Examples/{Mesh_ThreeMeshes.py => Mesh_Basic_Types.py} (82%) rename SimPEG/Examples/{Mesh_QuadTree_Create.py => Mesh_QuadTree_Creation.py} (50%) create mode 100644 SimPEG/Examples/Mesh_QuadTree_HangingNodes.py create mode 100644 SimPEG/Examples/Mesh_Tensor_Creation.py delete mode 100644 SimPEG/FLOW/Examples/__init__.py create mode 100644 docs/examples/EM_FDEM_1D_Inversion.rst create mode 100644 docs/examples/FLOW_Richards_1D_Celia1990.rst create mode 100644 docs/examples/Mesh_Basic_PlotImage.rst create mode 100644 docs/examples/Mesh_Basic_Types.rst delete mode 100644 docs/examples/Mesh_QuadTree_Create.rst create mode 100644 docs/examples/Mesh_QuadTree_Creation.rst create mode 100644 docs/examples/Mesh_QuadTree_HangingNodes.rst create mode 100644 docs/examples/Mesh_Tensor_Creation.rst delete mode 100644 docs/examples/Mesh_ThreeMeshes.rst delete mode 100644 tests/em/examples/__init__.py delete mode 100644 tests/em/examples/test_Examples.py delete mode 100644 tests/flow/test_examples.py diff --git a/.travis.yml b/.travis.yml index c2c672bf..b9826bab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ python: sudo: false env: - - TEST_DIR=tests/em/examples - TEST_DIR=tests/em/fdem/forward - TEST_DIR=tests/em/fdem/inverse/derivs - TEST_DIR=tests/em/fdem/inverse/adjoint diff --git a/SimPEG/EM/Examples/__init__.py b/SimPEG/EM/Examples/__init__.py deleted file mode 100644 index eb36678d..00000000 --- a/SimPEG/EM/Examples/__init__.py +++ /dev/null @@ -1 +0,0 @@ -import CylInversion diff --git a/SimPEG/EM/Examples/CylInversion.py b/SimPEG/Examples/EM_FDEM_1D_Inversion.py similarity index 96% rename from SimPEG/EM/Examples/CylInversion.py rename to SimPEG/Examples/EM_FDEM_1D_Inversion.py index cfcfcfc1..fdf2750e 100644 --- a/SimPEG/EM/Examples/CylInversion.py +++ b/SimPEG/Examples/EM_FDEM_1D_Inversion.py @@ -4,6 +4,13 @@ from scipy.constants import mu_0 import matplotlib.pyplot as plt def run(plotIt=True): + """ + EM: FDEM: 1D: Inversion + ======================= + + Here we will create and run a FDEM 1D inversion. + + """ cs, ncx, ncz, npad = 5., 25, 15, 15 hx = [(cs,ncx), (cs,npad,1.3)] diff --git a/SimPEG/FLOW/Examples/Celia1990.py b/SimPEG/Examples/FLOW_Richards_1D_Celia1990.py similarity index 54% rename from SimPEG/FLOW/Examples/Celia1990.py rename to SimPEG/Examples/FLOW_Richards_1D_Celia1990.py index 24ae82a6..7c663e6b 100644 --- a/SimPEG/FLOW/Examples/Celia1990.py +++ b/SimPEG/Examples/FLOW_Richards_1D_Celia1990.py @@ -3,6 +3,39 @@ from SimPEG.FLOW import Richards import matplotlib.pyplot as plt def run(plotIt=True): + """ + FLOW: Richards: 1D: Celia1990 + ============================= + + There are two different forms of Richards equation that differ + on how they deal with the non-linearity in the time-stepping term. + + The most fundamental form, referred to as the + 'mixed'-form of Richards Equation Celia1990_ + + .. math:: + + \\frac{\partial \\theta(\psi)}{\partial t} - \\nabla \cdot k(\psi) \\nabla \psi - \\frac{\partial k(\psi)}{\partial z} = 0 + \quad \psi \in \Omega + + where \\\\(\\\\theta\\\\) is water content, and \\\\(\\\\psi\\\\) is pressure head. + This formulation of Richards equation is called the + 'mixed'-form because the equation is parameterized in \\\\(\\\\psi\\\\) + but the time-stepping is in terms of \\\\(\\\\theta\\\\). + + As noted in Celia1990_ the 'head'-based form of Richards + equation can be written in the continuous form as: + + .. math:: + + \\frac{\partial \\theta}{\partial \psi}\\frac{\partial \psi}{\partial t} - \\nabla \cdot k(\psi) \\nabla \psi - \\frac{\partial k(\psi)}{\partial z} = 0 \quad \psi \in \Omega + + However, it can be shown that this does not conserve mass in the discrete formulation. + + Here we reproduce the results from Celia1990_ demonstrating the head-based formulation and the mixed-formulation. + + .. _Celia1990: http://www.webpages.uidaho.edu/ch/papers/Celia.pdf + """ M = Mesh.TensorMesh([np.ones(40)]) M.setCellGradBC('dirichlet') params = Richards.Empirical.HaverkampParams().celia1990 @@ -47,6 +80,7 @@ def run(plotIt=True): plt.xlabel('Depth, cm') plt.ylabel('Pressure Head, cm') plt.legend(('$\Delta t$ = 10 sec','$\Delta t$ = 30 sec','$\Delta t$ = 120 sec')) + plt.show() if __name__ == '__main__': run() diff --git a/SimPEG/Examples/Forward_BasicDirectCurrent.py b/SimPEG/Examples/Forward_BasicDirectCurrent.py index a3c9ff97..06c9e79e 100644 --- a/SimPEG/Examples/Forward_BasicDirectCurrent.py +++ b/SimPEG/Examples/Forward_BasicDirectCurrent.py @@ -73,5 +73,4 @@ def run(plotIt=True): if __name__ == '__main__': - Utils._makeExample(__file__) run() diff --git a/SimPEG/Examples/Inversion_Linear.py b/SimPEG/Examples/Inversion_Linear.py index 2446bd58..a8a0eddc 100644 --- a/SimPEG/Examples/Inversion_Linear.py +++ b/SimPEG/Examples/Inversion_Linear.py @@ -1,29 +1,37 @@ from SimPEG import * -class LinearSurvey(Survey.BaseSurvey): - def projectFields(self, u): - return u - -class LinearProblem(Problem.BaseProblem): - """docstring for LinearProblem""" - - surveyPair = LinearSurvey - - def __init__(self, mesh, G, **kwargs): - Problem.BaseProblem.__init__(self, mesh, **kwargs) - self.G = G - - def fields(self, m, u=None): - return self.G.dot(m) - - def Jvec(self, m, v, u=None): - return self.G.dot(v) - - def Jtvec(self, m, v, u=None): - return self.G.T.dot(v) - def run(N=100, plotIt=True): + """ + Inversion: Linear Problem + ========================= + + Here we go over the basics of creating a linear problem and inversion. + + """ + + class LinearSurvey(Survey.BaseSurvey): + def projectFields(self, u): + return u + + class LinearProblem(Problem.BaseProblem): + + surveyPair = LinearSurvey + + def __init__(self, mesh, G, **kwargs): + Problem.BaseProblem.__init__(self, mesh, **kwargs) + self.G = G + + def fields(self, m, u=None): + return self.G.dot(m) + + def Jvec(self, m, v, u=None): + return self.G.dot(v) + + def Jtvec(self, m, v, u=None): + return self.G.T.dot(v) + + np.random.seed(1) mesh = Mesh.TensorMesh([N]) @@ -79,5 +87,4 @@ def run(N=100, plotIt=True): return prob, survey, mesh, mrec if __name__ == '__main__': - Utils._makeExample(__file__) run() diff --git a/SimPEG/Examples/Mesh_Basic_PlotImage.py b/SimPEG/Examples/Mesh_Basic_PlotImage.py new file mode 100644 index 00000000..3154f281 --- /dev/null +++ b/SimPEG/Examples/Mesh_Basic_PlotImage.py @@ -0,0 +1,46 @@ +from SimPEG import * + +def run(plotIt=True): + """ + Mesh: Basic: PlotImage + ====================== + + You can use M.PlotImage to plot images on all of the Meshes. + + + """ + M = Mesh.TensorMesh([32,32]) + v = Utils.ModelBuilder.randomModel(M.vnC, seed=789) + v = Utils.mkvc(v) + + O = Mesh.TreeMesh([32,32]) + O.refine(1) + def function(cell): + if (cell.center[0] < 0.75 and cell.center[0] > 0.25 and + cell.center[1] < 0.75 and cell.center[1] > 0.25):return 5 + if (cell.center[0] < 0.9 and cell.center[0] > 0.1 and + cell.center[1] < 0.9 and cell.center[1] > 0.1):return 4 + return 3 + O.refine(function) + + P = M.getInterpolationMat(O.gridCC, 'CC') + + ov = P * v + + if plotIt: + import matplotlib.pyplot as plt + + fig, axes = plt.subplots(1,2,figsize=(10,5)) + + out = M.plotImage(v, grid=True, ax=axes[0]) + cb = plt.colorbar(out[0], ax=axes[0]); cb.set_label("Random Field") + axes[0].set_title('TensorMesh') + + out = O.plotImage(ov, grid=True, ax=axes[1], clim=[0,1]) + cb = plt.colorbar(out[0], ax=axes[1]); cb.set_label("Random Field") + axes[1].set_title('TreeMesh') + + plt.show() + +if __name__ == '__main__': + run() diff --git a/SimPEG/Examples/Mesh_ThreeMeshes.py b/SimPEG/Examples/Mesh_Basic_Types.py similarity index 82% rename from SimPEG/Examples/Mesh_ThreeMeshes.py rename to SimPEG/Examples/Mesh_Basic_Types.py index 2fc7fa8d..430fe698 100644 --- a/SimPEG/Examples/Mesh_ThreeMeshes.py +++ b/SimPEG/Examples/Mesh_Basic_Types.py @@ -1,6 +1,13 @@ from SimPEG import * def run(plotIt=True): + """ + Mesh: Basic: Types + ================== + + Here we show SimPEG used to create three different types of meshes. + + """ sz = [16,16] tM = Mesh.TensorMesh(sz) qM = Mesh.TreeMesh(sz) @@ -20,5 +27,4 @@ def run(plotIt=True): plt.show() if __name__ == '__main__': - Utils._makeExample(__file__) run() diff --git a/SimPEG/Examples/Mesh_QuadTree_Create.py b/SimPEG/Examples/Mesh_QuadTree_Creation.py similarity index 50% rename from SimPEG/Examples/Mesh_QuadTree_Create.py rename to SimPEG/Examples/Mesh_QuadTree_Creation.py index f4417855..ede69a63 100644 --- a/SimPEG/Examples/Mesh_QuadTree_Create.py +++ b/SimPEG/Examples/Mesh_QuadTree_Creation.py @@ -1,7 +1,18 @@ from SimPEG import * def run(plotIt=True): - from SimPEG import Mesh, np + """ + Mesh: QuadTree: Creation + ======================== + + You can give the refine method a function, which is evaluated on every cell + of the TreeMesh. + + Occasionally it is useful to initially refine to a constant level + (e.g. 3 in this 32x32 mesh). This means the function is first evaluated + on an 8x8 mesh (2^3). + + """ M = Mesh.TreeMesh([32,32]) M.refine(3) def function(cell): @@ -14,5 +25,4 @@ def run(plotIt=True): if plotIt: M.plotGrid(showIt=True) if __name__ == '__main__': - Utils._makeExample(__file__) run() diff --git a/SimPEG/Examples/Mesh_QuadTree_HangingNodes.py b/SimPEG/Examples/Mesh_QuadTree_HangingNodes.py new file mode 100644 index 00000000..11726995 --- /dev/null +++ b/SimPEG/Examples/Mesh_QuadTree_HangingNodes.py @@ -0,0 +1,32 @@ +from SimPEG import * + +def run(plotIt=True): + """ + Mesh: QuadTree: Hanging Nodes + ============================= + + You can give the refine method a function, which is evaluated on every cell + of the TreeMesh. + + Occasionally it is useful to initially refine to a constant level + (e.g. 3 in this 32x32 mesh). This means the function is first evaluated + on an 8x8 mesh (2^3). + + """ + M = Mesh.TreeMesh([8,8]) + def function(cell): + xyz = cell.center + dist = ((xyz - [0.25,0.25])**2).sum()**0.5 + if dist < 0.25: + return 3 + return 2 + M.refine(function); + M.number() + if plotIt: + import matplotlib.pyplot as plt + M.plotGrid(nodes=True, cells=True, facesX=True) + plt.legend(('Grid', 'Cell Centers', 'Nodes', 'Hanging Nodes', 'X faces', 'Hanging X faces')) + plt.show() + +if __name__ == '__main__': + run() diff --git a/SimPEG/Examples/Mesh_Tensor_Creation.py b/SimPEG/Examples/Mesh_Tensor_Creation.py new file mode 100644 index 00000000..31ad3d69 --- /dev/null +++ b/SimPEG/Examples/Mesh_Tensor_Creation.py @@ -0,0 +1,35 @@ +from SimPEG import * + +def run(plotIt=True): + """ + + Mesh: Tensor: Creation + ====================== + + For tensor meshes, there are some functions that can come + in handy. For example, creating mesh tensors can be a bit time + consuming, these can be created speedily by just giving numbers + and sizes of padding. See the example below, that follows this + notation:: + + h1 = ( + (cellSize, numPad, [, increaseFactor]), + (cellSize, numCore), + (cellSize, numPad, [, increaseFactor]) + ) + + .. note:: + + You can center your mesh by passing a 'C' for the x0[i] position. + A 'N' will make the entire mesh negative, and a '0' (or a 0) will + make the mesh start at zero. + + """ + h1 = [(10, 5, -1.3), (5, 20), (10, 3, 1.3)] + M = Mesh.TensorMesh([h1, h1], x0='CN') + if plotIt: + M.plotGrid(showIt=True) + +if __name__ == '__main__': + run() + diff --git a/SimPEG/Examples/__init__.py b/SimPEG/Examples/__init__.py index 53b1f9a8..ac219a7f 100644 --- a/SimPEG/Examples/__init__.py +++ b/SimPEG/Examples/__init__.py @@ -5,5 +5,63 @@ __all__ = [] for x in glob(p.join(p.dirname(__file__), '*.py')): if not p.basename(x).startswith('__'): __import__(p.basename(x)[:-3], globals(), locals()) - __all__ += [p.basename(x)] + __all__ += [p.basename(x)[:-3]] del glob, p, x + +if __name__ == '__main__': + """ + + Run the following to create the examples documentation. + + """ + + import shutil, os + from SimPEG import Examples + + def _makeExample(filePath, runFunction): + filePath = os.path.realpath(filePath) + name = filePath.split(os.path.sep)[-1].rstrip('.pyc').rstrip('.py') + + docstr = runFunction.__doc__ + if docstr is None: + doc = '%s\n%s'%(name.replace('_',' '),'='*len(name)) + else: + doc = '\n'.join([_[8:].rstrip() for _ in docstr.split('\n')]) + + out = """.. _examples_%s: + +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + +%s + +.. plot:: + + from SimPEG import Examples + Examples.%s.run() + +.. literalinclude:: ../../SimPEG/Examples/%s.py + :language: python + :linenos: +"""%(name,doc,name,name) + + rst = os.path.sep.join((filePath.split(os.path.sep)[:-3] + ['docs', 'examples', name + '.rst'])) + + f = open(rst, 'w') + f.write(out) + f.close() + + + docExamplesDir = os.path.sep.join(os.path.realpath(__file__).split(os.path.sep)[:-3] + ['docs', 'examples']) + shutil.rmtree(docExamplesDir) + os.makedirs(docExamplesDir) + + for ex in dir(Examples): + if ex.startswith('_'): continue + E = getattr(Examples,ex) + _makeExample(E.__file__, E.run) diff --git a/SimPEG/FLOW/Examples/__init__.py b/SimPEG/FLOW/Examples/__init__.py deleted file mode 100644 index 7a894e11..00000000 --- a/SimPEG/FLOW/Examples/__init__.py +++ /dev/null @@ -1 +0,0 @@ -import Celia1990 diff --git a/SimPEG/Mesh/TreeMesh.py b/SimPEG/Mesh/TreeMesh.py index 69a44fdc..263909e4 100644 --- a/SimPEG/Mesh/TreeMesh.py +++ b/SimPEG/Mesh/TreeMesh.py @@ -1975,24 +1975,28 @@ class TreeMesh(BaseTensorMesh, InnerProducts): fig = ax.figure if grid: + X, Y, Z = [], [], [] for ind in self._sortedCells: p = self._asPointer(ind) n = self._cellN(p) h = self._cellH(p) - x = [n[0] , n[0] + h[0], n[0] + h[0], n[0] , n[0]] - y = [n[1] , n[1] , n[1] + h[1], n[1] + h[1], n[1]] if self.dim == 2: - ax.plot(x,y, 'b-') + X += [n[0] , n[0] + h[0], n[0] + h[0], n[0] , n[0], np.nan] + Y += [n[1] , n[1] , n[1] + h[1], n[1] + h[1], n[1], np.nan] elif self.dim == 3: - ax.plot(x,y, 'b-', zs=[n[2]]*5) - z = [n[2] + h[2], n[2] + h[2], n[2] + h[2], n[2] + h[2], n[2] + h[2]] - ax.plot(x,y, 'b-', zs=z) + X += [n[0] , n[0] + h[0], n[0] + h[0], n[0] , n[0], np.nan]*2 + Y += [n[1] , n[1] , n[1] + h[1], n[1] + h[1], n[1], np.nan]*2 + Z += [n[2]]*5+[np.nan] + Z += [n[2] + h[2], n[2] + h[2], n[2] + h[2], n[2] + h[2], n[2] + h[2], np.nan] sides = [0,0], [h[0],0], [0,h[1]], [h[0],h[1]] for s in sides: - x = [n[0] + s[0], n[0] + s[0]] - y = [n[1] + s[1], n[1] + s[1]] - z = [n[2] , n[2] + h[2]] - ax.plot(x,y, 'b-', zs=z) + X += [n[0] + s[0], n[0] + s[0]] + Y += [n[1] + s[1], n[1] + s[1]] + Z += [n[2] , n[2] + h[2]] + if self.dim == 2: + ax.plot(X,Y, 'b-') + elif self.dim == 3: + ax.plot(X,Y, 'b-', zs=Z) if self.dim == 2: if cells: @@ -2004,11 +2008,11 @@ class TreeMesh(BaseTensorMesh, InnerProducts): ax.plot(self._gridN[:,0], self._gridN[:,1], 'ms') ax.plot(self._gridN[self._hangingN.keys(),0], self._gridN[self._hangingN.keys(),1], 'ms', ms=10, mfc='none', mec='m') if facesX: - ax.plot(self._gridFx[self._hangingFx.keys(),0], self._gridFx[self._hangingFx.keys(),1], 'gs', ms=10, mfc='none', mec='g') ax.plot(self._gridFx[:,0], self._gridFx[:,1], 'g>') + ax.plot(self._gridFx[self._hangingFx.keys(),0], self._gridFx[self._hangingFx.keys(),1], 'gs', ms=10, mfc='none', mec='g') if facesY: - ax.plot(self._gridFy[self._hangingFy.keys(),0], self._gridFy[self._hangingFy.keys(),1], 'gs', ms=10, mfc='none', mec='g') ax.plot(self._gridFy[:,0], self._gridFy[:,1], 'g^') + ax.plot(self._gridFy[self._hangingFy.keys(),0], self._gridFy[self._hangingFy.keys(),1], 'gs', ms=10, mfc='none', mec='g') ax.set_xlabel('x1') ax.set_ylabel('x2') elif self.dim == 3: @@ -2079,12 +2083,15 @@ class TreeMesh(BaseTensorMesh, InnerProducts): ax.grid(True) if showIt:plt.show() - def plotImage(self, I, ax=None, showIt=True, grid=False): + def plotImage(self, I, ax=None, showIt=False, grid=False, clim=None): if self.dim == 3: raise Exception('Use plot slice?') if ax is None: ax = plt.subplot(111) jet = cm = plt.get_cmap('jet') - cNorm = colors.Normalize(vmin=I.min(), vmax=I.max()) + cNorm = colors.Normalize( + vmin=I.min() if clim is None else clim[0], + vmax=I.max() if clim is None else clim[1]) + scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=jet) ax.set_xlim((self.x0[0], self.h[0].sum())) ax.set_ylim((self.x0[1], self.h[1].sum())) @@ -2093,8 +2100,10 @@ class TreeMesh(BaseTensorMesh, InnerProducts): ax.add_patch(plt.Rectangle((x0[0], x0[1]), sz[0], sz[1], facecolor=scalarMap.to_rgba(I[ii]), edgecolor='k' if grid else 'none')) # if text: ax.text(self.center[0],self.center[1],self.num) scalarMap._A = [] # http://stackoverflow.com/questions/8342549/matplotlib-add-colorbar-to-a-sequence-of-line-plots - plt.colorbar(scalarMap) + ax.set_xlabel('x') + ax.set_ylabel('y') if showIt: plt.show() + return [scalarMap] def plotSlice(self, v, vType='CC', normal='Z', ind=None, grid=True, view='real', diff --git a/SimPEG/Utils/__init__.py b/SimPEG/Utils/__init__.py index 67e1b117..5280ae79 100644 --- a/SimPEG/Utils/__init__.py +++ b/SimPEG/Utils/__init__.py @@ -1,6 +1,5 @@ from matutils import * from codeutils import * -from codeutils import _makeExample from meshutils import exampleLrmGrid, meshTensor, closestPoints, readUBCTensorMesh, writeUBCTensorMesh, writeUBCTensorModel, readVTRFile, writeVTRFile from curvutils import volTetra, faceInfo, indexCube from interputils import interpmat diff --git a/SimPEG/Utils/codeutils.py b/SimPEG/Utils/codeutils.py index fef34282..4a9a28a7 100644 --- a/SimPEG/Utils/codeutils.py +++ b/SimPEG/Utils/codeutils.py @@ -227,35 +227,3 @@ def requires(var): return requiresVarWrapper return requiresVar - -def _makeExample(filePath): - - import os - name = filePath.split(os.path.sep)[-1][:-3] - out = """.. _examples_%s: - -.. ------------------------------ .. -.. THIS FILE IS AUTO GENEREATED .. -.. ------------------------------ .. - -%s -%s - -.. plot:: - - from SimPEG import Examples - Examples.%s.run() - -.. literalinclude:: ../../SimPEG/Examples/%s.py - :language: python - :linenos: -"""%(name,name.replace('_',' '),'='*len(name),name,name) - - rst = os.path.sep.join((filePath.split(os.path.sep)[:-3] + ['docs', 'examples', name + '.rst'])) - - f = open(rst, 'w') - f.write(out) - f.close() - - - diff --git a/docs/examples/EM_FDEM_1D_Inversion.rst b/docs/examples/EM_FDEM_1D_Inversion.rst new file mode 100644 index 00000000..acbc8cdc --- /dev/null +++ b/docs/examples/EM_FDEM_1D_Inversion.rst @@ -0,0 +1,26 @@ +.. _examples_EM_FDEM_1D_Inversion: + +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + + +EM: FDEM: 1D: Inversion +======================= + +Here we will create and run a FDEM 1D inversion. + + + +.. plot:: + + from SimPEG import Examples + Examples.EM_FDEM_1D_Inversion.run() + +.. literalinclude:: ../../SimPEG/Examples/EM_FDEM_1D_Inversion.py + :language: python + :linenos: diff --git a/docs/examples/FLOW_Richards_1D_Celia1990.rst b/docs/examples/FLOW_Richards_1D_Celia1990.rst new file mode 100644 index 00000000..d2e01c13 --- /dev/null +++ b/docs/examples/FLOW_Richards_1D_Celia1990.rst @@ -0,0 +1,52 @@ +.. _examples_FLOW_Richards_1D_Celia1990: + +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + + +FLOW: Richards: 1D: Celia1990 +============================= + +There are two different forms of Richards equation that differ +on how they deal with the non-linearity in the time-stepping term. + +The most fundamental form, referred to as the +'mixed'-form of Richards Equation Celia1990_ + +.. math:: + + \frac{\partial \theta(\psi)}{\partial t} - \nabla \cdot k(\psi) \nabla \psi - \frac{\partial k(\psi)}{\partial z} = 0 + \quad \psi \in \Omega + +where \\(\\theta\\) is water content, and \\(\\psi\\) is pressure head. +This formulation of Richards equation is called the +'mixed'-form because the equation is parameterized in \\(\\psi\\) +but the time-stepping is in terms of \\(\\theta\\). + +As noted in Celia1990_ the 'head'-based form of Richards +equation can be written in the continuous form as: + +.. math:: + + \frac{\partial \theta}{\partial \psi}\frac{\partial \psi}{\partial t} - \nabla \cdot k(\psi) \nabla \psi - \frac{\partial k(\psi)}{\partial z} = 0 \quad \psi \in \Omega + +However, it can be shown that this does not conserve mass in the discrete formulation. + +Here we reproduce the results from Celia1990_ demonstrating the head-based formulation and the mixed-formulation. + +.. _Celia1990: http://www.webpages.uidaho.edu/ch/papers/Celia.pdf + + +.. plot:: + + from SimPEG import Examples + Examples.FLOW_Richards_1D_Celia1990.run() + +.. literalinclude:: ../../SimPEG/Examples/FLOW_Richards_1D_Celia1990.py + :language: python + :linenos: diff --git a/docs/examples/Forward_BasicDirectCurrent.rst b/docs/examples/Forward_BasicDirectCurrent.rst index 6fc816e7..20b39eb8 100644 --- a/docs/examples/Forward_BasicDirectCurrent.rst +++ b/docs/examples/Forward_BasicDirectCurrent.rst @@ -1,8 +1,12 @@ .. _examples_Forward_BasicDirectCurrent: -.. ------------------------------ .. -.. THIS FILE IS AUTO GENEREATED .. -.. ------------------------------ .. +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. Forward BasicDirectCurrent ========================== diff --git a/docs/examples/Inversion_Linear.rst b/docs/examples/Inversion_Linear.rst index 637315ae..d635d8e1 100644 --- a/docs/examples/Inversion_Linear.rst +++ b/docs/examples/Inversion_Linear.rst @@ -1,11 +1,20 @@ .. _examples_Inversion_Linear: -.. ------------------------------ .. -.. THIS FILE IS AUTO GENEREATED .. -.. ------------------------------ .. +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + + +Inversion: Linear Problem +========================= + +Here we go over the basics of creating a linear problem and inversion. + -Inversion Linear -================ .. plot:: diff --git a/docs/examples/Mesh_Basic_PlotImage.rst b/docs/examples/Mesh_Basic_PlotImage.rst new file mode 100644 index 00000000..a730f303 --- /dev/null +++ b/docs/examples/Mesh_Basic_PlotImage.rst @@ -0,0 +1,27 @@ +.. _examples_Mesh_Basic_PlotImage: + +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + + +Mesh: Basic: PlotImage +====================== + +You can use M.PlotImage to plot images on all of the Meshes. + + + + +.. plot:: + + from SimPEG import Examples + Examples.Mesh_Basic_PlotImage.run() + +.. literalinclude:: ../../SimPEG/Examples/Mesh_Basic_PlotImage.py + :language: python + :linenos: diff --git a/docs/examples/Mesh_Basic_Types.rst b/docs/examples/Mesh_Basic_Types.rst new file mode 100644 index 00000000..9bbce0e8 --- /dev/null +++ b/docs/examples/Mesh_Basic_Types.rst @@ -0,0 +1,26 @@ +.. _examples_Mesh_Basic_Types: + +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + + +Mesh: Basic: Types +================== + +Here we show SimPEG used to create three different types of meshes. + + + +.. plot:: + + from SimPEG import Examples + Examples.Mesh_Basic_Types.run() + +.. literalinclude:: ../../SimPEG/Examples/Mesh_Basic_Types.py + :language: python + :linenos: diff --git a/docs/examples/Mesh_QuadTree_Create.rst b/docs/examples/Mesh_QuadTree_Create.rst deleted file mode 100644 index 644593d5..00000000 --- a/docs/examples/Mesh_QuadTree_Create.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. _examples_Mesh_QuadTree_Create: - -.. ------------------------------ .. -.. THIS FILE IS AUTO GENEREATED .. -.. ------------------------------ .. - -Mesh QuadTree Create -==================== - -.. plot:: - - from SimPEG import Examples - Examples.Mesh_QuadTree_Create.run() - -.. literalinclude:: ../../SimPEG/Examples/Mesh_QuadTree_Create.py - :language: python - :linenos: diff --git a/docs/examples/Mesh_QuadTree_Creation.rst b/docs/examples/Mesh_QuadTree_Creation.rst new file mode 100644 index 00000000..5db5a982 --- /dev/null +++ b/docs/examples/Mesh_QuadTree_Creation.rst @@ -0,0 +1,31 @@ +.. _examples_Mesh_QuadTree_Creation: + +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + + +Mesh: QuadTree: Creation +======================== + +You can give the refine method a function, which is evaluated on every cell +of the TreeMesh. + +Occasionally it is useful to initially refine to a constant level +(e.g. 3 in this 32x32 mesh). This means the function is first evaluated +on an 8x8 mesh (2^3). + + + +.. plot:: + + from SimPEG import Examples + Examples.Mesh_QuadTree_Creation.run() + +.. literalinclude:: ../../SimPEG/Examples/Mesh_QuadTree_Creation.py + :language: python + :linenos: diff --git a/docs/examples/Mesh_QuadTree_HangingNodes.rst b/docs/examples/Mesh_QuadTree_HangingNodes.rst new file mode 100644 index 00000000..93875478 --- /dev/null +++ b/docs/examples/Mesh_QuadTree_HangingNodes.rst @@ -0,0 +1,31 @@ +.. _examples_Mesh_QuadTree_HangingNodes: + +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + + +Mesh: QuadTree: Hanging Nodes +============================= + +You can give the refine method a function, which is evaluated on every cell +of the TreeMesh. + +Occasionally it is useful to initially refine to a constant level +(e.g. 3 in this 32x32 mesh). This means the function is first evaluated +on an 8x8 mesh (2^3). + + + +.. plot:: + + from SimPEG import Examples + Examples.Mesh_QuadTree_HangingNodes.run() + +.. literalinclude:: ../../SimPEG/Examples/Mesh_QuadTree_HangingNodes.py + :language: python + :linenos: diff --git a/docs/examples/Mesh_Tensor_Creation.rst b/docs/examples/Mesh_Tensor_Creation.rst new file mode 100644 index 00000000..e6cc5b67 --- /dev/null +++ b/docs/examples/Mesh_Tensor_Creation.rst @@ -0,0 +1,43 @@ +.. _examples_Mesh_Tensor_Creation: + +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + + + +Mesh: Tensor: Creation +====================== + +For tensor meshes, there are some functions that can come +in handy. For example, creating mesh tensors can be a bit time +consuming, these can be created speedily by just giving numbers +and sizes of padding. See the example below, that follows this +notation:: + + h1 = ( + (cellSize, numPad, [, increaseFactor]), + (cellSize, numCore), + (cellSize, numPad, [, increaseFactor]) + ) + +.. note:: + + You can center your mesh by passing a 'C' for the x0[i] position. + A 'N' will make the entire mesh negative, and a '0' (or a 0) will + make the mesh start at zero. + + + +.. plot:: + + from SimPEG import Examples + Examples.Mesh_Tensor_Creation.run() + +.. literalinclude:: ../../SimPEG/Examples/Mesh_Tensor_Creation.py + :language: python + :linenos: diff --git a/docs/examples/Mesh_ThreeMeshes.rst b/docs/examples/Mesh_ThreeMeshes.rst deleted file mode 100644 index 948398a7..00000000 --- a/docs/examples/Mesh_ThreeMeshes.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. _examples_Mesh_ThreeMeshes: - -.. ------------------------------ .. -.. THIS FILE IS AUTO GENEREATED .. -.. ------------------------------ .. - -Mesh ThreeMeshes -================ - -.. plot:: - - from SimPEG import Examples - Examples.Mesh_ThreeMeshes.run() - -.. literalinclude:: ../../SimPEG/Examples/Mesh_ThreeMeshes.py - :language: python - :linenos: diff --git a/tests/em/examples/__init__.py b/tests/em/examples/__init__.py deleted file mode 100644 index 38d84328..00000000 --- a/tests/em/examples/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -if __name__ == '__main__': - import os - import glob - import unittest - test_file_strings = glob.glob('test_*.py') - module_strings = [str[0:len(str)-3] for str in test_file_strings] - suites = [unittest.defaultTestLoader.loadTestsFromName(str) for str - in module_strings] - testSuite = unittest.TestSuite(suites) - - unittest.TextTestRunner(verbosity=2).run(testSuite) diff --git a/tests/em/examples/test_Examples.py b/tests/em/examples/test_Examples.py deleted file mode 100644 index 5a601d3b..00000000 --- a/tests/em/examples/test_Examples.py +++ /dev/null @@ -1,10 +0,0 @@ -import unittest, os -from SimPEG.EM import Examples - -class EM_ExamplesRunning(unittest.TestCase): - - def test_CylInversion(self): - Examples.CylInversion.run(plotIt=False) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/flow/test_examples.py b/tests/flow/test_examples.py deleted file mode 100644 index bc519e6d..00000000 --- a/tests/flow/test_examples.py +++ /dev/null @@ -1,12 +0,0 @@ -import unittest -import sys -from SimPEG.FLOW.Examples import Celia1990 -import numpy as np - -class TestCelia1990(unittest.TestCase): - def test_running(self): - Celia1990.run(plotIt=False) - self.assertTrue(True) - -if __name__ == '__main__': - unittest.main() From b2aab4163b655b93c1465012b9e5e2024e308e05 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Thu, 26 Nov 2015 13:56:04 -0700 Subject: [PATCH 05/77] New examples for Mesh --- .../Examples/Mesh_Operators_CahnHilliard.py | 105 ++++++++++++++++++ SimPEG/Examples/Mesh_QuadTree_FaceDiv.py | 49 ++++++++ docs/examples/Mesh_Operators_CahnHilliard.rst | 57 ++++++++++ docs/examples/Mesh_QuadTree_FaceDiv.rst | 26 +++++ 4 files changed, 237 insertions(+) create mode 100644 SimPEG/Examples/Mesh_Operators_CahnHilliard.py create mode 100644 SimPEG/Examples/Mesh_QuadTree_FaceDiv.py create mode 100644 docs/examples/Mesh_Operators_CahnHilliard.rst create mode 100644 docs/examples/Mesh_QuadTree_FaceDiv.rst diff --git a/SimPEG/Examples/Mesh_Operators_CahnHilliard.py b/SimPEG/Examples/Mesh_Operators_CahnHilliard.py new file mode 100644 index 00000000..8e42e617 --- /dev/null +++ b/SimPEG/Examples/Mesh_Operators_CahnHilliard.py @@ -0,0 +1,105 @@ +from SimPEG import * + +def run(plotIt=True, n=60): + """ + Mesh: Operators: Cahn Hilliard + ============================== + + This example is based on the example in the FiPy_ library. + Please see their documentation for more information about the Cahn-Hilliard equation. + + The "Cahn-Hilliard" equation separates a field \\\\( \\\\phi \\\\) into 0 and 1 with smooth transitions. + + .. math:: + + \\frac{\partial \phi}{\partial t} = \\nabla \cdot D \\nabla \left( \\frac{\partial f}{\partial \phi} - \epsilon^2 \\nabla^2 \phi \\right) + + Where \\\\( f \\\\) is the energy function \\\\( f = ( a^2 / 2 )\\\\phi^2(1 - \\\\phi)^2 \\\\) + which drives \\\\( \\\\phi \\\\) towards either 0 or 1, this competes with the term + \\\\(\\\\epsilon^2 \\\\nabla^2 \\\\phi \\\\) which is a diffusion term that creates smooth changes in \\\\( \\\\phi \\\\). + The equation can be factored: + + .. math:: + + \\frac{\partial \phi}{\partial t} = \\nabla \cdot D \\nabla \psi \\\\ + \psi = \\frac{\partial^2 f}{\partial \phi^2} (\phi - \phi^{\\text{old}}) + \\frac{\partial f}{\partial \phi} - \epsilon^2 \\nabla^2 \phi + + Here we will need the derivatives of \\\\( f \\\\): + + .. math:: + + \\frac{\partial f}{\partial \phi} = (a^2/2)2\phi(1-\phi)(1-2\phi) + \\frac{\partial^2 f}{\partial \phi^2} = (a^2/2)2[1-6\phi(1-\phi)] + + The implementation below uses backwards Euler in time with an exponentially increasing time step. + The initial \\\\( \\\\phi \\\\) is a normally distributed field with a standard deviation of 0.1 and mean of 0.5. + The grid is 60x60 and takes a few seconds to solve ~130 times. The results are seen below, and you can see the + field separating as the time increases. + + .. _FiPy: http://www.ctcms.nist.gov/fipy/examples/cahnHilliard/generated/examples.cahnHilliard.mesh2DCoupled.html + + """ + + np.random.seed(5) + + # Here we are going to rearrange the equations: + + # (phi_ - phi)/dt = A*(d2fdphi2*(phi_ - phi) + dfdphi - L*phi_) + # (phi_ - phi)/dt = A*(d2fdphi2*phi_ - d2fdphi2*phi + dfdphi - L*phi_) + # (phi_ - phi)/dt = A*d2fdphi2*phi_ + A*( - d2fdphi2*phi + dfdphi - L*phi_) + # phi_ - phi = dt*A*d2fdphi2*phi_ + dt*A*(- d2fdphi2*phi + dfdphi - L*phi_) + # phi_ - dt*A*d2fdphi2 * phi_ = dt*A*(- d2fdphi2*phi + dfdphi - L*phi_) + phi + # (I - dt*A*d2fdphi2) * phi_ = dt*A*(- d2fdphi2*phi + dfdphi - L*phi_) + phi + # (I - dt*A*d2fdphi2) * phi_ = dt*A*dfdphi - dt*A*d2fdphi2*phi - dt*A*L*phi_ + phi + # (dt*A*d2fdphi2 - I) * phi_ = dt*A*d2fdphi2*phi + dt*A*L*phi_ - phi - dt*A*dfdphi + # (dt*A*d2fdphi2 - I - dt*A*L) * phi_ = (dt*A*d2fdphi2 - I)*phi - dt*A*dfdphi + + h = [(0.25,n)] + M = Mesh.TensorMesh([h,h]) + + # Constants + D = a = epsilon = 1. + I = Utils.speye(M.nC) + + # Operators + A = D * M.faceDiv * M.cellGrad + L = epsilon**2 * M.faceDiv * M.cellGrad + + duration = 75 + elapsed = 0. + dexp = -5 + phi = np.random.normal(loc=0.5,scale=0.01,size=M.nC) + ii, jj = 0, 0 + PHIS = [] + capture = np.logspace(-1,np.log10(duration),8) + while elapsed < duration: + dt = min(100, np.exp(dexp)) + elapsed += dt + dexp += 0.05 + + dfdphi = a**2 * 2 * phi * (1 - phi) * (1 - 2 * phi) + d2fdphi2 = Utils.sdiag(a**2 * 2 * (1 - 6 * phi * (1 - phi))) + + MAT = (dt*A*d2fdphi2 - I - dt*A*L) + rhs = (dt*A*d2fdphi2 - I)*phi - dt*A*dfdphi + phi = Solver(MAT)*rhs + + if elapsed > capture[jj]: + PHIS += [(elapsed, phi.copy())] + jj += 1 + if ii % 10 == 0: print ii, elapsed + ii += 1 + + if plotIt: + import matplotlib.pyplot as plt + fig, axes = plt.subplots(2,4,figsize=(14,6)) + axes = np.array(axes).flatten().tolist() + for ii, ax in zip(np.linspace(0,len(PHIS)-1,len(axes)),axes): + ii = int(ii) + out = M.plotImage(PHIS[ii][1],ax=ax) + ax.axis('off') + ax.set_title('Elapsed Time: %4.1f'%PHIS[ii][0]) + plt.show() + +if __name__ == '__main__': + run() diff --git a/SimPEG/Examples/Mesh_QuadTree_FaceDiv.py b/SimPEG/Examples/Mesh_QuadTree_FaceDiv.py new file mode 100644 index 00000000..5bd67929 --- /dev/null +++ b/SimPEG/Examples/Mesh_QuadTree_FaceDiv.py @@ -0,0 +1,49 @@ +from SimPEG import * + +def run(plotIt=True, n=60): + """ + Mesh: QuadTree: FaceDiv + ======================= + + + + """ + + + M = Mesh.TreeMesh([[(1,16)],[(1,16)]], levels=4) + M._refineCell([0,0,0]) + M._refineCell([0,0,1]) + M._refineCell([4,4,2]) + M.__dirty__ = True + M.number() + + + if plotIt: + import matplotlib.pyplot as plt + fig, axes = plt.subplots(2,1,figsize=(10,10)) + + M.plotGrid(cells=True, nodes=False, ax=axes[0]) + axes[0].axis('off') + axes[0].set_title('Simple QuadTree Mesh') + axes[0].set_xlim([-1,17]) + axes[0].set_ylim([-1,17]) + + for ii, loc in zip(range(M.nC),M.gridCC): + axes[0].text(loc[0]+0.2,loc[1],'%d'%ii, color='r') + + axes[0].plot(M.gridFx[:,0],M.gridFx[:,1], 'g>') + for ii, loc in zip(range(M.nFx),M.gridFx): + axes[0].text(loc[0]+0.2,loc[1],'%d'%ii, color='g') + + axes[0].plot(M.gridFy[:,0],M.gridFy[:,1], 'm^') + for ii, loc in zip(range(M.nFy),M.gridFy): + axes[0].text(loc[0]+0.2,loc[1]+0.2,'%d'%(ii+M.nFx), color='m') + + axes[1].spy(M.faceDiv) + axes[1].set_title('Face Divergence') + axes[1].set_ylabel('Cell Number') + axes[1].set_xlabel('Face Number') + plt.show() + +if __name__ == '__main__': + run() diff --git a/docs/examples/Mesh_Operators_CahnHilliard.rst b/docs/examples/Mesh_Operators_CahnHilliard.rst new file mode 100644 index 00000000..9786e911 --- /dev/null +++ b/docs/examples/Mesh_Operators_CahnHilliard.rst @@ -0,0 +1,57 @@ +.. _examples_Mesh_Operators_CahnHilliard: + +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + + +Mesh: Operators: Cahn Hilliard +============================== + +This example is based on the example in the FiPy_ library. +Please see their documentation for more information about the Cahn-Hilliard equation. + +The "Cahn-Hilliard" equation separates a field \\( \\phi \\) into 0 and 1 with smooth transitions. + +.. math:: + + \frac{\partial \phi}{\partial t} = \nabla \cdot D \nabla \left( \frac{\partial f}{\partial \phi} - \epsilon^2 \nabla^2 \phi \right) + +Where \\( f \\) is the energy function \\( f = ( a^2 / 2 )\\phi^2(1 - \\phi)^2 \\) +which drives \\( \\phi \\) towards either 0 or 1, this competes with the term +\\(\\epsilon^2 \\nabla^2 \\phi \\) which is a diffusion term that creates smooth changes in \\( \\phi \\). +The equation can be factored: + +.. math:: + + \frac{\partial \phi}{\partial t} = \nabla \cdot D \nabla \psi \\ + \psi = \frac{\partial^2 f}{\partial \phi^2} (\phi - \phi^{\text{old}}) + \frac{\partial f}{\partial \phi} - \epsilon^2 \nabla^2 \phi + +Here we will need the derivatives of \\( f \\): + +.. math:: + + \frac{\partial f}{\partial \phi} = (a^2/2)2\phi(1-\phi)(1-2\phi) + \frac{\partial^2 f}{\partial \phi^2} = (a^2/2)2[1-6\phi(1-\phi)] + +The implementation below uses backwards Euler in time with an exponentially increasing time step. +The initial \\( \\phi \\) is a normally distributed field with a standard deviation of 0.1 and mean of 0.5. +The grid is 60x60 and takes a few seconds to solve ~130 times. The results are seen below, and you can see the +field separating as the time increases. + +.. _FiPy: http://www.ctcms.nist.gov/fipy/examples/cahnHilliard/generated/examples.cahnHilliard.mesh2DCoupled.html + + + +.. plot:: + + from SimPEG import Examples + Examples.Mesh_Operators_CahnHilliard.run() + +.. literalinclude:: ../../SimPEG/Examples/Mesh_Operators_CahnHilliard.py + :language: python + :linenos: diff --git a/docs/examples/Mesh_QuadTree_FaceDiv.rst b/docs/examples/Mesh_QuadTree_FaceDiv.rst new file mode 100644 index 00000000..6bfdd47f --- /dev/null +++ b/docs/examples/Mesh_QuadTree_FaceDiv.rst @@ -0,0 +1,26 @@ +.. _examples_Mesh_QuadTree_FaceDiv: + +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + + +Mesh: QuadTree: FaceDiv +======================= + + + + + +.. plot:: + + from SimPEG import Examples + Examples.Mesh_QuadTree_FaceDiv.run() + +.. literalinclude:: ../../SimPEG/Examples/Mesh_QuadTree_FaceDiv.py + :language: python + :linenos: From 528253a8cbaaf5e5cda8d3b73c4dea86676701fd Mon Sep 17 00:00:00 2001 From: Brendan Smithyman Date: Thu, 26 Nov 2015 19:18:04 -0500 Subject: [PATCH 06/77] Generalize mapPair --- SimPEG/Regularization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SimPEG/Regularization.py b/SimPEG/Regularization.py index e3506290..65fba9f3 100644 --- a/SimPEG/Regularization.py +++ b/SimPEG/Regularization.py @@ -24,7 +24,7 @@ class BaseRegularization(object): Utils.setKwargs(self, **kwargs) self.mesh = mesh assert isinstance(mesh, Mesh.BaseMesh), "mesh must be a SimPEG.Mesh object." - self.mapping = mapping or Maps.IdentityMap(mesh) + self.mapping = mapping or self.mapPair(mesh) self.mapping._assertMatchesPair(self.mapPair) @property From 83cb5ce46a9dd3e908e95935856f7910dc8bd65a Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Sat, 28 Nov 2015 12:55:24 -0800 Subject: [PATCH 07/77] - Meshlesses Identity Map (takes nP instead of a mesh) - Tikhonov regularization if active cells are used (don't take derivs across interfaces between active cells and not) - testing improvements: test 1D, 2D, 3D on a random tensor mesh , also test that for a constant mref, phi_m(ref) = 0 --- SimPEG/Maps.py | 72 +++++++++++++++++++++++++++++ SimPEG/Regularization.py | 49 +++++++++++++++++--- tests/base/test_regularization.py | 75 ++++++++++++++++++++++++++----- 3 files changed, 179 insertions(+), 17 deletions(-) diff --git a/SimPEG/Maps.py b/SimPEG/Maps.py index 5b4782ac..7c46fa1d 100644 --- a/SimPEG/Maps.py +++ b/SimPEG/Maps.py @@ -118,6 +118,78 @@ class IdentityMap(object): def __str__(self): return "%s(%s,%s)" % (self.__class__.__name__, self.shape[0], self.shape[1]) + +class IdentityMap_Meshless(IdentityMap): + + def __init__(self, nP=None, **kwargs): + IdentityMap.__init__(self, None, **kwargs) + self._nP = nP + + @property + def nP(self): + """ + :rtype: int + :return: number of parameters in the model + """ + if self._nP is None: + return '*' + return self._nP + + @property + def shape(self): + """ + The default shape is (mesh.nC, nP). + + :rtype: (int,int) + :return: shape of the operator as a tuple + """ + if self._nP is None: + return ('*', '*') + return (self.nP, self.nP) + + + def _transform(self, m): + """ + Changes the model into the physical property. + + .. note:: + + This can be called by the __mul__ property against a numpy.ndarray. + + :param numpy.array m: model + :rtype: numpy.array + :return: transformed model + + """ + return m + + def inverse(self, D): + """ + Changes the physical property into the model. + + .. note:: + + The *transformInverse* may not be easy to create in general. + + :param numpy.array D: physical property + :rtype: numpy.array + :return: model + + """ + raise NotImplementedError('The transformInverse is not implemented.') + + def deriv(self, m): + """ + The derivative of the transformation. + + :param numpy.array m: model + :rtype: scipy.csr_matrix + :return: derivative of transformed model + + """ + return sp.identity(self.nP) + + class ComboMap(IdentityMap): """Combination of various maps.""" diff --git a/SimPEG/Regularization.py b/SimPEG/Regularization.py index e3506290..7e98f073 100644 --- a/SimPEG/Regularization.py +++ b/SimPEG/Regularization.py @@ -20,12 +20,13 @@ class BaseRegularization(object): mesh = None #: A SimPEG.Mesh instance. mref = None #: Reference model. - def __init__(self, mesh, mapping=None, **kwargs): + def __init__(self, mesh, mapping=None, indActive=None, **kwargs): Utils.setKwargs(self, **kwargs) self.mesh = mesh assert isinstance(mesh, Mesh.BaseMesh), "mesh must be a SimPEG.Mesh object." self.mapping = mapping or Maps.IdentityMap(mesh) self.mapping._assertMatchesPair(self.mapPair) + self.indActive = indActive @property def parent(self): @@ -112,8 +113,6 @@ class BaseRegularization(object): return mD.T * ( self.W.T * ( self.W * ( mD * v) ) ) - - class Tikhonov(BaseRegularization): """**Tikhonov Regularization** @@ -205,14 +204,18 @@ class Tikhonov(BaseRegularization): alpha_yy = Utils.dependentProperty('_alpha_yy', 0.0, ['_W', '_Wyy'], "Weight for the second derivative in the y direction") alpha_zz = Utils.dependentProperty('_alpha_zz', 0.0, ['_W', '_Wzz'], "Weight for the second derivative in the z direction") - def __init__(self, mesh, mapping=None, **kwargs): + def __init__(self, mesh, mapping=None, indActive = None, **kwargs): BaseRegularization.__init__(self, mesh, mapping=mapping, **kwargs) + self.indActive = indActive @property def Ws(self): """Regularization matrix Ws""" if getattr(self,'_Ws', None) is None: - self._Ws = Utils.sdiag((self.mesh.vol*self.alpha_s)**0.5) + self._Ws = Utils.sdiag((self.mesh.vol*self.alpha_s)**0.5) + if self.indActive is not None: + Pac = Utils.speye(self.mesh.nC)[:,self.indActive] + self._Ws = Pac.T * self._Ws * Pac return self._Ws @property @@ -221,6 +224,13 @@ class Tikhonov(BaseRegularization): if getattr(self, '_Wx', None) is None: Ave_x_vol = self.mesh.aveF2CC[:,:self.mesh.nFx].T*self.mesh.vol self._Wx = Utils.sdiag((Ave_x_vol*self.alpha_x)**0.5)*self.mesh.cellGradx + + if self.indActive is not None: + indActive_Fx = (self.mesh.aveFx2CC.T * self.indActive) == 1 + Pac = Utils.speye(self.mesh.nC)[:,self.indActive] + Pafx = Utils.speye(self.mesh.nFx)[:,indActive_Fx] + self._Wx = Pafx.T*self._Wx*Pac + return self._Wx @property @@ -229,6 +239,13 @@ class Tikhonov(BaseRegularization): if getattr(self, '_Wy', None) is None: Ave_y_vol = self.mesh.aveF2CC[:,self.mesh.nFx:np.sum(self.mesh.vnF[:2])].T*self.mesh.vol self._Wy = Utils.sdiag((Ave_y_vol*self.alpha_y)**0.5)*self.mesh.cellGrady + + if self.indActive is not None: + indActive_Fy = (self.mesh.aveFy2CC.T * self.indActive) == 1 + Pac = Utils.speye(self.mesh.nC)[:,self.indActive] + Pafy = Utils.speye(self.mesh.nFy)[:,indActive_Fy] + self._Wy = Pafy.T*self._Wy*Pac + return self._Wy @property @@ -237,6 +254,13 @@ class Tikhonov(BaseRegularization): if getattr(self, '_Wz', None) is None: Ave_z_vol = self.mesh.aveF2CC[:,np.sum(self.mesh.vnF[:2]):].T*self.mesh.vol self._Wz = Utils.sdiag((Ave_z_vol*self.alpha_z)**0.5)*self.mesh.cellGradz + + if self.indActive is not None: + indActive_Fz = (self.mesh.aveFz2CC.T * self.indActive) == 1 + Pac = Utils.speye(self.mesh.nC)[:,self.indActive] + Pafz = Utils.speye(self.mesh.nFz)[:,indActive_Fz] + self._Wz = Pafz.T*self._Wz*Pac + return self._Wz @property @@ -244,6 +268,11 @@ class Tikhonov(BaseRegularization): """Regularization matrix Wxx""" if getattr(self, '_Wxx', None) is None: self._Wxx = Utils.sdiag((self.mesh.vol*self.alpha_xx)**0.5)*self.mesh.faceDivx*self.mesh.cellGradx + + if self.indActive is not None: + Pac = Utils.speye(self.mesh.nC)[:,self.indActive] + self._Wxx = Pac.T*self._Wxx*Pac + return self._Wxx @property @@ -251,6 +280,11 @@ class Tikhonov(BaseRegularization): """Regularization matrix Wyy""" if getattr(self, '_Wyy', None) is None: self._Wyy = Utils.sdiag((self.mesh.vol*self.alpha_yy)**0.5)*self.mesh.faceDivy*self.mesh.cellGrady + + if self.indActive is not None: + Pac = Utils.speye(self.mesh.nC)[:,self.indActive] + self._Wyy = Pac.T*self._Wyy*Pac + return self._Wyy @property @@ -258,6 +292,11 @@ class Tikhonov(BaseRegularization): """Regularization matrix Wzz""" if getattr(self, '_Wzz', None) is None: self._Wzz = Utils.sdiag((self.mesh.vol*self.alpha_zz)**0.5)*self.mesh.faceDivz*self.mesh.cellGradz + + if self.indActive is not None: + Pac = Utils.speye(self.mesh.nC)[:,self.indActive] + self._Wzz = Pac.T*self._Wzz*Pac + return self._Wzz @property diff --git a/tests/base/test_regularization.py b/tests/base/test_regularization.py index af7da692..3243e51d 100644 --- a/tests/base/test_regularization.py +++ b/tests/base/test_regularization.py @@ -4,11 +4,17 @@ from SimPEG import * from scipy.sparse.linalg import dsolve import inspect +TOL = 1e-20 class RegularizationTests(unittest.TestCase): def setUp(self): - self.mesh2 = Mesh.TensorMesh([3, 2]) + hx, hy, hz = np.random.rand(10), np.random.rand(9), np.random.rand(8) + hx, hy, hz = hx/hx.sum(), hy/hy.sum(), hz/hz.sum() + mesh1 = Mesh.TensorMesh([hx]) + mesh2 = Mesh.TensorMesh([hx, hy]) + mesh3 = Mesh.TensorMesh([hx, hy, hz]) + self.meshlist = [mesh1,mesh2, mesh3] def test_regularization(self): for R in dir(Regularization): @@ -16,18 +22,63 @@ class RegularizationTests(unittest.TestCase): if not inspect.isclass(r): continue if not issubclass(r, Regularization.BaseRegularization): continue - # if 'Regularization' not in R: continue - mapping = r.mapPair(self.mesh2) - reg = r(self.mesh2, mapping=mapping) - m = np.random.rand(mapping.nP) - reg.mref = m[:]*np.mean(m) + + for i, mesh in enumerate(self.meshlist): - print 'Check:', R - passed = Tests.checkDerivative(lambda m : [reg.eval(m), reg.evalDeriv(m)], m, plotIt=False) - self.assertTrue(passed) - print 'Check 2 Deriv:', R - passed = Tests.checkDerivative(lambda m : [reg.evalDeriv(m), reg.eval2Deriv(m)], m, plotIt=False) - self.assertTrue(passed) + print 'Testing %iD'%mesh.dim + + mapping = r.mapPair(mesh) + reg = r(mesh, mapping=mapping) + m = np.random.rand(mapping.nP) + reg.mref = np.ones_like(m)*np.mean(m) + + print 'Check: phi_m (mref) = %f' %reg.eval(reg.mref) + passed = reg.eval(reg.mref) < TOL + self.assertTrue(passed) + + print 'Check:', R + passed = Tests.checkDerivative(lambda m : [reg.eval(m), reg.evalDeriv(m)], m, plotIt=False) + self.assertTrue(passed) + + print 'Check 2 Deriv:', R + passed = Tests.checkDerivative(lambda m : [reg.evalDeriv(m), reg.eval2Deriv(m)], m, plotIt=False) + self.assertTrue(passed) + + def test_regularization_ActiveCells(self): + for R in dir(Regularization): + r = getattr(Regularization, R) + if not inspect.isclass(r): continue + if not issubclass(r, Regularization.BaseRegularization): + continue + + for i, mesh in enumerate(self.meshlist): + + print 'Testing Active Cells %iD'%(mesh.dim) + + if mesh.dim == 1: + indAct = Utils.mkvc(mesh.gridCC <= 0.8) + elif mesh.dim == 2: + indAct = Utils.mkvc(mesh.gridCC[:,-1] <= 2*np.sin(2*np.pi*mesh.gridCC[:,0])+0.5) + elif mesh.dim == 3: + indAct = Utils.mkvc(mesh.gridCC[:,-1] <= 2*np.sin(2*np.pi*mesh.gridCC[:,0])+0.5 * 2*np.sin(2*np.pi*mesh.gridCC[:,1])+0.5) + + mapping = Maps.IdentityMap_Meshless(nP=indAct.nonzero()[0].size) + + reg = r(mesh, mapping=mapping, indActive=indAct) + m = np.random.rand(mesh.nC)[indAct] + reg.mref = np.ones_like(m)*np.mean(m) + + print 'Check: phi_m (mref) = %f' %reg.eval(reg.mref) + passed = reg.eval(reg.mref) < TOL + self.assertTrue(passed) + + print 'Check:', R + passed = Tests.checkDerivative(lambda m : [reg.eval(m), reg.evalDeriv(m)], m, plotIt=False) + self.assertTrue(passed) + + print 'Check 2 Deriv:', R + passed = Tests.checkDerivative(lambda m : [reg.evalDeriv(m), reg.eval2Deriv(m)], m, plotIt=False) + self.assertTrue(passed) if __name__ == '__main__': From cfc921b66715c6935813fece4d631d6516afee3b Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Sat, 28 Nov 2015 13:12:44 -0800 Subject: [PATCH 08/77] cleaned out transform, inverse and deriv (all are inherited from IdentityMap) --- SimPEG/Maps.py | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/SimPEG/Maps.py b/SimPEG/Maps.py index 7c46fa1d..d36deed4 100644 --- a/SimPEG/Maps.py +++ b/SimPEG/Maps.py @@ -148,48 +148,6 @@ class IdentityMap_Meshless(IdentityMap): return (self.nP, self.nP) - def _transform(self, m): - """ - Changes the model into the physical property. - - .. note:: - - This can be called by the __mul__ property against a numpy.ndarray. - - :param numpy.array m: model - :rtype: numpy.array - :return: transformed model - - """ - return m - - def inverse(self, D): - """ - Changes the physical property into the model. - - .. note:: - - The *transformInverse* may not be easy to create in general. - - :param numpy.array D: physical property - :rtype: numpy.array - :return: model - - """ - raise NotImplementedError('The transformInverse is not implemented.') - - def deriv(self, m): - """ - The derivative of the transformation. - - :param numpy.array m: model - :rtype: scipy.csr_matrix - :return: derivative of transformed model - - """ - return sp.identity(self.nP) - - class ComboMap(IdentityMap): """Combination of various maps.""" From c4d34c4e0d8083641f79a745196a51aebfb8b29d Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Mon, 30 Nov 2015 17:46:57 -0800 Subject: [PATCH 09/77] Initial counting of nodes. - Some of the nodes in the cell may be hanging. --- SimPEG/Mesh/TreeMesh.py | 62 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/SimPEG/Mesh/TreeMesh.py b/SimPEG/Mesh/TreeMesh.py index 1e6c91c0..9554720f 100644 --- a/SimPEG/Mesh/TreeMesh.py +++ b/SimPEG/Mesh/TreeMesh.py @@ -824,8 +824,10 @@ class TreeMesh(BaseTensorMesh, InnerProducts): def _numberCells(self, force=False): if not self.__dirtyCells__ and not force: return self._cc2i = dict() + self._i2cc = dict() for ii, c in enumerate(sorted(self._cells)): self._cc2i[c] = ii + self._i2cc[ii] = c self.__dirtyCells__ = False def _numberNodes(self, force=False): @@ -2181,6 +2183,25 @@ class TreeMesh(BaseTensorMesh, InnerProducts): if showIt: plt.show() return tuple(out) + def __len__(self): return self.nC + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [self[ii] for ii in xrange(*key.indices(len(self)))] + elif isinstance( key, int ) : + if key < 0 : #Handle negative indices + key += len( self ) + if key >= len( self ) : + raise IndexError, "The index (%d) is out of range."%key + + self._numberCells() # no-op if numbered + index = self._i2cc[key] + pointer = self._asPointer(index) + return Cell(self, index, pointer) + else: + raise TypeError, "Invalid argument type." + class Cell(object): def __init__(self, mesh, index, pointer): @@ -2188,6 +2209,34 @@ class Cell(object): self._index = index self._pointer = pointer + @property + def nodes(self): + M = self.mesh + M._numberNodes() + p = self._pointer + i = self._index + w = M._levelWidth(p[-1]) + + if M.dim == 2: + n = [ + i, + M._index([ p[0] + w, p[1] , p[2]]), + M._index([ p[0] , p[1]+ w, p[2]]), + M._index([ p[0] + w, p[1]+ w, p[2]]), + ] + elif self.dim == 3: + n = [ + i, + M._index([ p[0] + w, p[1] , p[2] ,p[3]]), + M._index([ p[0] , p[1] + w, p[2] ,p[3]]), + M._index([ p[0] + w, p[1] + w, p[2] ,p[3]]), + M._index([ p[0] , p[1] , p[2] + w,p[3]]), + M._index([ p[0] + w, p[1] , p[2] + w,p[3]]), + M._index([ p[0] , p[1] + w, p[2] + w,p[3]]), + M._index([ p[0] + w, p[1] + w, p[2] + w,p[3]]), + ] + return [M._n2i[_] for _ in n] + @property def center(self): if getattr(self, '_center', None) is None: @@ -2270,7 +2319,7 @@ if __name__ == '__main__': # T = TreeMesh([[(1,128)],[(1,128)],[(1,128)]],levels=7) # T = TreeMesh([128,128,128]) # T = TreeMesh([64,64],levels=6) - T = TreeMesh([4,4,4]) + T = TreeMesh([8,8]) # T = TreeMesh([[(1,128)],[(1,128)]],levels=7) # T.refine(lambda xc:2, balance=False) # T._index([0,0,0]) @@ -2281,8 +2330,15 @@ if __name__ == '__main__': T.refine(function)#, balance=False) # print time.time() - tic # print T.nC - T.plotSlice(np.log(T.vol))#np.random.rand(T.nC)) - + # T.plotSlice(np.log(T.vol))#np.random.rand(T.nC)) + T.plotGrid() + # print [c for c in T] + c = T[0] + plt.plot(c.center[0],c.center[1],'r.') + nodes = c.nodes + for n in nodes: + _ = T._gridN[n,:] + plt.plot(_[0],_[1],'gs') plt.show() blah From e30a7bcafc963c3ed6ebb168df3c97c70e5ce3e8 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Mon, 30 Nov 2015 17:52:38 -0800 Subject: [PATCH 10/77] documentation updates --- SimPEG/Mesh/TreeMesh.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SimPEG/Mesh/TreeMesh.py b/SimPEG/Mesh/TreeMesh.py index 9554720f..a4515dab 100644 --- a/SimPEG/Mesh/TreeMesh.py +++ b/SimPEG/Mesh/TreeMesh.py @@ -561,15 +561,18 @@ class TreeMesh(BaseTensorMesh, InnerProducts): return [p - (p % mod) for p in pointer[:-1]] + [pointer[-1]-1] def _cellN(self, p): + """Node location [x,y(,z)] of a single cell, closest to origin, given a pointer.""" p = self._asPointer(p) return [hi[:p[ii]].sum() for ii, hi in enumerate(self.h)] def _cellH(self, p): + """Widths of a single cell given a pointer.""" p = self._asPointer(p) w = self._levelWidth(p[-1]) return [hi[p[ii]:p[ii]+w].sum() for ii, hi in enumerate(self.h)] def _cellC(self, p): + """Cell center of a single cell (without origin correction), given a pointer.""" return (np.array(self._cellH(p))/2.0 + self._cellN(p)).tolist() def _levelWidth(self, level): From a7ab0dc1e246eabb440a56a3e25bbd5851e950ad Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Mon, 30 Nov 2015 18:00:16 -0800 Subject: [PATCH 11/77] Unit tests for getitem on tree mesh --- tests/mesh/test_TreeMesh.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/mesh/test_TreeMesh.py b/tests/mesh/test_TreeMesh.py index e624ce87..afad27d1 100644 --- a/tests/mesh/test_TreeMesh.py +++ b/tests/mesh/test_TreeMesh.py @@ -26,6 +26,27 @@ class TestSimpleQuadTree(unittest.TestCase): assert np.allclose(np.r_[M._areaFxFull, M._areaFyFull], M._deflationMatrix('F') * M.area) + def test_getitem(self): + M = Mesh.TreeMesh([4,4]) + M.refine(1) + assert M.nC == 4 + assert len(M) == M.nC + assert np.allclose(M[0].center, [0.25,0.25]) + actual = [[0,0],[0.5,0],[0,0.5],[0.5,0.5]] + for i, n in enumerate(M[0].nodes): + assert np.allclose(M._gridN[n,:], actual[i]) + + def test_getitem3D(self): + M = Mesh.TreeMesh([4,4,4]) + M.refine(1) + assert M.nC == 8 + assert len(M) == M.nC + assert np.allclose(M[0].center, [0.25,0.25,0.25]) + actual = [[0,0,0],[0.5,0,0],[0,0.5,0],[0.5,0.5,0], + [0,0,0.5],[0.5,0,0.5],[0,0.5,0.5],[0.5,0.5,0.5]] + for i, n in enumerate(M[0].nodes): + assert np.allclose(M._gridN[n,:], actual[i]) + def test_refine(self): M = Mesh.TreeMesh([4,4,4]) M.refine(1) From 7da637e883304ca1ecbaf016357a0b3d51bf9a1b Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Mon, 30 Nov 2015 18:05:02 -0800 Subject: [PATCH 12/77] documentation on Cell.nodes --- SimPEG/Mesh/TreeMesh.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SimPEG/Mesh/TreeMesh.py b/SimPEG/Mesh/TreeMesh.py index a4515dab..eea2ad4c 100644 --- a/SimPEG/Mesh/TreeMesh.py +++ b/SimPEG/Mesh/TreeMesh.py @@ -2214,6 +2214,7 @@ class Cell(object): @property def nodes(self): + """The node index in _gridN (this may include hanging nodes).""" M = self.mesh M._numberNodes() p = self._pointer From 589cd655af735394e7dc322ad496eb04179b8179 Mon Sep 17 00:00:00 2001 From: GudniRos Date: Wed, 2 Dec 2015 16:00:01 -0800 Subject: [PATCH 13/77] Updated vtk write classes. --- SimPEG/Utils/meshutils.py | 80 +++++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 8 deletions(-) diff --git a/SimPEG/Utils/meshutils.py b/SimPEG/Utils/meshutils.py index 585fcc9a..816132fc 100644 --- a/SimPEG/Utils/meshutils.py +++ b/SimPEG/Utils/meshutils.py @@ -205,7 +205,6 @@ def writeUBCTensorModel(fileName, mesh, model): np.savetxt(fileName, modelMatTR.ravel()) - def readVTRFile(fileName): """ Read VTK Rectilinear (vtr xml file) and return SimPEG Tensor mesh and model @@ -296,13 +295,14 @@ def writeVTRFile(fileName,mesh,model=None): vtkObj.SetZCoordinates(numpy_to_vtk(vZ,deep=1)) # Assign the model('s) to the object - for item in model.iteritems(): - # Convert numpy array - vtkDoubleArr = numpy_to_vtk(item[1],deep=1) - vtkDoubleArr.SetName(item[0]) - vtkObj.GetCellData().AddArray(vtkDoubleArr) - # Set the active scalar - vtkObj.GetCellData().SetActiveScalars(model.keys()[0]) + if model is not None: + for item in model.iteritems(): + # Convert numpy array + vtkDoubleArr = numpy_to_vtk(item[1],deep=1) + vtkDoubleArr.SetName(item[0]) + vtkObj.GetCellData().AddArray(vtkDoubleArr) + # Set the active scalar + vtkObj.GetCellData().SetActiveScalars(model.keys()[0]) vtkObj.Update() @@ -318,6 +318,70 @@ def writeVTRFile(fileName,mesh,model=None): vtrWriteFilter.SetFileName(fileName) vtrWriteFilter.Update() +def writeVTUFile(fileName,ocTreeMesh,modelDict=None): + ''' + Function to write a VTU file from a SimPEG TreeMesh and model. + ''' + from vtk import vtkXMLUnstructuredGridWriter as Writer + from vtk.util.numpy_support import numpy_to_vtk + + # Make the object + vtuObj = simpegOcTree2vtuObj(ocTreeMesh,modelDict) + + # Make the writer + vtuWriteFilter = Writer() + if float(vtk.VTK_VERSION.split('.')[0]) >=6: + vtuWriteFilter.SetInputData(vtuObj) + else: + vtuWriteFilter.SetInput(vtuObj) + vtuWriteFilter.SetInput(vtuObj) + vtuWriteFilter.SetFileName(fileName) + # Write the file + vtuWriteFilter.Update() + +def simpegOcTree2vtuObj(simpegOcTreeMesh,modelDict=None): + ''' + Convert simpeg OcTree mesh and model to a VTK vtu object. + + ''' + import vtk + from vtk.util.numpy_support import numpy_to_vtk + + if str(type(simpegOcTreeMesh)).split()[-1][1:-2] not in 'SimPEG.Mesh.TreeMesh.TreeMesh': + raise IOError('simpegOcTreeMesh is not a SimPEG TreeMesh.') + + # Make the data parts for the vtu object + # Points + ptsMat = simpegOcTreeMesh._gridN + simpegOcTreeMesh.x0 + vtkPts = vtk.vtkPoints() + vtkPts.SetData(npsup.numpy_to_vtk(ptsMat,deep=True)) + # Cells + cellConn = np.array([c.nodes for c in simpegOcTreeMesh],dtype=np.int64) + + cellsMat = np.concatenate((np.ones((cellConn.shape[0],1),dtype=np.int64)*cellConn.shape[1],cellConn),axis=1).ravel() + cellsArr = vtk.vtkCellArray() + cellsArr.SetNumberOfCells(cellConn.shape[0]) + cellsArr.SetCells(cellConn.shape[0],npsup.numpy_to_vtkIdTypeArray(cellsMat,deep=True)) + + # Make the object + vtuObj = vtk.vtkUnstructuredGrid() + vtuObj.SetPoints(vtkPts) + vtuObj.SetCells(vtk.VTK_VOXEL,cellsArr) + # Add the level of refinement as a cell array + cellSides = np.array([np.array(vtuObj.GetCell(i).GetBounds()).reshape((3,2)).dot(np.array([-1, 1])) for i in np.arange(vtuObj.GetNumberOfCells())]) + uniqueLevel, indLevel = np.unique(np.prod(cellSides,axis=1),return_inverse=True) + refineLevelArr = npsup.numpy_to_vtk(indLevel.max() - indLevel,deep=1) + refineLevelArr.SetName('octreeLevel') + vtuObj.GetCellData().AddArray(refineLevelArr) + # Assign the model('s) to the object + if modelDict is not None: + for item in modelDict.iteritems(): + # Convert numpy array + vtkDoubleArr = npsup.numpy_to_vtk(item[1],deep=1) + vtkDoubleArr.SetName(item[0]) + vtuObj.GetCellData().AddArray(vtkDoubleArr) + + return vtuObj def ExtractCoreMesh(xyzlim, mesh, meshType='tensor'): """ From a8551f3e04473a94e4e2c3bf044a0f515859c9bb Mon Sep 17 00:00:00 2001 From: GudniRos Date: Wed, 2 Dec 2015 16:04:28 -0800 Subject: [PATCH 14/77] Added function to write a UBC octree mesh for TreeMesh object. --- SimPEG/Utils/meshutils.py | 51 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/SimPEG/Utils/meshutils.py b/SimPEG/Utils/meshutils.py index 816132fc..81b15566 100644 --- a/SimPEG/Utils/meshutils.py +++ b/SimPEG/Utils/meshutils.py @@ -205,6 +205,57 @@ def writeUBCTensorModel(fileName, mesh, model): np.savetxt(fileName, modelMatTR.ravel()) +def writeUBCocTreeFiles(fileName,mesh,modelDict=None,): + ''' + Write UBC ocTree mesh and model files from a simpeg ocTree mesh and model. + + :param + + ''' + + # Calculate information to write in the file. + # Number of cells in the underlying mesh + nCunderMesh = np.array([h.size for h in mesh.h],dtype=np.int64) + # The top-south-west most corner of the mesh + tswCorn = mesh.x0 + np.array([0,0,np.sum(mesh.h[2])]) + # Smallest cell size + smallCell = np.array([h.min() for h in mesh.h]) + # Number of cells + nrCells = mesh.nC + + ## Extract iformation about the cells. + # cell pointers + cellPointers = np.array([c._pointer for c in mesh]) + # cell with + cellW = np.array([ mesh._levelWidth(i) for i in cellPointers[:,-1] ]) + # Need to shift the pointers to work with UBC indexing + # UBC Octree indexes always the top-left-close (top-south-west) corner first and orders the cells in z(top-down),x,y vs x,y,z(bottom-up). + # Shift index up by 1 + ubcCellPt = cellPointers[:,0:-1].copy() + np.array([1.,1.,1.]) + # Need reindex the z index to be from the top-left-close corner and to be from the global top. + ubcCellPt[:,2] = ( nCunderMesh[-1] + 2) - (ubcCellPt[:,2] + cellW) + + # Reorder the ubcCellPt + ubcReorder = np.argsort(ubcCellPt.view(','.join(3*['float'])),axis=0,order=['f2','f1','f0'])[:,0] + # Make a array with the pointers and the withs, that are order in the ubc ordering + indArr = np.concatenate((ubcCellPt[ubcReorder,:],cellW[ubcReorder].reshape((-1,1)) ),axis=1) + + ## Write the UBC octree mesh file + with open(fileName,'w') as mshOut: + mshOut.write('{:.0f} {:.0f} {:.0f}\n'.format(nCunderMesh[0],nCunderMesh[1],nCunderMesh[2])) + mshOut.write('{:.4f} {:.4f} {:.4f}\n'.format(tswCorn[0],tswCorn[1],tswCorn[2])) + mshOut.write('{:.3f} {:.3f} {:.3f}\n'.format(smallCell[0],smallCell[1],smallCell[2])) + mshOut.write('{:.0f} \n'.format(nrCells)) + np.savetxt(mshOut,indArr,fmt='%i') + + ## Print the models + # Assign the model('s) to the object + if modelDict is not None: + # indUBCvector = np.argsort(cX0[np.argsort(np.concatenate((cX0[:,0:2],cX0[:,2:3].max() - cX0[:,2:3]),axis=1).view(','.join(3*['float'])),axis=0,order=('f2','f1','f0'))[:,0]].view(','.join(3*['float'])),axis=0,order=('f2','f1','f0'))[:,0] + for item in modelDict.iteritems(): + # Save the data + np.savetxt(item[0],item[1][ubcReorder],fmt='%3.5e') + def readVTRFile(fileName): """ Read VTK Rectilinear (vtr xml file) and return SimPEG Tensor mesh and model From 25ad1488f5c617a2f9d24aab1af93db76d372595 Mon Sep 17 00:00:00 2001 From: GudniRos Date: Wed, 2 Dec 2015 19:20:21 -0800 Subject: [PATCH 15/77] Added a function to read UBC octree mesh. Updated __init__ to import the new functions. --- SimPEG/Utils/__init__.py | 2 +- SimPEG/Utils/meshutils.py | 66 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/SimPEG/Utils/__init__.py b/SimPEG/Utils/__init__.py index 5280ae79..4d5c8e2e 100644 --- a/SimPEG/Utils/__init__.py +++ b/SimPEG/Utils/__init__.py @@ -1,6 +1,6 @@ from matutils import * from codeutils import * -from meshutils import exampleLrmGrid, meshTensor, closestPoints, readUBCTensorMesh, writeUBCTensorMesh, writeUBCTensorModel, readVTRFile, writeVTRFile +from meshutils import exampleLrmGrid, meshTensor, closestPoints, readUBCTensorMesh, writeUBCTensorMesh, writeUBCocTreeFiles, readUBCocTreeFiles, writeUBCTensorModel, readVTRFile, writeVTRFile, writeVTUFile from curvutils import volTetra, faceInfo, indexCube from interputils import interpmat from ipythonutils import easyAnimate as animate diff --git a/SimPEG/Utils/meshutils.py b/SimPEG/Utils/meshutils.py index 81b15566..3537f0b5 100644 --- a/SimPEG/Utils/meshutils.py +++ b/SimPEG/Utils/meshutils.py @@ -205,11 +205,13 @@ def writeUBCTensorModel(fileName, mesh, model): np.savetxt(fileName, modelMatTR.ravel()) -def writeUBCocTreeFiles(fileName,mesh,modelDict=None,): +def writeUBCocTreeFiles(fileName,mesh,modelDict=None): ''' Write UBC ocTree mesh and model files from a simpeg ocTree mesh and model. - :param + :param str fileName: File to write to + :param simpeg.Mesh.TreeMesh mesh: The mesh + :param dictionary modelDict: The models in a dictionary, where the keys is the name of the of the model file ''' @@ -256,6 +258,66 @@ def writeUBCocTreeFiles(fileName,mesh,modelDict=None,): # Save the data np.savetxt(item[0],item[1][ubcReorder],fmt='%3.5e') +def readUBCocTreeFiles(meshFile,modelFiles=None): + """ + Read UBC 3D OcTree mesh and/or modelFiles + + Input: + :param str meshFile: path to the UBC GIF OcTree mesh file to read + :param list of str modelFiles: list of paths modelFiles + + Output: + :return SimPEG.Mesh.TreeMesh mesh: The octree mesh + :return list of ndarray's: models as a list of numpy array's + """ + + ## Read the file lines + fileLines = np.genfromtxt(meshFile,dtype=str,delimiter='\n') + # Extract the data + nCunderMesh = np.array(fileLines[0].split(),dtype=float) + # I think this is the case? + if np.unique(nCunderMesh).size >1: + raise Exception('SimPEG TreeMeshes have the same number of cell in all directions') + tswCorn = np.array(fileLines[1].split(),dtype=float) + smallCell = np.array(fileLines[2].split(),dtype=float) + nrCells = np.array(fileLines[3].split(),dtype=float) + # Read the index array + indArr = np.genfromtxt(fileLines[4::],dtype=np.int) + + ## Calculate simpeg parameters + h1,h2,h3 = [np.ones(nr)*sz for nr,sz in zip(nCunderMesh,smallCell)] + x0 = tswCorn - np.sum(h3) + # Need to convert the index array to a points list that complies with SimPEG TreeMesh. + # Shift to start at 0 + simpegCellPt = indArr[:,0:-1].copy() - np.array([1.,1.,1.]) + # Need reindex the z index to be from the bottom-left-close corner and to be from the global bottom. + simpegCellPt[:,2] = ( nCunderMesh[-1] + 2) - (simpegCellPt[:,2] - indArr[:,3]) + # Figure out the reordering + simpegReorder = np.argsort(simpegCellPt.view(','.join(3*['float'])),axis=0,order=['f2','f1','f0'])[:,0] + + # Calculate the cell level + simpegLevel = np.log2(np.min(nCunderMesh)) - np.log2(indArr[:,3]) + # Make a pointer matrix + simpegPointers = np.concatenate((simpegCellPt[simpegReorder,:],simpegLevel[simpegReorder].reshape((-1,1))),axis=1) + # Make an index set + + ## Make the tree mesh + from SimPEG.Mesh import TreeMesh + mesh = TreeMesh([h1,h2,h3],x0) + mesh._cells = set([mesh._index(p) for p in simpegPointers.tolist()]) + + if modelFiles is None: + return mesh + else: + modList = [] + for modFile in modelFiles: + modArr = np.loadtxt(modFile) + if len(modArr.shape) == 1: + modList.append(modArr[simpegReorder]) + else: + modList.append(modArr[simpegReorder,:]) + return mesh, modList + def readVTRFile(fileName): """ Read VTK Rectilinear (vtr xml file) and return SimPEG Tensor mesh and model From c298ebe8d8fd33db12d97c8d9013a1148dfd0449 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Fri, 4 Dec 2015 15:42:08 -0800 Subject: [PATCH 16/77] Remove the Meshless Identity Map. - This is now default functionality in the IdentityMap. --- SimPEG/Maps.py | 127 +++++++++++++----------------- tests/base/test_regularization.py | 8 +- 2 files changed, 57 insertions(+), 78 deletions(-) diff --git a/SimPEG/Maps.py b/SimPEG/Maps.py index d36deed4..a3c76e6a 100644 --- a/SimPEG/Maps.py +++ b/SimPEG/Maps.py @@ -10,21 +10,25 @@ class IdentityMap(object): SimPEG Map """ - __metaclass__ = Utils.SimPEGMetaClass - mesh = None #: A SimPEG Mesh - - def __init__(self, mesh, **kwargs): + def __init__(self, mesh=None, nP=None, **kwargs): Utils.setKwargs(self, **kwargs) + + if nP is not None: + assert type(nP) in [int, long], ' Number of parameters must be an integer.' + self.mesh = mesh + self._nP = nP @property def nP(self): """ :rtype: int - :return: number of parameters in the model + :return: number of parameters that the mapping accepts """ + if self._nP is not None: + return self._nP if self.mesh is None: return '*' return self.mesh.nC @@ -32,11 +36,15 @@ class IdentityMap(object): @property def shape(self): """ - The default shape is (mesh.nC, nP). + The default shape is (mesh.nC, nP) if the mesh is defined. + If this is a meshless mapping (i.e. nP is defined independently) + the shape will be the the shape (nP,nP). :rtype: (int,int) :return: shape of the operator as a tuple """ + if self._nP is not None: + return (self.nP, self.nP) if self.mesh is None: return ('*', self.nP) return (self.mesh.nC, self.nP) @@ -119,35 +127,6 @@ class IdentityMap(object): return "%s(%s,%s)" % (self.__class__.__name__, self.shape[0], self.shape[1]) -class IdentityMap_Meshless(IdentityMap): - - def __init__(self, nP=None, **kwargs): - IdentityMap.__init__(self, None, **kwargs) - self._nP = nP - - @property - def nP(self): - """ - :rtype: int - :return: number of parameters in the model - """ - if self._nP is None: - return '*' - return self._nP - - @property - def shape(self): - """ - The default shape is (mesh.nC, nP). - - :rtype: (int,int) - :return: shape of the operator as a tuple - """ - if self._nP is None: - return ('*', '*') - return (self.nP, self.nP) - - class ComboMap(IdentityMap): """Combination of various maps.""" @@ -505,7 +484,7 @@ class ActiveCells(IdentityMap): else: self.valInactive = valInactive.copy() self.valInactive[self.indActive] = 0 - + inds = np.nonzero(self.indActive)[0] self.P = sp.csr_matrix((np.ones(inds.size),(inds, range(inds.size))), shape=(self.nC, self.nP)) @@ -738,7 +717,7 @@ class PolyMap(IdentityMap): Parameterize the model space using a polynomials in a wholespace. ..math:: - + y = \mathbf{V} c Define the model as: @@ -782,10 +761,10 @@ class PolyMap(IdentityMap): else: raise(Exception("Input for normal = X or Y or Z")) #3D - elif self.mesh.dim == 3: + elif self.mesh.dim == 3: X = self.mesh.gridCC[:,0] - Y = self.mesh.gridCC[:,1] - Z = self.mesh.gridCC[:,2] + Y = self.mesh.gridCC[:,1] + Z = self.mesh.gridCC[:,2] if self.normal =='X': f = polynomial.polyval2d(Y, Z, c.reshape((self.order[0]+1,self.order[1]+1))) - X elif self.normal =='Y': @@ -796,43 +775,43 @@ class PolyMap(IdentityMap): raise(Exception("Input for normal = X or Y or Z")) else: raise(Exception("Only supports 2D")) - + return sig1+(sig2-sig1)*(np.arctan(alpha*f)/np.pi+0.5) - + def deriv(self, m): alpha = self.slope sig1,sig2, c = m[0],m[1],m[2:] if self.logSigma: sig1, sig2 = np.exp(sig1), np.exp(sig2) #2D - if self.mesh.dim == 2: + if self.mesh.dim == 2: X = self.mesh.gridCC[:,0] Y = self.mesh.gridCC[:,1] if self.normal =='X': f = polynomial.polyval(Y, c) - X - V = polynomial.polyvander(Y, len(c)-1) + V = polynomial.polyvander(Y, len(c)-1) elif self.normal =='Y': f = polynomial.polyval(X, c) - Y - V = polynomial.polyvander(X, len(c)-1) + V = polynomial.polyvander(X, len(c)-1) else: - raise(Exception("Input for normal = X or Y or Z")) + raise(Exception("Input for normal = X or Y or Z")) #3D - elif self.mesh.dim == 3: + elif self.mesh.dim == 3: X = self.mesh.gridCC[:,0] Y = self.mesh.gridCC[:,1] Z = self.mesh.gridCC[:,2] if self.normal =='X': f = polynomial.polyval2d(Y, Z, c.reshape((self.order[0]+1,self.order[1]+1))) - X - V = polynomial.polyvander2d(Y, Z, self.order) + V = polynomial.polyvander2d(Y, Z, self.order) elif self.normal =='Y': f = polynomial.polyval2d(X, Z, c.reshape((self.order[0]+1,self.order[1]+1))) - Y - V = polynomial.polyvander2d(X, Z, self.order) + V = polynomial.polyvander2d(X, Z, self.order) elif self.normal =='Z': f = polynomial.polyval2d(X, Y, c.reshape((self.order[0]+1,self.order[1]+1))) - Z - V = polynomial.polyvander2d(X, Y, self.order) + V = polynomial.polyvander2d(X, Y, self.order) else: raise(Exception("Input for normal = X or Y or Z")) @@ -845,16 +824,16 @@ class PolyMap(IdentityMap): g3 = Utils.sdiag(alpha*(sig2-sig1)/(1.+(alpha*f)**2)/np.pi)*V - return sp.csr_matrix(np.c_[g1,g2,g3]) + return sp.csr_matrix(np.c_[g1,g2,g3]) class SplineMap(IdentityMap): """SplineMap - Parameterize the boundary of two geological units using a spline interpolation + Parameterize the boundary of two geological units using a spline interpolation ..math:: - + g = f(x)-y Define the model as: @@ -879,7 +858,7 @@ class SplineMap(IdentityMap): def nP(self): if self.mesh.dim == 2: return np.size(self.pts)+2 - elif self.mesh.dim == 3: + elif self.mesh.dim == 3: return np.size(self.pts)*2+2 else: raise(Exception("Only supports 2D and 3D")) @@ -896,28 +875,28 @@ class SplineMap(IdentityMap): X = self.mesh.gridCC[:,0] Y = self.mesh.gridCC[:,1] self.spl = UnivariateSpline(self.pts, c, k=self.order, s=0) - if self.normal =='X': + if self.normal =='X': f = self.spl(Y) - X elif self.normal =='Y': f = self.spl(X) - Y else: raise(Exception("Input for normal = X or Y or Z")) - # 3D: - # Comments: + # 3D: + # Comments: # Make two spline functions and link them using linear interpolation. # This is not quite direct extension of 2D to 3D case # Using 2D interpolation is possible - elif self.mesh.dim == 3: + elif self.mesh.dim == 3: X = self.mesh.gridCC[:,0] - Y = self.mesh.gridCC[:,1] + Y = self.mesh.gridCC[:,1] Z = self.mesh.gridCC[:,2] - npts = np.size(self.pts) + npts = np.size(self.pts) if np.mod(c.size, 2): raise(Exception("Put even points!")) - + self.spl = {"splb":UnivariateSpline(self.pts, c[:npts], k=self.order, s=0), "splt":UnivariateSpline(self.pts, c[npts:], k=self.order, s=0)} @@ -932,7 +911,7 @@ class SplineMap(IdentityMap): raise(Exception("Input for normal = X or Y or Z")) else: raise(Exception("Only supports 2D and 3D")) - + return sig1+(sig2-sig1)*(np.arctan(alpha*f)/np.pi+0.5) @@ -942,7 +921,7 @@ class SplineMap(IdentityMap): if self.logSigma: sig1, sig2 = np.exp(sig1), np.exp(sig2) #2D - if self.mesh.dim == 2: + if self.mesh.dim == 2: X = self.mesh.gridCC[:,0] Y = self.mesh.gridCC[:,1] @@ -951,9 +930,9 @@ class SplineMap(IdentityMap): elif self.normal =='Y': f = self.spl(X) - Y else: - raise(Exception("Input for normal = X or Y or Z")) + raise(Exception("Input for normal = X or Y or Z")) #3D - elif self.mesh.dim == 3: + elif self.mesh.dim == 3: X = self.mesh.gridCC[:,0] Y = self.mesh.gridCC[:,1] Z = self.mesh.gridCC[:,2] @@ -961,7 +940,7 @@ class SplineMap(IdentityMap): zb = self.ptsv[0] zt = self.ptsv[1] flines = (self.spl["splt"](Y)-self.spl["splb"](Y))*(Z-zb)/(zt-zb) + self.spl["splb"](Y) - f = flines - X + f = flines - X # elif self.normal =='Y': # elif self.normal =='Z': else: @@ -974,7 +953,7 @@ class SplineMap(IdentityMap): g1 = -(np.arctan(alpha*f)/np.pi + 0.5) + 1.0 g2 = (np.arctan(alpha*f)/np.pi + 0.5) - + if self.mesh.dim ==2: g3 = np.zeros((self.mesh.nC, self.npts)) if self.normal =='Y': @@ -988,7 +967,7 @@ class SplineMap(IdentityMap): cb = c.copy() dy = self.mesh.hy[ind]*1.5 ca[i] = ctemp+dy - cb[i] = ctemp-dy + cb[i] = ctemp-dy spla = UnivariateSpline(self.pts, ca, k=self.order, s=0) splb = UnivariateSpline(self.pts, cb, k=self.order, s=0) fderiv = (spla(X)-splb(X))/(2*dy) @@ -998,7 +977,7 @@ class SplineMap(IdentityMap): g3 = np.zeros((self.mesh.nC, self.npts*2)) if self.normal =='X': # Here we use perturbation to compute sensitivity - for i in range(self.npts*2): + for i in range(self.npts*2): ctemp = c[i] ind = np.argmin(abs(self.mesh.vectorCCy-ctemp)) ca = c.copy() @@ -1012,20 +991,20 @@ class SplineMap(IdentityMap): splbb = UnivariateSpline(self.pts, cb[:self.npts], k=self.order, s=0) flinesa = (self.spl["splt"](Y)-splba(Y))*(Z-zb)/(zt-zb) + splba(Y) - X flinesb = (self.spl["splt"](Y)-splbb(Y))*(Z-zb)/(zt-zb) + splbb(Y) - X - #treat top boundary + #treat top boundary else: splta = UnivariateSpline(self.pts, ca[self.npts:], k=self.order, s=0) spltb = UnivariateSpline(self.pts, ca[self.npts:], k=self.order, s=0) flinesa = (self.spl["splt"](Y)-splta(Y))*(Z-zb)/(zt-zb) + splta(Y) - X - flinesb = (self.spl["splt"](Y)-spltb(Y))*(Z-zb)/(zt-zb) + spltb(Y) - X - fderiv = (flinesa-flinesb)/(2*dy) + flinesb = (self.spl["splt"](Y)-spltb(Y))*(Z-zb)/(zt-zb) + spltb(Y) - X + fderiv = (flinesa-flinesb)/(2*dy) g3[:,i] = Utils.sdiag(alpha*(sig2-sig1)/(1.+(alpha*f)**2)/np.pi)*fderiv else : raise(Exception("Not Implemented for Y and Z, your turn :)")) - return sp.csr_matrix(np.c_[g1,g2,g3]) + return sp.csr_matrix(np.c_[g1,g2,g3]) + - \ No newline at end of file diff --git a/tests/base/test_regularization.py b/tests/base/test_regularization.py index 3243e51d..050c46ac 100644 --- a/tests/base/test_regularization.py +++ b/tests/base/test_regularization.py @@ -22,7 +22,7 @@ class RegularizationTests(unittest.TestCase): if not inspect.isclass(r): continue if not issubclass(r, Regularization.BaseRegularization): continue - + for i, mesh in enumerate(self.meshlist): print 'Testing %iD'%mesh.dim @@ -32,7 +32,7 @@ class RegularizationTests(unittest.TestCase): m = np.random.rand(mapping.nP) reg.mref = np.ones_like(m)*np.mean(m) - print 'Check: phi_m (mref) = %f' %reg.eval(reg.mref) + print 'Check: phi_m (mref) = %f' %reg.eval(reg.mref) passed = reg.eval(reg.mref) < TOL self.assertTrue(passed) @@ -50,7 +50,7 @@ class RegularizationTests(unittest.TestCase): if not inspect.isclass(r): continue if not issubclass(r, Regularization.BaseRegularization): continue - + for i, mesh in enumerate(self.meshlist): print 'Testing Active Cells %iD'%(mesh.dim) @@ -62,7 +62,7 @@ class RegularizationTests(unittest.TestCase): elif mesh.dim == 3: indAct = Utils.mkvc(mesh.gridCC[:,-1] <= 2*np.sin(2*np.pi*mesh.gridCC[:,0])+0.5 * 2*np.sin(2*np.pi*mesh.gridCC[:,1])+0.5) - mapping = Maps.IdentityMap_Meshless(nP=indAct.nonzero()[0].size) + mapping = Maps.IdentityMap(nP=indAct.nonzero()[0].size) reg = r(mesh, mapping=mapping, indActive=indAct) m = np.random.rand(mesh.nC)[indAct] From 1a40e35c269e1a4974507b9141a85bece85bb5df Mon Sep 17 00:00:00 2001 From: GudniRos Date: Wed, 9 Dec 2015 14:39:00 -0800 Subject: [PATCH 17/77] Fixed import bugs. --- SimPEG/Utils/meshutils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SimPEG/Utils/meshutils.py b/SimPEG/Utils/meshutils.py index 3537f0b5..2750d8bf 100644 --- a/SimPEG/Utils/meshutils.py +++ b/SimPEG/Utils/meshutils.py @@ -458,7 +458,7 @@ def simpegOcTree2vtuObj(simpegOcTreeMesh,modelDict=None): ''' import vtk - from vtk.util.numpy_support import numpy_to_vtk + from vtk.util.numpy_support import numpy_to_vtk, numpy_to_vtkIdTypeArray if str(type(simpegOcTreeMesh)).split()[-1][1:-2] not in 'SimPEG.Mesh.TreeMesh.TreeMesh': raise IOError('simpegOcTreeMesh is not a SimPEG TreeMesh.') @@ -467,14 +467,14 @@ def simpegOcTree2vtuObj(simpegOcTreeMesh,modelDict=None): # Points ptsMat = simpegOcTreeMesh._gridN + simpegOcTreeMesh.x0 vtkPts = vtk.vtkPoints() - vtkPts.SetData(npsup.numpy_to_vtk(ptsMat,deep=True)) + vtkPts.SetData(numpy_to_vtk(ptsMat,deep=True)) # Cells cellConn = np.array([c.nodes for c in simpegOcTreeMesh],dtype=np.int64) cellsMat = np.concatenate((np.ones((cellConn.shape[0],1),dtype=np.int64)*cellConn.shape[1],cellConn),axis=1).ravel() cellsArr = vtk.vtkCellArray() cellsArr.SetNumberOfCells(cellConn.shape[0]) - cellsArr.SetCells(cellConn.shape[0],npsup.numpy_to_vtkIdTypeArray(cellsMat,deep=True)) + cellsArr.SetCells(cellConn.shape[0],numpy_to_vtkIdTypeArray(cellsMat,deep=True)) # Make the object vtuObj = vtk.vtkUnstructuredGrid() @@ -483,14 +483,14 @@ def simpegOcTree2vtuObj(simpegOcTreeMesh,modelDict=None): # Add the level of refinement as a cell array cellSides = np.array([np.array(vtuObj.GetCell(i).GetBounds()).reshape((3,2)).dot(np.array([-1, 1])) for i in np.arange(vtuObj.GetNumberOfCells())]) uniqueLevel, indLevel = np.unique(np.prod(cellSides,axis=1),return_inverse=True) - refineLevelArr = npsup.numpy_to_vtk(indLevel.max() - indLevel,deep=1) + refineLevelArr = numpy_to_vtk(indLevel.max() - indLevel,deep=1) refineLevelArr.SetName('octreeLevel') vtuObj.GetCellData().AddArray(refineLevelArr) # Assign the model('s) to the object if modelDict is not None: for item in modelDict.iteritems(): # Convert numpy array - vtkDoubleArr = npsup.numpy_to_vtk(item[1],deep=1) + vtkDoubleArr = numpy_to_vtk(item[1],deep=1) vtkDoubleArr.SetName(item[0]) vtuObj.GetCellData().AddArray(vtkDoubleArr) From e678affe41be5e41588045b21acde0daf50681e3 Mon Sep 17 00:00:00 2001 From: GudniRos Date: Wed, 9 Dec 2015 15:33:05 -0800 Subject: [PATCH 18/77] Changed ave[F/E]2CC to be a csr not a css, which doesn't support indexing. --- SimPEG/Mesh/TreeMesh.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SimPEG/Mesh/TreeMesh.py b/SimPEG/Mesh/TreeMesh.py index eea2ad4c..c400b956 100644 --- a/SimPEG/Mesh/TreeMesh.py +++ b/SimPEG/Mesh/TreeMesh.py @@ -1706,9 +1706,9 @@ class TreeMesh(BaseTensorMesh, InnerProducts): "Construct the averaging operator on cell faces to cell centers." if getattr(self, '_aveF2CC', None) is None: if self.dim == 2: - self._aveF2CC = 1./self.dim*sp.hstack([self.aveFx2CC, self.aveFy2CC]) + self._aveF2CC = 1./self.dim*sp.hstack([self.aveFx2CC, self.aveFy2CC]).tocsr() elif self.dim == 3: - self._aveF2CC = 1./self.dim*sp.hstack([self.aveFx2CC, self.aveFy2CC, self.aveFz2CC]) + self._aveF2CC = 1./self.dim*sp.hstack([self.aveFx2CC, self.aveFy2CC, self.aveFz2CC]).tocsr() return self._aveF2CC @property @@ -1716,9 +1716,9 @@ class TreeMesh(BaseTensorMesh, InnerProducts): "Construct the averaging operator on cell faces to cell centers." if getattr(self, '_aveF2CCV', None) is None: if self.dim == 2: - self._aveF2CCV = sp.block_diag([self.aveFx2CC, self.aveFy2CC]) + self._aveF2CCV = sp.block_diag([self.aveFx2CC, self.aveFy2CC]).tocsr() elif self.dim == 3: - self._aveF2CCV = sp.block_diag([self.aveFx2CC, self.aveFy2CC, self.aveFz2CC]) + self._aveF2CCV = sp.block_diag([self.aveFx2CC, self.aveFy2CC, self.aveFz2CC]).tocsr() return self._aveF2CCV @property From d6585dcfcdd89ce38a5664fa4023a16774c789ce Mon Sep 17 00:00:00 2001 From: GudniRos Date: Wed, 9 Dec 2015 15:43:51 -0800 Subject: [PATCH 19/77] Add Saving Directive --- SimPEG/Directives.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/SimPEG/Directives.py b/SimPEG/Directives.py index 48d7abcf..349432e4 100644 --- a/SimPEG/Directives.py +++ b/SimPEG/Directives.py @@ -206,6 +206,32 @@ class SaveOutputEveryIteration(_SaveEveryIteration): f.write(' %3d %1.4e %1.4e %1.4e %1.4e\n'%(self.opt.iter, self.invProb.beta, self.invProb.phi_d, self.invProb.phi_m, self.opt.f)) f.close() +class SaveOutputDictEveryIteration(_SaveEveryIteration): + """SaveOutputDictEveryIteration""" + + def initialize(self): + print "SimPEG.SaveOutputDictEveryIteration will save your inversion pro + + def endIter(self): + # Save the data. + ms = self.reg.Ws * ( self.reg.mapping * (self.invProb.curModel - self.r + phi_ms = 0.5*ms.dot(ms) + if self.reg.smoothModel == True: + mref = self.reg.mref + else: + mref = 0 + mx = self.reg.Wx * ( self.reg.mapping * (self.invProb.curModel - mref) + phi_mx = 0.5 * mx.dot(mx) + if self.prob.mesh.dim==2: + my = self.reg.Wy * ( self.reg.mapping * (self.invProb.curModel - mr + phi_my = 0.5 * my.dot(my) + else: + phi_my = 'NaN' + if self.prob.mesh.dim==3: + mz = self.reg.Wz * ( self.reg.mapping * (self.invProb.curModel - mr + phi_mz = 0.5 * mz.dot(mz) + else: + phi_mz = 'NaN' From 4b7f7c3c147403004e00edc27d42f42714c4c5ff Mon Sep 17 00:00:00 2001 From: GudniRos Date: Wed, 9 Dec 2015 15:46:57 -0800 Subject: [PATCH 20/77] Fixed a spelling error --- SimPEG/Directives.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SimPEG/Directives.py b/SimPEG/Directives.py index 349432e4..9e4626ca 100644 --- a/SimPEG/Directives.py +++ b/SimPEG/Directives.py @@ -210,7 +210,8 @@ class SaveOutputDictEveryIteration(_SaveEveryIteration): """SaveOutputDictEveryIteration""" def initialize(self): - print "SimPEG.SaveOutputDictEveryIteration will save your inversion pro + print "SimPEG.SaveOutputDictEveryIteration will save your inversion progress as dictionary: '###-%s.npz'" + def endIter(self): # Save the data. From ba2ac747409a12fb09c202d8fd61d9aa4a4ffe99 Mon Sep 17 00:00:00 2001 From: GudniRos Date: Wed, 9 Dec 2015 15:53:41 -0800 Subject: [PATCH 21/77] Fixed code error --- SimPEG/Directives.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/SimPEG/Directives.py b/SimPEG/Directives.py index 9e4626ca..e91ffb71 100644 --- a/SimPEG/Directives.py +++ b/SimPEG/Directives.py @@ -210,31 +210,34 @@ class SaveOutputDictEveryIteration(_SaveEveryIteration): """SaveOutputDictEveryIteration""" def initialize(self): - print "SimPEG.SaveOutputDictEveryIteration will save your inversion progress as dictionary: '###-%s.npz'" - + print "SimPEG.SaveOutputDictEveryIteration will save your inversion progress as dictionary: '###-%s.npz'"%self.fileName def endIter(self): # Save the data. - ms = self.reg.Ws * ( self.reg.mapping * (self.invProb.curModel - self.r + ms = self.reg.Ws * ( self.reg.mapping * (self.invProb.curModel - self.reg.mref) ) phi_ms = 0.5*ms.dot(ms) if self.reg.smoothModel == True: mref = self.reg.mref else: mref = 0 - mx = self.reg.Wx * ( self.reg.mapping * (self.invProb.curModel - mref) + mx = self.reg.Wx * ( self.reg.mapping * (self.invProb.curModel - mref) ) phi_mx = 0.5 * mx.dot(mx) if self.prob.mesh.dim==2: - my = self.reg.Wy * ( self.reg.mapping * (self.invProb.curModel - mr + my = self.reg.Wy * ( self.reg.mapping * (self.invProb.curModel - mref) ) phi_my = 0.5 * my.dot(my) else: phi_my = 'NaN' if self.prob.mesh.dim==3: - mz = self.reg.Wz * ( self.reg.mapping * (self.invProb.curModel - mr + mz = self.reg.Wz * ( self.reg.mapping * (self.invProb.curModel - mref) ) phi_mz = 0.5 * mz.dot(mz) else: phi_mz = 'NaN' + # Save the file as a npz + np.savez('{:03d}-{:s}'.format(self.opt.iter,self.fileName), iter=self.opt.iter, beta=self.invProb.beta, phi_d=self.invProb.phi_d, phi_m=self.invProb.phi_m, phi_ms=phi_ms, phi_mx=phi_mx, phi_my=phi_my, phi_mz=phi_mz,f=self.opt.f, m=self.invProb.curModel) + + # class UpdateReferenceModel(Parameter): From 69d109524e50c27e29b61d02e29adbd5f8c4fdbc Mon Sep 17 00:00:00 2001 From: GudniRos Date: Sat, 12 Dec 2015 13:52:26 -0800 Subject: [PATCH 22/77] Working on reordering of UBC models. --- .gitignore | 1 + SimPEG/Utils/meshutils.py | 33 ++++++++++++++++++++------------- tests/mesh/test_MeshIO.py | 0 3 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 tests/mesh/test_MeshIO.py diff --git a/.gitignore b/.gitignore index fc798cea..6a826230 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ nosetests.xml *.sublime-workspace docs/_build/ Makefile +diff.temp diff --git a/SimPEG/Utils/meshutils.py b/SimPEG/Utils/meshutils.py index 2750d8bf..ce7e1228 100644 --- a/SimPEG/Utils/meshutils.py +++ b/SimPEG/Utils/meshutils.py @@ -286,25 +286,26 @@ def readUBCocTreeFiles(meshFile,modelFiles=None): ## Calculate simpeg parameters h1,h2,h3 = [np.ones(nr)*sz for nr,sz in zip(nCunderMesh,smallCell)] - x0 = tswCorn - np.sum(h3) + x0 = tswCorn - np.array([0,0,np.sum(h3)]) # Need to convert the index array to a points list that complies with SimPEG TreeMesh. # Shift to start at 0 - simpegCellPt = indArr[:,0:-1].copy() - np.array([1.,1.,1.]) + simpegCellPt = indArr[:,0:-1].copy() + simpegCellPt[:,2] = ( nCunderMesh[-1] + 2) - (simpegCellPt[:,2] + indArr[:,3]) # Need reindex the z index to be from the bottom-left-close corner and to be from the global bottom. - simpegCellPt[:,2] = ( nCunderMesh[-1] + 2) - (simpegCellPt[:,2] - indArr[:,3]) - # Figure out the reordering - simpegReorder = np.argsort(simpegCellPt.view(','.join(3*['float'])),axis=0,order=['f2','f1','f0'])[:,0] + simpegCellPt = simpegCellPt - np.array([1.,1.,1.]) # Calculate the cell level simpegLevel = np.log2(np.min(nCunderMesh)) - np.log2(indArr[:,3]) # Make a pointer matrix - simpegPointers = np.concatenate((simpegCellPt[simpegReorder,:],simpegLevel[simpegReorder].reshape((-1,1))),axis=1) - # Make an index set + simpegPointers = np.concatenate((simpegCellPt,simpegLevel.reshape((-1,1))),axis=1) + + # Figure out the reordering + simpegReorder = np.argsort((np.array([[1,1,1,-1]])*simpegPointers).view(','.join(4*['float'])),axis=0,order=['f3','f2','f1','f0'])[:,0] ## Make the tree mesh from SimPEG.Mesh import TreeMesh mesh = TreeMesh([h1,h2,h3],x0) - mesh._cells = set([mesh._index(p) for p in simpegPointers.tolist()]) + mesh._cells = set([mesh._index(p) for p in simpegPointers[simpegReorder,:].tolist()]) if modelFiles is None: return mesh @@ -427,7 +428,10 @@ def writeVTRFile(fileName,mesh,model=None): raise IOError('{:s} is an incorrect extension, has to be .vtr') # Write the file. vtrWriteFilter = rectWriter() - vtrWriteFilter.SetInput(vtkObj) + if float(VTK_VERSION.split('.')[0]) >=6: + vtrWriteFilter.SetInputData(vtkObj) + else: + vtuWriteFilter.SetInput(vtuObj) vtrWriteFilter.SetFileName(fileName) vtrWriteFilter.Update() @@ -435,7 +439,7 @@ def writeVTUFile(fileName,ocTreeMesh,modelDict=None): ''' Function to write a VTU file from a SimPEG TreeMesh and model. ''' - from vtk import vtkXMLUnstructuredGridWriter as Writer + from vtk import vtkXMLUnstructuredGridWriter as Writer, VTK_VERSION from vtk.util.numpy_support import numpy_to_vtk # Make the object @@ -443,11 +447,10 @@ def writeVTUFile(fileName,ocTreeMesh,modelDict=None): # Make the writer vtuWriteFilter = Writer() - if float(vtk.VTK_VERSION.split('.')[0]) >=6: + if float(VTK_VERSION.split('.')[0]) >=6: vtuWriteFilter.SetInputData(vtuObj) else: vtuWriteFilter.SetInput(vtuObj) - vtuWriteFilter.SetInput(vtuObj) vtuWriteFilter.SetFileName(fileName) # Write the file vtuWriteFilter.Update() @@ -465,7 +468,11 @@ def simpegOcTree2vtuObj(simpegOcTreeMesh,modelDict=None): # Make the data parts for the vtu object # Points - ptsMat = simpegOcTreeMesh._gridN + simpegOcTreeMesh.x0 + try: + ptsMat = simpegOcTreeMesh._gridN + simpegOcTreeMesh.x0 + except: + simpegOcTreeMesh.number() + ptsMat = simpegOcTreeMesh._gridN + simpegOcTreeMesh.x0 vtkPts = vtk.vtkPoints() vtkPts.SetData(numpy_to_vtk(ptsMat,deep=True)) # Cells diff --git a/tests/mesh/test_MeshIO.py b/tests/mesh/test_MeshIO.py new file mode 100644 index 00000000..e69de29b From 84eb69f6268c1d9a6bc8b0fab36d270e1d1192a9 Mon Sep 17 00:00:00 2001 From: GudniRos Date: Mon, 14 Dec 2015 01:46:01 -0800 Subject: [PATCH 23/77] UBC ocTree read and write working. --- SimPEG/Utils/meshutils.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/SimPEG/Utils/meshutils.py b/SimPEG/Utils/meshutils.py index ce7e1228..c43dfd13 100644 --- a/SimPEG/Utils/meshutils.py +++ b/SimPEG/Utils/meshutils.py @@ -299,13 +299,14 @@ def readUBCocTreeFiles(meshFile,modelFiles=None): # Make a pointer matrix simpegPointers = np.concatenate((simpegCellPt,simpegLevel.reshape((-1,1))),axis=1) - # Figure out the reordering - simpegReorder = np.argsort((np.array([[1,1,1,-1]])*simpegPointers).view(','.join(4*['float'])),axis=0,order=['f3','f2','f1','f0'])[:,0] - ## Make the tree mesh from SimPEG.Mesh import TreeMesh mesh = TreeMesh([h1,h2,h3],x0) - mesh._cells = set([mesh._index(p) for p in simpegPointers[simpegReorder,:].tolist()]) + mesh._cells = set([mesh._index(p) for p in simpegPointers.tolist()]) + + # Figure out the reordering + simpegReorder = np.argsort(np.array([mesh._index(i) for i in simpegPointers.tolist()])) + # simpegReorder = np.argsort((np.array([[1,1,1,-1]])*simpegPointers).view(','.join(4*['float'])),axis=0,order=['f3','f2','f1','f0'])[:,0] if modelFiles is None: return mesh From e42727610aad540694977adaf0f53a374c29ac90 Mon Sep 17 00:00:00 2001 From: GudniRos Date: Mon, 14 Dec 2015 19:08:35 -0800 Subject: [PATCH 24/77] Implemented IO test for octree mesh. --- tests/mesh/test_MeshIO.py | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/mesh/test_MeshIO.py b/tests/mesh/test_MeshIO.py index e69de29b..e3e49548 100644 --- a/tests/mesh/test_MeshIO.py +++ b/tests/mesh/test_MeshIO.py @@ -0,0 +1,47 @@ +import numpy as np +import unittest +import SimPEG as simpeg +from SimPEG.Mesh import TensorMesh, TreeMesh + + +class TestOcTreeIO(unittest.TestCase): + + def setUp(self): + h = np.ones(16) + mesh = simpeg.Mesh.TreeMesh([h,2*h,3*h]) + mesh.refine(3) + mesh._refineCell([0,0,0,3]) + mesh._refineCell([0,2,0,3]) + self.mesh = mesh + + def test_UBCfiles(self): + + mesh = self.mesh + # Make a vector + vec = np.arange(mesh.nC) + # Write aand read + simpeg.Utils.meshutils.writeUBCocTreeFiles('temp.msh',mesh,{'arange.txt':vec}) + meshUBC, vecUBC = simpeg.Utils.meshutils.readUBCocTreeFiles('temp.msh',['arange.txt']) + + # The mesh + assert mesh.__str__() == meshUBC.__str__() + assert np.sum(mesh.gridCC - meshUBC.gridCC) == 0 + assert np.sum(vec - vecUBC) == 0 + assert np.all(np.array(mesh.h) - np.array(meshUBC.h) == 0) + print 'IO of UBC octree files is working' + + def test_VTUfiles(self): + mesh = self.mesh + vec = np.arange(mesh.nC) + try: + simpeg.Utils.meshutils.writeVTUFile('test.vtu',mesh,{'arange':vec}) + run = True + except: + run = False + assert run + print 'Writing of VTU files is working' + + + +if __name__ == '__main__': + unittest.main() From 79f7ca7a1e566d8848381a108737276666504105 Mon Sep 17 00:00:00 2001 From: GudniRos Date: Tue, 15 Dec 2015 19:35:18 -0800 Subject: [PATCH 25/77] Adden dpred to be written in the saveDict directive --- SimPEG/Directives.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SimPEG/Directives.py b/SimPEG/Directives.py index e91ffb71..46576df5 100644 --- a/SimPEG/Directives.py +++ b/SimPEG/Directives.py @@ -235,7 +235,7 @@ class SaveOutputDictEveryIteration(_SaveEveryIteration): # Save the file as a npz - np.savez('{:03d}-{:s}'.format(self.opt.iter,self.fileName), iter=self.opt.iter, beta=self.invProb.beta, phi_d=self.invProb.phi_d, phi_m=self.invProb.phi_m, phi_ms=phi_ms, phi_mx=phi_mx, phi_my=phi_my, phi_mz=phi_mz,f=self.opt.f, m=self.invProb.curModel) + np.savez('{:03d}-{:s}'.format(self.opt.iter,self.fileName), iter=self.opt.iter, beta=self.invProb.beta, phi_d=self.invProb.phi_d, phi_m=self.invProb.phi_m, phi_ms=phi_ms, phi_mx=phi_mx, phi_my=phi_my, phi_mz=phi_mz,f=self.opt.f, m=self.invProb.curModel,dpred=self.invProb.dpred) From 43424503600e392dd4300a302ffdab65419b9b9b Mon Sep 17 00:00:00 2001 From: GudniRos Date: Thu, 17 Dec 2015 23:02:52 -0800 Subject: [PATCH 26/77] Updated test_MeshIO to remove the temp files after using them. --- tests/mesh/test_MeshIO.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/mesh/test_MeshIO.py b/tests/mesh/test_MeshIO.py index e3e49548..43c0e33e 100644 --- a/tests/mesh/test_MeshIO.py +++ b/tests/mesh/test_MeshIO.py @@ -1,5 +1,5 @@ import numpy as np -import unittest +import unittest, os import SimPEG as simpeg from SimPEG.Mesh import TensorMesh, TreeMesh @@ -29,17 +29,20 @@ class TestOcTreeIO(unittest.TestCase): assert np.sum(vec - vecUBC) == 0 assert np.all(np.array(mesh.h) - np.array(meshUBC.h) == 0) print 'IO of UBC octree files is working' + os.remove('temp.msh') + os.remove('arange.txt') def test_VTUfiles(self): mesh = self.mesh vec = np.arange(mesh.nC) try: - simpeg.Utils.meshutils.writeVTUFile('test.vtu',mesh,{'arange':vec}) + simpeg.Utils.meshutils.writeVTUFile('temp.vtu',mesh,{'arange':vec}) run = True except: run = False assert run print 'Writing of VTU files is working' + os.remove('temp.vtu') From f0f3f6c06a0640bc5ded5359b6f5424404e1421d Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sat, 2 Jan 2016 18:06:49 -0800 Subject: [PATCH 27/77] Update the examples init.py to explicitly reference things. You can run the main function to update the docs and imports. --- SimPEG/Examples/__init__.py | 62 ++++++++++++++++++++++++++++--------- docs/api_Tests.rst | 2 +- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/SimPEG/Examples/__init__.py b/SimPEG/Examples/__init__.py index ac219a7f..191664bb 100644 --- a/SimPEG/Examples/__init__.py +++ b/SimPEG/Examples/__init__.py @@ -1,24 +1,60 @@ -# This will import everything in the directory into this file -from os import path as p -from glob import glob -__all__ = [] -for x in glob(p.join(p.dirname(__file__), '*.py')): - if not p.basename(x).startswith('__'): - __import__(p.basename(x)[:-3], globals(), locals()) - __all__ += [p.basename(x)[:-3]] -del glob, p, x +# Run this file to add imports. + +##### AUTOIMPORTS ##### +import EM_FDEM_1D_Inversion +import FLOW_Richards_1D_Celia1990 +import Forward_BasicDirectCurrent +import Inversion_Linear +import Mesh_Basic_PlotImage +import Mesh_Basic_Types +import Mesh_Operators_CahnHilliard +import Mesh_QuadTree_Creation +import Mesh_QuadTree_FaceDiv +import Mesh_QuadTree_HangingNodes +import Mesh_Tensor_Creation +##### AUTOIMPORTS ##### if __name__ == '__main__': """ - Run the following to create the examples documentation. + Run the following to create the examples documentation and add to the imports at the top. """ import shutil, os from SimPEG import Examples + # Create the examples dir in the docs folder. + docExamplesDir = os.path.sep.join(os.path.realpath(__file__).split(os.path.sep)[:-3] + ['docs', 'examples']) + shutil.rmtree(docExamplesDir) + os.makedirs(docExamplesDir) + + # Get all the python examples in this folder + thispath = os.path.sep.join(__file__.split(os.path.sep)[:-1]) + onlyfiles = [f[:-3] for f in os.listdir(thispath) if os.path.isfile(os.path.join(thispath, f)) and f.endswith('.py') and not f.startswith('_')] + + # Add the imports to the top in the AUTOIMPORTS section + f = file(__file__, 'r') + inimports = False + out = '' + for line in f: + if not inimports: + out += line + + if line == "##### AUTOIMPORTS #####\n": + inimports = not inimports + if inimports: + out += '\n'.join(["import %s"%_ for _ in onlyfiles]) + out += '\n##### AUTOIMPORTS #####\n' + f.close() + + f = file(__file__, 'w') + f.write(out) + f.close() + + def _makeExample(filePath, runFunction): + """Makes the example given a path of the file and the run function.""" filePath = os.path.realpath(filePath) name = filePath.split(os.path.sep)[-1].rstrip('.pyc').rstrip('.py') @@ -52,15 +88,11 @@ if __name__ == '__main__': rst = os.path.sep.join((filePath.split(os.path.sep)[:-3] + ['docs', 'examples', name + '.rst'])) + print 'Creating: %s.rst'%name f = open(rst, 'w') f.write(out) f.close() - - docExamplesDir = os.path.sep.join(os.path.realpath(__file__).split(os.path.sep)[:-3] + ['docs', 'examples']) - shutil.rmtree(docExamplesDir) - os.makedirs(docExamplesDir) - for ex in dir(Examples): if ex.startswith('_'): continue E = getattr(Examples,ex) diff --git a/docs/api_Tests.rst b/docs/api_Tests.rst index 614d8747..b0b2fbd0 100644 --- a/docs/api_Tests.rst +++ b/docs/api_Tests.rst @@ -3,6 +3,6 @@ Testing SimPEG ============== -.. automodule:: SimPEG.Tests.TestUtils +.. automodule:: SimPEG.Tests :members: :undoc-members: From a18d48348d83b800115ff0a00cd2e8559a1b128a Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sat, 2 Jan 2016 18:20:58 -0800 Subject: [PATCH 28/77] Merge fast tests on travis. --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index b9826bab..8c8637af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,15 +5,13 @@ python: sudo: false env: + - TEST_DIR="tests/mesh tests/base tests/utils" + - TEST_DIR=tests/examples - TEST_DIR=tests/em/fdem/forward - TEST_DIR=tests/em/fdem/inverse/derivs - TEST_DIR=tests/em/fdem/inverse/adjoint - TEST_DIR=tests/em/tdem - - TEST_DIR=tests/mesh - TEST_DIR=tests/flow - - TEST_DIR=tests/utils - - TEST_DIR=tests/base - - TEST_DIR=tests/examples # Setup anaconda before_install: From d64fd4ae3558ae1771aa342ed1a8460184b2cd00 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sat, 2 Jan 2016 18:29:04 -0800 Subject: [PATCH 29/77] List examples in the init file. --- SimPEG/Examples/__init__.py | 10 +++++++--- tests/examples/test_examples.py | 4 +--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/SimPEG/Examples/__init__.py b/SimPEG/Examples/__init__.py index 191664bb..8431e4ba 100644 --- a/SimPEG/Examples/__init__.py +++ b/SimPEG/Examples/__init__.py @@ -12,6 +12,9 @@ import Mesh_QuadTree_Creation import Mesh_QuadTree_FaceDiv import Mesh_QuadTree_HangingNodes import Mesh_Tensor_Creation + +__examples__ = ["EM_FDEM_1D_Inversion", "FLOW_Richards_1D_Celia1990", "Forward_BasicDirectCurrent", "Inversion_Linear", "Mesh_Basic_PlotImage", "Mesh_Basic_Types", "Mesh_Operators_CahnHilliard", "Mesh_QuadTree_Creation", "Mesh_QuadTree_FaceDiv", "Mesh_QuadTree_HangingNodes", "Mesh_Tensor_Creation"] + ##### AUTOIMPORTS ##### if __name__ == '__main__': @@ -30,8 +33,8 @@ if __name__ == '__main__': os.makedirs(docExamplesDir) # Get all the python examples in this folder - thispath = os.path.sep.join(__file__.split(os.path.sep)[:-1]) - onlyfiles = [f[:-3] for f in os.listdir(thispath) if os.path.isfile(os.path.join(thispath, f)) and f.endswith('.py') and not f.startswith('_')] + thispath = os.path.sep.join(__file__.split(os.path.sep)[:-1]) + exfiles = [f[:-3] for f in os.listdir(thispath) if os.path.isfile(os.path.join(thispath, f)) and f.endswith('.py') and not f.startswith('_')] # Add the imports to the top in the AUTOIMPORTS section f = file(__file__, 'r') @@ -44,7 +47,8 @@ if __name__ == '__main__': if line == "##### AUTOIMPORTS #####\n": inimports = not inimports if inimports: - out += '\n'.join(["import %s"%_ for _ in onlyfiles]) + out += '\n'.join(["import %s"%_ for _ in exfiles]) + out += '\n\n__examples__ = ["' + '", "'.join(exfiles)+ '"]\n' out += '\n##### AUTOIMPORTS #####\n' f.close() diff --git a/tests/examples/test_examples.py b/tests/examples/test_examples.py index 48cbaa65..cffa2094 100644 --- a/tests/examples/test_examples.py +++ b/tests/examples/test_examples.py @@ -10,10 +10,8 @@ def get(test): self.assertTrue(True) return func attrs = dict() -tests = [_ for _ in dir(Examples) if not _.startswith('_')] -for test in tests: +for test in Examples.__examples__: attrs['test_'+test] = get(test) -del get, tests, _ TestExamples = type('TestExamples', (unittest.TestCase,), attrs) From dd5f5df69eefb8ed106376c73a37b0466fa3adfd Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sun, 3 Jan 2016 12:48:01 -0800 Subject: [PATCH 30/77] my_function.__name__ must be set to 'test_' for nosetests --- tests/examples/test_examples.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/examples/test_examples.py b/tests/examples/test_examples.py index cffa2094..2e4803b1 100644 --- a/tests/examples/test_examples.py +++ b/tests/examples/test_examples.py @@ -4,11 +4,11 @@ from SimPEG import Examples import numpy as np def get(test): - def func(self): + def test_func(self): print '\nTesting %s.run(plotIt=False)\n'%test getattr(Examples, test).run(plotIt=False) self.assertTrue(True) - return func + return test_func attrs = dict() for test in Examples.__examples__: attrs['test_'+test] = get(test) From 9ebd0fcedc0aa830dcbad483d67d252ed543bf2d Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sun, 3 Jan 2016 19:02:06 -0800 Subject: [PATCH 31/77] Use pymatsolver in TravisCI --- .travis.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8c8637af..db79b031 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,16 @@ python: sudo: false +addons: + apt: + packages: + - gcc + - gfortran + - libopenmpi-dev + - libmumps-seq-dev + - libblas-dev + - liblapack-dev + env: - TEST_DIR="tests/mesh tests/base tests/utils" - TEST_DIR=tests/examples @@ -23,9 +33,12 @@ before_install: # Install packages install: - - conda install --yes pip python=$TRAVIS_PYTHON_VERSION numpy scipy matplotlib cython ipython + - conda install --yes pip python=$TRAVIS_PYTHON_VERSION numpy scipy matplotlib cython ipython nose - pip install nose-cov python-coveralls - # - pip install -r requirements.txt + + - git clone https://github.com/rowanc1/pymatsolver.git + - cd pymatsolver; python setup.py install; cd .. + - python setup.py install - python setup.py build_ext --inplace From 00a29d27aa6f1bb0815342a5e3d4cf862db34d36 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sun, 3 Jan 2016 19:11:45 -0800 Subject: [PATCH 32/77] Decrease the number of iterations on the EM derivatives. This is to increase the speed at which travis runs tests. --- tests/em/fdem/inverse/derivs/test_FDEM_derivs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/em/fdem/inverse/derivs/test_FDEM_derivs.py b/tests/em/fdem/inverse/derivs/test_FDEM_derivs.py index 52108c4e..d3bcb218 100644 --- a/tests/em/fdem/inverse/derivs/test_FDEM_derivs.py +++ b/tests/em/fdem/inverse/derivs/test_FDEM_derivs.py @@ -38,7 +38,7 @@ def derivTest(fdemType, comp): survey = prb.survey def fun(x): return survey.dpred(x), lambda x: prb.Jvec(x0, x) - return Tests.checkDerivative(fun, x0, num=3, plotIt=False, eps=FLR) + return Tests.checkDerivative(fun, x0, num=2, plotIt=False, eps=FLR) class FDEM_DerivTests(unittest.TestCase): From d93a23306ce222eee6c3caab58ba08979e5b4154 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sun, 10 Jan 2016 12:47:43 -0800 Subject: [PATCH 33/77] =?UTF-8?q?Bump=20version:=200.1.3=20=E2=86=92=200.1?= =?UTF-8?q?.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- SimPEG/__init__.py | 2 +- docs/conf.py | 4 ++-- setup.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index f739d080..f5efbf0d 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,4 +1,4 @@ [bumpversion] -current_version = 0.1.3 +current_version = 0.1.4 files = setup.py SimPEG/__init__.py docs/conf.py diff --git a/SimPEG/__init__.py b/SimPEG/__init__.py index 369134e7..94bf255f 100644 --- a/SimPEG/__init__.py +++ b/SimPEG/__init__.py @@ -15,7 +15,7 @@ import Directives import Inversion import Tests -__version__ = '0.1.3' +__version__ = '0.1.4' __author__ = 'Rowan Cockett' __license__ = 'MIT' __copyright__ = 'Copyright 2014 Rowan Cockett' diff --git a/docs/conf.py b/docs/conf.py index 3bb33621..4f3f4462 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,9 +51,9 @@ copyright = u'2013, SimPEG Developers' # built documents. # # The short X.Y version. -version = '0.1.3' +version = '0.1.4' # The full version, including alpha/beta/rc tags. -release = '0.1.3' +release = '0.1.4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 531dc539..a90d6119 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,7 @@ with open("README.rst") as f: setup( name = "SimPEG", - version = "0.1.3", + version = "0.1.4", packages = find_packages(), install_requires = ['numpy>=1.7', 'scipy>=0.13', From 8da717521cf95484c21812a94982c18d8c5356c8 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sun, 10 Jan 2016 13:01:00 -0800 Subject: [PATCH 34/77] Update scripts for pip --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index a90d6119..34c29703 100644 --- a/setup.py +++ b/setup.py @@ -64,6 +64,7 @@ cython_files = [ "SimPEG/Mesh/TreeUtils" ] extensions = [Extension(f, [f+ext]) for f in cython_files] +scripts = [f+'.pyx' for f in cython_files] if USE_CYTHON and "cleanall" not in args: from Cython.Build import cythonize @@ -95,5 +96,6 @@ setup( use_2to3 = False, include_dirs=[np.get_include()], ext_modules = extensions, + scripts=scripts, **cythonKwargs ) From 985d5b6469f39d56aea9d5d94ccb1e59fc10dee0 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sun, 10 Jan 2016 13:01:05 -0800 Subject: [PATCH 35/77] =?UTF-8?q?Bump=20version:=200.1.7=20=E2=86=92=200.1?= =?UTF-8?q?.8=20(+7=20squashed=20commits)=20Squashed=20commits:=20[ac5bb36?= =?UTF-8?q?]=20Bump=20version:=200.1.8=20=E2=86=92=200.1.9=20[8acd6b2]=20I?= =?UTF-8?q?mportException=20-->=20ImportError=20[ac410a8]=20matplotlib.pyp?= =?UTF-8?q?lot=20has=20errors=20on=20import,=20put=20these=20in=20the=20fu?= =?UTF-8?q?nctions=20that=20rely=20on=20them=20directly.=20[f128a20]=20Bum?= =?UTF-8?q?p=20version:=200.1.6=20=E2=86=92=200.1.7=20[5866bea]=20Remove?= =?UTF-8?q?=20IPython=20utils.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are out of date, and have problems on Linux (without a proper visual backend). [a519e56] Bump version: 0.1.5 → 0.1.6 [f45aa83] Bump version: 0.1.4 → 0.1.5 --- .bumpversion.cfg | 2 +- SimPEG/EM/Analytics/FDEM.py | 1 - SimPEG/Examples/EM_FDEM_1D_Inversion.py | 5 +- SimPEG/Examples/FLOW_Richards_1D_Celia1990.py | 2 +- SimPEG/Examples/Forward_BasicDirectCurrent.py | 7 +- SimPEG/Mesh/TreeMesh.py | 33 +++++++-- SimPEG/Mesh/View.py | 68 ++----------------- SimPEG/Tests.py | 2 +- SimPEG/Utils/__init__.py | 1 - SimPEG/Utils/ipythonutils.py | 28 -------- SimPEG/__init__.py | 2 +- docs/conf.py | 4 +- setup.py | 2 +- 13 files changed, 50 insertions(+), 107 deletions(-) delete mode 100644 SimPEG/Utils/ipythonutils.py diff --git a/.bumpversion.cfg b/.bumpversion.cfg index f5efbf0d..ea37cced 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,4 +1,4 @@ [bumpversion] -current_version = 0.1.4 +current_version = 0.1.9 files = setup.py SimPEG/__init__.py docs/conf.py diff --git a/SimPEG/EM/Analytics/FDEM.py b/SimPEG/EM/Analytics/FDEM.py index ce7e623d..9e776fdf 100644 --- a/SimPEG/EM/Analytics/FDEM.py +++ b/SimPEG/EM/Analytics/FDEM.py @@ -2,7 +2,6 @@ from __future__ import division import numpy as np from scipy.constants import mu_0, pi from scipy.special import erf -import matplotlib.pyplot as plt from SimPEG import Utils diff --git a/SimPEG/Examples/EM_FDEM_1D_Inversion.py b/SimPEG/Examples/EM_FDEM_1D_Inversion.py index fdf2750e..aba70f4b 100644 --- a/SimPEG/Examples/EM_FDEM_1D_Inversion.py +++ b/SimPEG/Examples/EM_FDEM_1D_Inversion.py @@ -1,7 +1,7 @@ from SimPEG import * import SimPEG.EM as EM from scipy.constants import mu_0 -import matplotlib.pyplot as plt + def run(plotIt=True): """ @@ -31,6 +31,7 @@ def run(plotIt=True): if plotIt: + import matplotlib.pyplot as plt fig, ax = plt.subplots(1,1, figsize = (3, 6)) plt.semilogx(sigma[active], mesh.vectorCCz[active]) ax.set_ylim(-600, 0) @@ -60,6 +61,7 @@ def run(plotIt=True): survey.Wd = 1/(abs(survey.dobs)*std) if plotIt: + import matplotlib.pyplot as plt fig, ax = plt.subplots(1,1, figsize = (10, 6)) ax.loglog(rx.times, dtrue, 'b.-') ax.loglog(rx.times, survey.dobs, 'r.-') @@ -88,6 +90,7 @@ def run(plotIt=True): mopt = inv.run(m0) if plotIt: + import matplotlib.pyplot as plt fig, ax = plt.subplots(1,1, figsize = (3, 6)) plt.semilogx(sigma[active], mesh.vectorCCz[active]) plt.semilogx(np.exp(mopt), mesh.vectorCCz[active]) diff --git a/SimPEG/Examples/FLOW_Richards_1D_Celia1990.py b/SimPEG/Examples/FLOW_Richards_1D_Celia1990.py index 7c663e6b..4c90ea7b 100644 --- a/SimPEG/Examples/FLOW_Richards_1D_Celia1990.py +++ b/SimPEG/Examples/FLOW_Richards_1D_Celia1990.py @@ -1,6 +1,5 @@ from SimPEG import * from SimPEG.FLOW import Richards -import matplotlib.pyplot as plt def run(plotIt=True): """ @@ -61,6 +60,7 @@ def run(plotIt=True): Hs_H120= getFields(120.,'head') if not plotIt:return + import matplotlib.pyplot as plt plt.figure(figsize=(13,5)) plt.subplot(121) plt.plot(40-M.gridCC, Hs_M10[-1],'b-') diff --git a/SimPEG/Examples/Forward_BasicDirectCurrent.py b/SimPEG/Examples/Forward_BasicDirectCurrent.py index 06c9e79e..efbb287c 100644 --- a/SimPEG/Examples/Forward_BasicDirectCurrent.py +++ b/SimPEG/Examples/Forward_BasicDirectCurrent.py @@ -1,7 +1,4 @@ from SimPEG import Mesh, Utils, np, SolverLU -import matplotlib.pyplot as plt -import matplotlib -from matplotlib.mlab import griddata ## 2D DC forward modeling example with Tensor and Curvilinear Meshes @@ -39,6 +36,10 @@ def run(plotIt=True): if not plotIt: return + import matplotlib.pyplot as plt + import matplotlib + from matplotlib.mlab import griddata + #Step4: Making Figure fig, axes = plt.subplots(1,2,figsize=(12*1.2,4*1.2)) label = ["(a)", "(b)"] diff --git a/SimPEG/Mesh/TreeMesh.py b/SimPEG/Mesh/TreeMesh.py index 997f7be9..1d5c8c8c 100644 --- a/SimPEG/Mesh/TreeMesh.py +++ b/SimPEG/Mesh/TreeMesh.py @@ -90,11 +90,6 @@ # from SimPEG import np, sp, Utils, Solver -import matplotlib.pyplot as plt -import matplotlib -from mpl_toolkits.mplot3d import Axes3D -import matplotlib.colors as colors -import matplotlib.cm as cmx try: import TreeUtils @@ -1973,6 +1968,13 @@ class TreeMesh(BaseTensorMesh, InnerProducts): facesX=False, facesY=False, facesZ=False, edgesX=False, edgesY=False, edgesZ=False): + + import matplotlib.pyplot as plt + import matplotlib + from mpl_toolkits.mplot3d import Axes3D + import matplotlib.colors as colors + import matplotlib.cm as cmx + # self.number() axOpts = {'projection':'3d'} if self.dim == 3 else {} @@ -2094,6 +2096,13 @@ class TreeMesh(BaseTensorMesh, InnerProducts): def plotImage(self, I, ax=None, showIt=False, grid=False, clim=None): if self.dim == 3: raise Exception('Use plot slice?') + + import matplotlib.pyplot as plt + import matplotlib + from mpl_toolkits.mplot3d import Axes3D + import matplotlib.colors as colors + import matplotlib.cm as cmx + if ax is None: ax = plt.subplot(111) jet = cm = plt.get_cmap('jet') cNorm = colors.Normalize( @@ -2123,6 +2132,13 @@ class TreeMesh(BaseTensorMesh, InnerProducts): assert vType in ['CC','F','E'] assert self.dim == 3 + + import matplotlib.pyplot as plt + import matplotlib + from mpl_toolkits.mplot3d import Axes3D + import matplotlib.colors as colors + import matplotlib.cm as cmx + szSliceDim = len(getattr(self, 'h'+normal.lower())) #: Size of the sliced dimension if ind is None: ind = int(szSliceDim/2) assert type(ind) in [int, long], 'ind must be an integer' @@ -2269,6 +2285,13 @@ class CellLookUpException(TreeException): if __name__ == '__main__': + + import matplotlib.pyplot as plt + import matplotlib + from mpl_toolkits.mplot3d import Axes3D + import matplotlib.colors as colors + import matplotlib.cm as cmx + def topo(x): return np.sin(x*(2.*np.pi))*0.3 + 0.5 diff --git a/SimPEG/Mesh/View.py b/SimPEG/Mesh/View.py index b078eb66..089d7d9a 100644 --- a/SimPEG/Mesh/View.py +++ b/SimPEG/Mesh/View.py @@ -1,8 +1,11 @@ import numpy as np -import matplotlib.pyplot as plt -import matplotlib -from mpl_toolkits.mplot3d import Axes3D -from SimPEG.Utils import mkvc, animate +from SimPEG.Utils import mkvc +try: + import matplotlib.pyplot as plt + import matplotlib + from mpl_toolkits.mplot3d import Axes3D +except ImportError, e: + print 'Trouble importing matplotlib.' class TensorView(object): @@ -479,63 +482,6 @@ class TensorView(object): ax.grid(True) if showIt: plt.show() - def slicer(mesh, var, imageType='CC', normal='z', index=0, ax=None, clim=None): - assert normal in 'xyz', 'normal must be x, y, or z' - if ax is None: ax = plt.subplot(111) - I = mesh.r(var,'CC','CC','M') - axes = [p for p in 'xyz' if p not in normal.lower()] - if normal is 'x': I = I[index,:,:] - if normal is 'y': I = I[:,index,:] - if normal is 'z': I = I[:,:,index] - if clim is None: clim = [I.min(),I.max()] - p = ax.pcolormesh(getattr(mesh,'vectorN'+axes[0]),getattr(mesh,'vectorN'+axes[1]),I.T,vmin=clim[0],vmax=clim[1]) - ax.axis('tight') - ax.set_xlabel(axes[0]) - ax.set_ylabel(axes[1]) - return p - - def videoSlicer(mesh,var,imageType='CC',normal='z',figsize=(10,8)): - assert mesh.dim > 2, 'This is for 3D meshes only.' - # First set up the figure, the axis, and the plot element we want to animate - fig = plt.figure(figsize=figsize) - ax = plt.axes() - clim = [var.min(),var.max()] - plt.colorbar(mesh.slicer(var, imageType=imageType, normal=normal, index=0, ax=ax, clim=clim)) - tlt = plt.title(normal) - - def animateFrame(i): - mesh.slicer(var, imageType=imageType, normal=normal, index=i, ax=ax, clim=clim) - tlt.set_text(normal.upper()+('-Slice: %d, %4.4f' % (i,getattr(mesh,'vectorCC'+normal)[i]))) - - return animate(fig, animateFrame, frames=mesh.vnC['xyz'.index(normal)]) - - def video(mesh, var, function, figsize=(10, 8), colorbar=True, skip=1): - """ - Call a function for a list of models to create a video. - - :: - - def function(var, ax, clim, tlt, i): - tlt.set_text('%d'%i) - return mesh.plotImage(var, imageType='CC', ax=ax, clim=clim) - - mesh.video([model1, model2, ..., modeln],function) - """ - # First set up the figure, the axis, and the plot element we want to animate - fig = plt.figure(figsize=figsize) - ax = plt.axes() - VAR = np.concatenate(var) - clim = [VAR.min(),VAR.max()] - tlt = plt.title('') - if colorbar: - plt.colorbar(function(var[0],ax,clim,tlt,0)) - - frames = np.arange(0,len(var),skip) - def animateFrame(j): - i = frames[j] - function(var[i],ax,clim,tlt,i) - - return animate(fig, animateFrame, frames=len(frames)) class CylView(object): diff --git a/SimPEG/Tests.py b/SimPEG/Tests.py index 6d414441..804ca6b8 100644 --- a/SimPEG/Tests.py +++ b/SimPEG/Tests.py @@ -1,5 +1,4 @@ import numpy as np -import matplotlib.pyplot as plt from numpy.linalg import norm from SimPEG.Utils import mkvc, sdiag, diagEst from SimPEG import Utils @@ -311,6 +310,7 @@ def checkDerivative(fctn, x0, num=7, plotIt=True, dx=None, expectedOrder=2, tole if plotIt: + import matplotlib.pyplot as plt ax = ax or plt.subplot(111) ax.loglog(h, E0, 'b') ax.loglog(h, E1, 'g--') diff --git a/SimPEG/Utils/__init__.py b/SimPEG/Utils/__init__.py index 5280ae79..250146c1 100644 --- a/SimPEG/Utils/__init__.py +++ b/SimPEG/Utils/__init__.py @@ -3,7 +3,6 @@ from codeutils import * from meshutils import exampleLrmGrid, meshTensor, closestPoints, readUBCTensorMesh, writeUBCTensorMesh, writeUBCTensorModel, readVTRFile, writeVTRFile from curvutils import volTetra, faceInfo, indexCube from interputils import interpmat -from ipythonutils import easyAnimate as animate from CounterUtils import * import ModelBuilder import SolverUtils diff --git a/SimPEG/Utils/ipythonutils.py b/SimPEG/Utils/ipythonutils.py deleted file mode 100644 index 7b66e131..00000000 --- a/SimPEG/Utils/ipythonutils.py +++ /dev/null @@ -1,28 +0,0 @@ -from tempfile import NamedTemporaryFile -import matplotlib.pyplot as plt -from matplotlib import animation - -# http://jakevdp.github.io/blog/2013/05/12/embedding-matplotlib-animations/ -# http://www.renevolution.com/how-to-install-ffmpeg-on-mac-os-x/ - -VIDEO_TAG = """""" - -def anim_to_html(anim): - if not hasattr(anim, '_encoded_video'): - with NamedTemporaryFile(suffix='.mp4') as f: - anim.save(f.name, fps=20, extra_args=['-vcodec', 'libx264', '-pix_fmt', 'yuv420p']) - video = open(f.name, "rb").read() - anim._encoded_video = video.encode("base64") - - return VIDEO_TAG.format(anim._encoded_video) - -def display_animation(anim): - plt.close(anim._fig) - return anim_to_html(anim) - -animation.Animation._repr_html_ = display_animation - -easyAnimate = animation.FuncAnimation diff --git a/SimPEG/__init__.py b/SimPEG/__init__.py index 94bf255f..cc51fd1f 100644 --- a/SimPEG/__init__.py +++ b/SimPEG/__init__.py @@ -15,7 +15,7 @@ import Directives import Inversion import Tests -__version__ = '0.1.4' +__version__ = '0.1.9' __author__ = 'Rowan Cockett' __license__ = 'MIT' __copyright__ = 'Copyright 2014 Rowan Cockett' diff --git a/docs/conf.py b/docs/conf.py index 4f3f4462..fee262de 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,9 +51,9 @@ copyright = u'2013, SimPEG Developers' # built documents. # # The short X.Y version. -version = '0.1.4' +version = '0.1.9' # The full version, including alpha/beta/rc tags. -release = '0.1.4' +release = '0.1.9' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 34c29703..bcb5b8e3 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ with open("README.rst") as f: setup( name = "SimPEG", - version = "0.1.4", + version = "0.1.9", packages = find_packages(), install_requires = ['numpy>=1.7', 'scipy>=0.13', From f482f9d87715c1d02e09ebf2ea3aba8b6ce281a9 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sun, 10 Jan 2016 15:16:46 -0800 Subject: [PATCH 36/77] Rearrange tests in order of speed for travis. --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index db79b031..258c4b7b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,12 +16,12 @@ addons: env: - TEST_DIR="tests/mesh tests/base tests/utils" - - TEST_DIR=tests/examples - - TEST_DIR=tests/em/fdem/forward - TEST_DIR=tests/em/fdem/inverse/derivs - - TEST_DIR=tests/em/fdem/inverse/adjoint - TEST_DIR=tests/em/tdem - TEST_DIR=tests/flow + - TEST_DIR=tests/examples + - TEST_DIR=tests/em/fdem/inverse/adjoint + - TEST_DIR=tests/em/fdem/forward # Setup anaconda before_install: From 8a61259cab83ab66753a70a665e499afb67e2c1f Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sun, 10 Jan 2016 15:17:13 -0800 Subject: [PATCH 37/77] Remove out of date GCE folder. --- GCEtools/PETScIO.py | 470 ------------------------------------ GCEtools/PetscExample.ipynb | 198 --------------- GCEtools/gceStartup.txt | 11 - GCEtools/petscStartup.sh | 145 ----------- GCEtools/startup.sh | 22 -- 5 files changed, 846 deletions(-) delete mode 100644 GCEtools/PETScIO.py delete mode 100644 GCEtools/PetscExample.ipynb delete mode 100644 GCEtools/gceStartup.txt delete mode 100755 GCEtools/petscStartup.sh delete mode 100644 GCEtools/startup.sh diff --git a/GCEtools/PETScIO.py b/GCEtools/PETScIO.py deleted file mode 100644 index 0977d59a..00000000 --- a/GCEtools/PETScIO.py +++ /dev/null @@ -1,470 +0,0 @@ -#!/usr/bin/python -""" -Input and output functions. -""" - - -import os as _os -import errno as _errno -import sys as _sys -import numpy as _np - -from petsc4py import PETSc as _PETSc -import fileinput as _fl - -def vecToArray(obj): - """ Converts a PETSc vector to a numpy array, available on *all* MPI nodes. - - Args: - obj (petsc4py.PETSc.Vec): input vector. - - Returns: - numpy.array : - """ - # scatter vector 'obj' to all processes - comm = obj.getComm() - scatter, obj0 = _PETSc.Scatter.toAll(obj) - scatter.scatter(obj, obj0, False, _PETSc.Scatter.Mode.FORWARD) - - return _np.asarray(obj0) - - # deallocate - comm.barrier() - scatter.destroy() - obj0.destroy() - - -def vecToArray0(obj): - """ Converts a PETSc vector to a numpy array available on MPI node 0. - - Args: - obj (petsc4py.PETSc.Vec): input vector. - - Returns: - numpy.array : - """ - # scatter vector 'obj' to process 0 - comm = obj.getComm() - rank = comm.getRank() - scatter, obj0 = _PETSc.Scatter.toZero(obj) - scatter.scatter(obj, obj0, False, _PETSc.Scatter.Mode.FORWARD) - - if rank == 0: return _np.asarray(obj0) - - # deallocate - comm.barrier() - scatter.destroy() - obj0.destroy() - - -def arrayToVec(vecArray): - """ Converts a (global) array to a PETSc vector over :attr:`petsc4py.PETSc.COMM_WORLD`. - - Args: - vecArray (array or numpy.array): input vector. - - Returns: - petsc4py.PETSc.Vec() : - """ - vec = _PETSc.Vec().create(comm=_PETSc.COMM_WORLD) - vec.setSizes(len(vecArray)) - vec.setUp() - (Istart,Iend) = vec.getOwnershipRange() - return vec.createWithArray(vecArray[Istart:Iend], - comm=_PETSc.COMM_WORLD) - vec.destroy() - - -def arrayToMat(matArray): - """ Converts a (global) 2D array to a PETSc matrix over :attr:`petsc4py.PETSc.COMM_WORLD`. - - Args: - matArray (array or numpy.array): input square array. - - :rtype: petsc4py.PETSc.Mat() - - .. important:: - Requires `SciPy `_. - - """ - try: - import scipy.sparse as sparse - except: - print '\nERROR: loading matrices from txt files requires Scipy!' - return - - matSparse =matArray - - mat = _PETSc.Mat().createAIJ(size=matSparse.shape,comm=_PETSc.COMM_WORLD) - (Istart,Iend) = mat.getOwnershipRange() - - ai = matSparse.indptr[Istart:Iend+1] - matSparse.indptr[Istart] - aj = matSparse.indices[matSparse.indptr[Istart]:matSparse.indptr[Iend]] - av = matSparse.data[matSparse.indptr[Istart]:matSparse.indptr[Iend]] - - mat.setValuesCSR(ai,aj,av) - mat.assemble() - - return mat - mat.destroy() - -def matToSparse(mat): - """ Converts a PETSc matrix to a (global) sparse matrix. - - Args: - mat (petsc4py.PETSc.Mat): input PETSc matrix. - - :rtype: scipy.sparse.csr_matrix - - .. important:: - Requires `SciPy `_. - - """ - import scipy.sparse as sparse - - data = mat.getValuesCSR() - - (Istart,Iend) = mat.getOwnershipRange() - columns = mat.getSize()[0] - sparseSubMat = sparse.csr_matrix(data[::-1],shape=(Iend-Istart,columns)) - - comm = _PETSc.COMM_WORLD - - sparseSubMat = comm.tompi4py().allgather(sparseSubMat) - - return sparse.vstack(sparseSubMat) - -def adjToH(adj,d=[0],amp=[0.]): - """ Creates a 1 particle PETSc-type Hamiltonian matrix from a PETSc adjacency matrix. - - Args: - adj (petsc4py.PETSc.Mat): input PETSc-type adjacency matrix. - - d (array of ints): an array containing *integers* indicating the nodes - where diagonal defects are to be placed (e.g. ``d=[0,1,4]``). - - amp (array of floats): an array containing *floats* indicating the diagonal defect - amplitudes corresponding to each element in ``d`` (e.g. ``amp=[0.5,-1,4.2]``). - - Returns: - : 1 particle Hamiltonian matrix - :rtype: petsc4py.PETSc.Mat() - - Warning: - * The size of ``a`` and ``d`` must be identical - - >>> amp = [0.5,-1.,4.2] - >>> len(d) == len(amp) - True - - * Elements of ``d`` can range from :math:`[0,N-1]` where the adjacency matrix is :math:`N\\times N`. - - """ - (Istart,Iend) = adj.getOwnershipRange() - diagSum = [] - for i in range(Istart,Iend): - diagSum.append(_np.sum(adj.getRow(i)[-1])) - for j,val in enumerate(d): - if i==val: diagSum[i-Istart] += amp[j] - - mat = _PETSc.Mat().create(comm=_PETSc.COMM_WORLD) - mat.setSizes(adj.getSize()) - mat.setUp() - - for i in range(Istart,Iend): - mat.setValue(i,i,diagSum[i-Istart]) - - mat.assemble() - mat.axpy(-1,adj) - - return mat - mat.destroy() - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -#---------------------- Vec I/O functions --------------------------- -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -def exportVec(vec,filename,filetype): - """ Export a PETSc vector to a file. - - Args: - vec (petsc4py.PETSc.Vec): input vector. - filename (str): path to desired output file. - filetype (str): the filetype of the exported vector. - - * ``'txt'`` - a column vector in text format. - * ``'bin'`` - a PETSc binary vector. - """ - if _os.path.isabs(filename): - outDir = _os.path.dirname(filename) - else: - outDir = './'+_os.path.dirname(filename) - - # create output directory if it doesn't exist - try: - _os.mkdir(outDir) - except OSError as exception: - if exception.errno != _errno.EEXIST: - raise - - if filetype == 'txt': - # scatter prob to process 0 - comm = vec.getComm() - rank = comm.getRank() - scatter, vec0 = _PETSc.Scatter.toZero(vec) - scatter.scatter(vec, vec0, False, _PETSc.Scatter.Mode.FORWARD) - - # use process 0 to write to text file - if rank == 0: - array0 = _np.asarray(vec0) - with open(filename,'w') as f: - for i in range(len(array0)): - f.write('{0: .12e}\n'.format(array0[i])) - - # deallocate - comm.barrier() - scatter.destroy() - vec0.destroy() - - elif filetype == 'bin': - binSave = _PETSc.Viewer().createBinary(filename, 'w') - binSave(vec) - binSave.destroy() - - vec.comm.barrier() - -def loadVec(filename,filetype): - """ Import a PETSc vector from a file. - - Args: - filename (str): path to input file. - filetype (str): the filetype. - - * ``'txt'`` - a column vector in text format. - * ``'bin'`` - a PETSc binary vector. - """ - if filetype == 'txt': - try: - vecArray = _np.loadtxt(filename,dtype=_PETSc.ScalarType) - return arrayToVec(vecArray) - except: - print "\nERROR: input state space file " + filename\ - + " does not exist or is in an incorrect format" - _sys.exit() - - elif filetype == 'bin': - binLoad = _PETSc.Viewer().createBinary(filename, 'r') - try: - return _PETSc.Vec().load(binLoad) - except: - print "\nERROR: input state space file " + filename\ - + " does not exist or is in an incorrect format" - _sys.exit() - binLoad.destroy() - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -#---------------------- Mat I/O functions --------------------------- -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -def exportMat(mat,filename,filetype,mattype=None): - """ Export a PETSc matrix to a file. - - Args: - mat (petsc4py.PETSc.Mat): input matrix. - filename (str): path to desired output file. - filetype (str): the filetype of the exported vector. - - * ``'txt'`` - a 2D matrix array in text format. - * ``'bin'`` - a PETSc binary matrix. - mattype (str): (``None``,``'adj'``) - if set to ``adj``, only - integers ``0`` and ``1`` are written. Note - that this only applied in ``txt`` mode. - """ - rank = _PETSc.Comm.Get_rank(_PETSc.COMM_WORLD) - - if _os.path.isabs(filename): - outDir = _os.path.dirname(filename) - else: - outDir = './'+_os.path.dirname(filename) - - # create output directory if it doesn't exist - try: - _os.mkdir(outDir) - except OSError as exception: - if exception.errno != _errno.EEXIST: - raise - - if filetype == 'txt': - txtSave = _PETSc.Viewer().createASCII(filename, 'w', - format=_PETSc.Viewer.Format.ASCII_DENSE, comm=_PETSc.COMM_WORLD) - txtSave(mat) - txtSave.destroy() - - if rank == 0: - for line in _fl.FileInput(filename,inplace=1): - if line[2] != 't': - if mattype == 'adj': - line = line.replace(" i","j") - line = line.replace(" -","-") - line = line.replace("+-","-") - line = line.replace("0000e+01+0.00000e+00j","") - line = line.replace(".00000e+00+0.00000e+00j","") - line = line.replace(".","") - line = line.replace(" -","\t-") - line = line.replace(" ","\t") - line = line.replace(" ","") - line = line.replace("\t"," ") - print line, - else: - line = line.replace(" i","j") - line = line.replace(" -","-") - line = line.replace("+-","-") - print line, - - elif filetype == 'bin': - binSave = _PETSc.Viewer().createBinary(filename, 'w', comm=_PETSc.COMM_WORLD) - binSave(mat) - binSave.destroy() - - mat.comm.barrier() - -def loadMat(filename,filetype,delimiter=None): - """ Import a PETSc matrix from a file. - - Args: - filename (str): path to input file. - filetype (str): the filetype. - - * ``'txt'`` - a 2D matrix array in text format. - * ``'bin'`` - a PETSc matrix vector. - - delimiter (str): this is passed to `numpy.genfromtxt\ - `_ - in the case of strange delimiters in an imported ``txt`` file. - """ - if filetype == 'txt': - try: - try: - if delimiter is None: - matArray = _np.genfromtxt(filename,dtype=_PETSc.ScalarType) - else: - matArray = _np.genfromtxt(filename,dtype=_PETSc.ScalarType,delimiter=delimiter) - except: - filefix = [] - for line in _fl.FileInput(filename,inplace=0): - if line[2] != 't': - line = line.replace(" i","j") - line = line.replace(" -","-") - line = line.replace("+-","-") - filefix.append(line) - - matArray = _np.genfromtxt(filefix,dtype=_PETSc.ScalarType) - - return arrayToMat(matArray) - except: - print "\nERROR: input state space file " + filename\ - + " does not exist or is in an incorrect format" - _sys.exit() - - elif filetype == 'bin': - binLoad = _PETSc.Viewer().createBinary(filename, 'r') - try: - return _PETSc.Mat().load(binLoad) - except: - print "\nERROR: input state space file " + filename\ - + " does not exist or is in an incorrect format" - _sys.exit() - binLoad.destroy() - -def exportVecToMat(vec,filename,filetype): - """ Export a :math:`N^2` element PETSc vector as a :math:`N\\times N` matrix. - - This is useful when wanting to view the full statespace of a 2 particle - quantum walk. - - Args: - vec (petsc4py.PETSc.Vec): input :math:`N^2` element vector. - filename (str): path to desired output file. - filetype (str): the filetype of the exported vector. - - * ``'txt'`` - an :math:`N\\times N` 2D matrix array in text format. - * ``'bin'`` - an :math:`N\\times N` PETSc binary matrix. - """ - rank = _PETSc.Comm.Get_rank(_PETSc.COMM_WORLD) - - if _os.path.isabs(filename): - outDir = _os.path.dirname(filename) - else: - outDir = './'+_os.path.dirname(filename) - - # create output directory if it doesn't exist - try: - _os.mkdir(outDir) - except OSError as exception: - if exception.errno != _errno.EEXIST: - raise - - vecArray = vecToArray(vec) - matArray = vecArray.reshape([_np.sqrt(vecArray.size),_np.sqrt(vecArray.size)]) - - if filetype == 'txt': - #if rank == 0: _np.savetxt(filename,matArray) - txtSave = _PETSc.Viewer().createASCII(filename, 'w', - format=_PETSc.Viewer.Format.ASCII_DENSE, comm=_PETSc.COMM_WORLD) - txtSave(arrayToMat(matArray)) - txtSave.destroy() - - if rank == 0: - for line in _fl.FileInput(filename,inplace=1): - if line[2] != 't': - line = line.replace(" i","j") - line = line.replace(" -","-") - line = line.replace("+-","-") - print line, - - elif filetype == 'bin': - binSave = _PETSc.Viewer().createBinary(filename, 'w', comm=_PETSc.COMM_WORLD) - binSave(arrayToMat(matArray)) - binSave.destroy() - vec.comm.barrier() - - -def loadMatToVec(filename,filetype): - """ Load a :math:`N\\times N` matrix as a :math:`N^2` element PETSc vector. - - This is useful when wanting to import the full statespace of a 2 particle - quantum walk to use for propagation. - - Args: - filename (str): path to the input file. - filetype (str): the filetype - - * ``'txt'`` - an :math:`N\\times N` 2D matrix array in text format. - * ``'bin'`` - **Not yet implemented! Please use a txt \ - format for this type of import**. - """ - if filetype == 'txt': - try: - try: - matArray = _np.loadtxt(filename,dtype=_PETSc.ScalarType) - except: - filefix = [] - for line in _fl.FileInput(filename,inplace=0): - if line[2] != 't': - line = line.replace(" i","j") - line = line.replace(" -","-") - line = line.replace("+-","-") - filefix.append(line) - - matArray = _np.loadtxt(filefix,dtype=_PETSc.ScalarType) - - vecArray = matArray.reshape(matArray.shape[0]**2) - return arrayToVec(vecArray) - except: - print "\nERROR: input state space file " + filename\ - + " does not exist or is in an incorrect format" - _sys.exit() - - elif filetype == 'bin': - print '\nERROR: only works for txt storage!' - _sys.exit() diff --git a/GCEtools/PetscExample.ipynb b/GCEtools/PetscExample.ipynb deleted file mode 100644 index 40e960ea..00000000 --- a/GCEtools/PetscExample.ipynb +++ /dev/null @@ -1,198 +0,0 @@ -{ - "metadata": { - "name": "" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from SimPEG import *\n", - "%pylab inline" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Populating the interactive namespace from numpy and matplotlib\n" - ] - } - ], - "prompt_number": 4 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "M = mesh.TensorMesh([20,30]) \n", - "A = M.faceDiv*M.faceDiv.T + sp.identity(M.nC)\n", - "b = np.random.rand(M.nC)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 35 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "phi = Solver(A).solve(b)\n", - "colorbar(M.plotImage(phi))" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 37, - "text": [ - "" - ] - }, - { - "metadata": {}, - "output_type": "display_data", - "png": "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\n", - "text": [ - "" - ] - } - ], - "prompt_number": 37 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print np.linalg.norm(A*phi-b)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "1.5220841846e-13\n" - ] - } - ], - "prompt_number": 38 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import petsc4py\n", - "import sys\n", - "petsc4py.init(sys.argv)\n", - "from petsc4py import PETSc" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 39 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import PETScIO as IO\n", - "Apetsc = PETSc.Mat().createAIJ(size=A.shape,csr=(A.indptr, A.indices, A.data))\n", - "bpetsc = IO.arrayToVec(b)\n", - "xpetsc = IO.arrayToVec(0*b)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 40 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "ksp = PETSc.KSP().create()\n", - "pc = PETSc.PC().create()\n", - "\n", - "ksp.setOperators(Apetsc)\n", - "\n", - "ksp.setType(ksp.Type.CG)\n", - "pc = ksp.getPC()\n", - "pc.setType(pc.Type.HYPRE)\n", - "ksp.view()\n", - "\n", - "ksp.solve(bpetsc, xpetsc)\n", - "print ksp.its" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "3\n" - ] - } - ], - "prompt_number": 76 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "X = IO.vecToArray(xpetsc)\n", - "print np.linalg.norm(A*X-b)\n", - "M.plotImage(X)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "0.000199616893249\n" - ] - }, - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 77, - "text": [ - "" - ] - }, - { - "metadata": {}, - "output_type": "display_data", - "png": "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\nrkJggg==\n", - "text": [ - "" - ] - } - ], - "prompt_number": 77 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [], - "language": "python", - "metadata": {}, - "outputs": [] - } - ], - "metadata": {} - } - ] -} \ No newline at end of file diff --git a/GCEtools/gceStartup.txt b/GCEtools/gceStartup.txt deleted file mode 100644 index 76bbcb39..00000000 --- a/GCEtools/gceStartup.txt +++ /dev/null @@ -1,11 +0,0 @@ -# Check project status -gcutil getproject --project= --cache_flag_values - -# Start an instance -gcutil addinstance - -# Log in -gcutil ssh - -# Shut down -gcutil deleteinstance \ No newline at end of file diff --git a/GCEtools/petscStartup.sh b/GCEtools/petscStartup.sh deleted file mode 100755 index 147ae6ea..00000000 --- a/GCEtools/petscStartup.sh +++ /dev/null @@ -1,145 +0,0 @@ -#! /bin/bash - -locale-gen en_US en_US.UTF-8 hu_HU hu_HU.UTF-8 > output.t -dpkg-reconfigure locales >> output.t - -sudo apt-get update >> output.t -echo " " -echo " " -echo " ============================================" -echo " | Installing packages form package manager |" -echo " ============================================" -echo " " -echo " " - -sudo apt-get -y install aptitude >> output.t - -packages=(gcc gfortran git libopenmpi-dev python-pip python-dev git flex bison cmake vim cython ipython python-scipy python-numpy python-nose python-pip python-matplotlib python-vtk python-h5py libmumps-ptscotch-4.10.0 libmumps-ptscotch-dev libblas-dev liblapack-dev ) - - -for item in ${packages[*]} -do - printf " %-30s\n" $item - -done - -for item in ${packages[*]} -do - tput cuu1 -done - -for item in ${packages[*]} -do - sudo aptitude -y install $item >> output.t - printf " %-30s %-4s\n" $item done -done - - -echo " " -echo " " -echo " =====================================" -echo " | Installing extra Python libraries |" -echo " =====================================" -echo " " -echo " " - - -pipPackages=(mpi4py pymumps) - -for item in ${pipPackages[*]} -do - printf " %-30s\n" $item -done - -for item in ${pipPackages[*]} -do - tput cuu1 -done - -for item in ${pipPackages[*]} -do - sudo pip install $item >> output.t - printf " %-30s %-4s\n" $item done -done - -Upgrade=(scipy numpy ipython) - -for item in ${Upgrade[*]} -do - printf " %-8s%-7s\n" $item upgrade - -done - -for item in ${Upgrade[*]} -do - tput cuu1 -done - -for item in ${Upgrade[*]} -do - sudo pip install $item --upgrade >> output.t - printf " %-8s%-7s %-4s\n" $item upgrade done -done - - - -echo " " -echo " " -echo " =====================" -echo " | Installing SimPEG |" -echo " =====================" -echo " " -echo " " -cd ~ - - -git clone https://github.com/simpeg/simpeg.git >> output.t -cd simpeg/SimPEG/ -python setup.py >> output.t -cd ~ - -mkdir petsc -cd petsc - -echo " " -echo " " -echo " ====================" -echo " | Installing PETSc |" -echo " ====================" -echo " " -echo " " -wget http://ftp.mcs.anl.gov/pub/petsc/release-snapshots/petsc-3.4.3.tar.gz - -tar -zxf petsc-3.4.3.tar.gz - -cd petsc-3.4.3 - -./configure --with-debugging=no --dowload-mpich=yes --download-blacs=yes --download-f-blas-lapack=yes --download-scalapack=yes --download-mumps=yes --download-ml=yes --download-spooles=yes --download-hypre=yes --dowload-trilinos=yes --download-metis=yes --download-parmetis=yes --download-umfpack=yes --download-ptscotch=yes --download-superlu=yes --download-superlu_dist=yes --download-essl=yes --download-eucild=yes --download-spai=yes --download-mpi4py=yes --download-petsc4py=yes --download-scientificpython=yes - - -echo "export PETSC_DIR=/home/${USER}/petsc/petsc-3.4.3" >> ~/.bashrc -echo "export PETSC_ARCH=arch-linux2-c-opt" >> ~/.bashrc -export PETSC_DIR=/home/${USER}/petsc/petsc-3.4.3 -export PETSC_ARCH=arch-linux2-c-opt -. ~/.bashrc - -make PETSC_DIR=/home/${USER}/petsc/petsc-3.4.3 PETSC_ARCH=arch-linux2-c-opt all -make PETSC_DIR=/home/${USER}/petsc/petsc-3.4.3 PETSC_ARCH=arch-linux2-c-opt test - -cd ~/petsc -echo " " -echo " " -echo " =======================" -echo " | Installing PETSc4PY |" -echo " =======================" -echo " " -echo " " -git clone https://bitbucket.org/petsc/petsc4py.git -cd petsc4py/ -python setup.py build >> output.t -python setup.py install --prefix=~/petsc >> output.t - -echo "export PYTHONPATH=~/petsc/lib/python2.7/site-packages:/home/$USER/simpeg:${PYTHONPATH}" >> ~/.bashrc - -cd ~ -source ~/.bashrc diff --git a/GCEtools/startup.sh b/GCEtools/startup.sh deleted file mode 100644 index 91797960..00000000 --- a/GCEtools/startup.sh +++ /dev/null @@ -1,22 +0,0 @@ -#! /bin/bash -sudo aptitude -y update -sudo aptitude -y upgrade -sudo aptitude -y install gcc gfortran git libopenmpi-dev python-pip python-dev -sudo aptitude -y install ipython python-scipy python-numpy python-nose python-pip python-matplotlib -sudo aptitude -y install libmumps-ptscotch-4.10.0 libmumps-ptscotch-dev -sudo aptitude -y install libblas-dev liblapack-dev - -sudo pip install mpi4py -sudo pip install pymumps - -sudo pip install scipy --upgrade -sudo pip install numpy --upgrade -sudo pip install ipython --upgrade - -git clone https://github.com/simpeg/simpeg.git -cd simpeg/SimPEG/ -python setup.py -cd ~ - -echo export PYTHONPATH=/home/$USER/simpeg/ >> .bashrc -source .bashrc From 2c5b19b7a09210483c8706e79f2d6cd7780d7124 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sun, 10 Jan 2016 17:21:01 -0800 Subject: [PATCH 38/77] Updates to the documentation. --- AUTHORS.rst | 10 +- CITATION.rst | 5 +- LICENSE | 2 +- PROJECTS.rst | 36 ----- SimPEG/EM/Analytics/FDEM.py | 3 +- SimPEG/Regularization.py | 83 +---------- docs/InversionWorkflow-PreSimPEG.png | Bin 0 -> 50417 bytes docs/api_FiniteVolume.rst | 19 +++ docs/api_ForwardProblem.rst | 7 +- docs/{api_Inverse.rst => api_Inversion.rst} | 23 +-- docs/api_InversionComponents.rst | 11 ++ docs/api_Mesh.rst | 3 +- docs/api_MeshCode.rst | 20 +-- docs/api_Optimization.rst | 9 ++ docs/api_Regularization.rst | 100 +++++++++++++ docs/api_Utilities.rst | 10 ++ docs/api_Utils.rst | 9 +- docs/api_bigPicture.rst | 64 ++++++++- docs/api_installing.rst | 4 +- docs/api_license.rst | 17 --- docs/em/api_Utils.rst | 7 +- docs/em/index.rst | 5 +- docs/index.rst | 151 +++++++++----------- 23 files changed, 316 insertions(+), 282 deletions(-) delete mode 100644 PROJECTS.rst create mode 100644 docs/InversionWorkflow-PreSimPEG.png create mode 100644 docs/api_FiniteVolume.rst rename docs/{api_Inverse.rst => api_Inversion.rst} (56%) create mode 100644 docs/api_InversionComponents.rst create mode 100644 docs/api_Optimization.rst create mode 100644 docs/api_Regularization.rst create mode 100644 docs/api_Utilities.rst delete mode 100644 docs/api_license.rst diff --git a/AUTHORS.rst b/AUTHORS.rst index 2a7d1903..f5e5ff7b 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,9 +1,13 @@ -- Luz Angelica Caudillo-Mata, (`@lacmajedrez `_) - Rowan Cockett, (`@rowanc1 `_) -- Eldad Haber, (`@ehaber99 `_) - Lindsey Heagy, (`@lheagy `_) - Seogi Kang, (`@sgkang `_) -- Dave Marchant, (`@dwfmarchant `_) +- Brendan Smithyman, (`@bsmithyman `_) - Gudni Rosenkjaer, (`@grosenkj `_) +- Dom Fournier, (`@fourndo `_) +- Dave Marchant, (`@dwfmarchant `_) - Lars Ruthotto, (`@lruthotto `_) - Mike Wathen, (`@mrwathen `_) +- Luz Angelica Caudillo-Mata, (`@lacmajedrez `_) +- Eldad Haber, (`@ehaber99 `_) +- Doug Oldenburg, (`@dougoldenburg `_) +- Adam Pidlisecky, (`@aPid1 `_) diff --git a/CITATION.rst b/CITATION.rst index a3a13d7e..b0b2a76f 100644 --- a/CITATION.rst +++ b/CITATION.rst @@ -1,14 +1,13 @@ Citing SimPEG -============= +------------- -There is a paper about SimPEG! +There is a `paper about SimPEG `_, if you use this code, please help our scientific visibility by citing our work! Cockett, R., Kang, S., Heagy, L. J., Pidlisecky, A., & Oldenburg, D. W. (2015). SimPEG: An open source framework for simulation and gradient based parameter estimation in geophysical applications. Computers & Geosciences. BibTex: -------- .. code:: diff --git a/LICENSE b/LICENSE index 43ee31ab..c046b35d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2015 SimPEG Developers +Copyright (c) 2013-2016 SimPEG Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/PROJECTS.rst b/PROJECTS.rst deleted file mode 100644 index ce411d9d..00000000 --- a/PROJECTS.rst +++ /dev/null @@ -1,36 +0,0 @@ -- Electromagnetics (`simpegEM `_) - .. image:: https://travis-ci.org/simpeg/simpegem.svg?branch=master - :target: https://travis-ci.org/simpeg/simpegem - :alt: Master Branch - .. image:: https://coveralls.io/repos/simpeg/simpegem/badge.png?branch=master - :target: https://coveralls.io/r/simpeg/simpegem?branch=master -- Potential Fields (`simpegPF `_) - .. image:: https://travis-ci.org/simpeg/simpegpf.svg?branch=master - :target: https://travis-ci.org/simpeg/simpegpf - :alt: Master Branch - .. image:: https://coveralls.io/repos/simpeg/simpegpf/badge.png?branch=master - :target: https://coveralls.io/r/simpeg/simpegpf?branch=master -- Ground Water Flow (`simpegFLOW `_) - .. image:: https://travis-ci.org/simpeg/simpegflow.svg?branch=master - :target: https://travis-ci.org/simpeg/simpegflow - :alt: Master Branch - .. image:: https://coveralls.io/repos/simpeg/simpegflow/badge.png?branch=master - :target: https://coveralls.io/r/simpeg/simpegflow?branch=master -- Direct Current Resistivity (`simpegDC `_) - .. image:: https://travis-ci.org/simpeg/simpegdc.svg?branch=master - :target: https://travis-ci.org/simpeg/simpegdc - :alt: Master Branch - .. image:: https://coveralls.io/repos/simpeg/simpegdc/badge.png?branch=master - :target: https://coveralls.io/r/simpeg/simpegdc?branch=master -- Electromagnetics 1D (`simpegEM1D `_) - .. image:: https://travis-ci.org/simpeg/simpegEM1D.svg?branch=master - :target: https://travis-ci.org/simpeg/simpegEM1D - :alt: Master Branch - .. image:: https://coveralls.io/repos/simpeg/simpegEM1D/badge.png?branch=master - :target: https://coveralls.io/r/simpeg/simpegEM1D?branch=master -- Magnetotellurics (`simpegMT `_) - .. image:: https://travis-ci.org/simpeg/simpegmt.svg?branch=master - :target: https://travis-ci.org/simpeg/simpegmt - :alt: Master Branch - .. image:: https://coveralls.io/repos/simpeg/simpegmt/badge.png?branch=master - :target: https://coveralls.io/r/simpeg/simpegmt?branch=master diff --git a/SimPEG/EM/Analytics/FDEM.py b/SimPEG/EM/Analytics/FDEM.py index 9e776fdf..1f52459c 100644 --- a/SimPEG/EM/Analytics/FDEM.py +++ b/SimPEG/EM/Analytics/FDEM.py @@ -58,8 +58,9 @@ def MagneticDipoleWholeSpace(XYZ, srcLoc, sig, f, moment=1., orientation='X', mu from SimPEG import EM import matplotlib.pyplot as plt + from scipy.constants import mu_0 freqs = np.logspace(-2,5,100) - Bx, By, Bz = EM.Analytics.FDEM.AnalyticMagDipoleWholeSpace([0,100,0], [0,0,0], 1e-2, freqs, m=1, orientation='Z') + Bx, By, Bz = EM.Analytics.FDEM.MagneticDipoleWholeSpace([0,100,0], [0,0,0], 1e-2, freqs, moment=1, orientation='Z') plt.loglog(freqs, np.abs(Bz.real)/mu_0, 'b') plt.loglog(freqs, np.abs(Bz.imag)/mu_0, 'r') plt.legend(('real','imag')) diff --git a/SimPEG/Regularization.py b/SimPEG/Regularization.py index e3506290..97b5e7a2 100644 --- a/SimPEG/Regularization.py +++ b/SimPEG/Regularization.py @@ -115,86 +115,7 @@ class BaseRegularization(object): class Tikhonov(BaseRegularization): - """**Tikhonov Regularization** - - Here we will define regularization of a model, m, in general however, this should be thought of as (m-m_ref) but otherwise it is exactly the same: - - .. math:: - - R(m) = \int_\Omega \\frac{\\alpha_x}{2}\left(\\frac{\partial m}{\partial x}\\right)^2 + \\frac{\\alpha_y}{2}\left(\\frac{\partial m}{\partial y}\\right)^2 \partial v - - Our discrete gradient operator works on cell centers and gives the derivative on the cell faces, which is not where we want to be evaluating this integral. We need to average the values back to the cell-centers before we integrate. To avoid null spaces, we square first and then average. In 2D with ij notation it looks like this: - - .. math:: - - R(m) \\approx \sum_{ij} \left[\\frac{\\alpha_x}{2}\left[\left(\\frac{m_{i+1,j} - m_{i,j}}{h}\\right)^2 + \left(\\frac{m_{i,j} - m_{i-1,j}}{h}\\right)^2\\right] - + \\frac{\\alpha_y}{2}\left[\left(\\frac{m_{i,j+1} - m_{i,j}}{h}\\right)^2 + \left(\\frac{m_{i,j} - m_{i,j-1}}{h}\\right)^2\\right] - \\right]h^2 - - If we let D_1 be the derivative matrix in the x direction - - .. math:: - - \mathbf{D}_1 = \mathbf{I}_2\otimes\mathbf{d}_1 - - .. math:: - - \mathbf{D}_2 = \mathbf{d}_2\otimes\mathbf{I}_1 - - Where d_1 is the one dimensional derivative: - - .. math:: - - \mathbf{d}_1 = \\frac{1}{h} \left[ \\begin{array}{cccc} - -1 & 1 & & \\\\ - & \ddots & \ddots&\\\\ - & & -1 & 1\end{array} \\right] - - .. math:: - - R(m) \\approx \mathbf{v}^\\top \left[\\frac{\\alpha_x}{2}\mathbf{A}_1 (\mathbf{D}_1 m) \odot (\mathbf{D}_1 m) + \\frac{\\alpha_y}{2}\mathbf{A}_2 (\mathbf{D}_2 m) \odot (\mathbf{D}_2 m) \\right] - - Recall that this is really a just point wise multiplication, or a diagonal matrix times a vector. When we multiply by something in a diagonal we can interchange and it gives the same results (i.e. it is point wise) - - .. math:: - - \mathbf{a\odot b} = \\text{diag}(\mathbf{a})\mathbf{b} = \\text{diag}(\mathbf{b})\mathbf{a} = \mathbf{b\odot a} - - and the transpose also is true (but the sizes have to make sense...): - - .. math:: - - \mathbf{a}^\\top\\text{diag}(\mathbf{b}) = \mathbf{b}^\\top\\text{diag}(\mathbf{a}) - - So R(m) can simplify to: - - .. math:: - - R(m) \\approx \mathbf{m}^\\top \left[\\frac{\\alpha_x}{2}\mathbf{D}_1^\\top \\text{diag}(\mathbf{A}_1^\\top\mathbf{v}) \mathbf{D}_1 + \\frac{\\alpha_y}{2}\mathbf{D}_2^\\top \\text{diag}(\mathbf{A}_2^\\top \mathbf{v}) \mathbf{D}_2 \\right] \mathbf{m} - - We will define W_x as: - - .. math:: - - \mathbf{W}_x = \sqrt{\\alpha_x}\\text{diag}\left(\sqrt{\mathbf{A}_1^\\top\mathbf{v}}\\right) \mathbf{D}_1 - - - And then W as a tall matrix of all of the different regularization terms: - - .. math:: - - \mathbf{W} = \left[ \\begin{array}{c} - \mathbf{W}_s\\\\ - \mathbf{W}_x\\\\ - \mathbf{W}_y\end{array} \\right] - - Then we can write - - .. math:: - - R(m) \\approx \\frac{1}{2}\mathbf{m^\\top W^\\top W m} - - + """ """ smoothModel = True #: SMOOTH and SMOOTH_MOD_DIF options alpha_s = Utils.dependentProperty('_alpha_s', 1e-6, ['_W', '_Ws'], "Smallness weight") @@ -311,7 +232,7 @@ class Tikhonov(BaseRegularization): if self.smoothModel == True: mD1 = self.mapping.deriv(m) mD2 = self.mapping.deriv(m - self.mref) - r1 = self.Wsmooth * ( self.mapping * (m)) + r1 = self.Wsmooth * ( self.mapping * (m)) r2 = self.Ws * ( self.mapping * (m - self.mref) ) out1 = mD1.T * ( self.Wsmooth.T * r1 ) out2 = mD2.T * ( self.Ws.T * r2 ) diff --git a/docs/InversionWorkflow-PreSimPEG.png b/docs/InversionWorkflow-PreSimPEG.png new file mode 100644 index 0000000000000000000000000000000000000000..2396da691b2bf1c130e2451e50da5c4f6ea17630 GIT binary patch literal 50417 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 literal 0 HcmV?d00001 diff --git a/docs/api_FiniteVolume.rst b/docs/api_FiniteVolume.rst new file mode 100644 index 00000000..40e9b879 --- /dev/null +++ b/docs/api_FiniteVolume.rst @@ -0,0 +1,19 @@ +.. _api_FiniteVolume: + +Finite Volume +************* + +Any numerical implementation requires the discretization of continuous functions into discrete approximations. These approximations are typically organized in a mesh, which defines boundaries, locations, and connectivity. Of specific interest to geophysical simulations, we require that averaging, interpolation and differential operators be defined for any mesh. In SimPEG, we have implemented a staggered mimetic finite volume approach (`Hyman and Shashkov, 1999 `_). This approach requires the definitions of variables at either cell-centers, nodes, faces, or edges as seen in the figure below. + +.. image:: images/finitevolrealestate.png + :width: 400 px + :alt: FiniteVolume + :align: center + + +.. toctree:: + :maxdepth: 2 + + api_Mesh + api_DiffOps + api_InnerProducts diff --git a/docs/api_ForwardProblem.rst b/docs/api_ForwardProblem.rst index b8f2fd90..0bf9596f 100644 --- a/docs/api_ForwardProblem.rst +++ b/docs/api_ForwardProblem.rst @@ -61,11 +61,6 @@ If the forward problem is invertible, then we can rearrange for \\(\\frac{\\part This can often be computed given a vector (i.e. \\(J(v)\\)) rather than stored, as \\(J\\) is a large dense matrix. -.. math:: - - u(m) - - The API ======= @@ -78,7 +73,7 @@ Problem Survey ------ - .. automodule:: SimPEG.Survey :members: + :undoc-members: diff --git a/docs/api_Inverse.rst b/docs/api_Inversion.rst similarity index 56% rename from docs/api_Inverse.rst rename to docs/api_Inversion.rst index bd4d8704..5cac5f0c 100644 --- a/docs/api_Inverse.rst +++ b/docs/api_Inversion.rst @@ -1,21 +1,19 @@ -.. _api_Inverse: -Regularization -************** +InvProblem +********** -.. automodule:: SimPEG.Regularization +.. automodule:: SimPEG.InvProblem :show-inheritance: :members: :undoc-members: -Optimize -******** +Inversion +********* -.. automodule:: SimPEG.Optimization +.. automodule:: SimPEG.Inversion :show-inheritance: - :private-members: :members: :undoc-members: @@ -27,12 +25,3 @@ Directives :members: :undoc-members: -Inversion -********* - -.. automodule:: SimPEG.Inversion - :show-inheritance: - :members: - :undoc-members: - - diff --git a/docs/api_InversionComponents.rst b/docs/api_InversionComponents.rst new file mode 100644 index 00000000..eeaf1bbf --- /dev/null +++ b/docs/api_InversionComponents.rst @@ -0,0 +1,11 @@ +Inversion Components +******************** + +.. toctree:: + :maxdepth: 3 + + api_DataMisfit + api_Regularization + api_Optimization + api_Inversion + diff --git a/docs/api_Mesh.rst b/docs/api_Mesh.rst index a7f7abae..ed216b13 100644 --- a/docs/api_Mesh.rst +++ b/docs/api_Mesh.rst @@ -24,8 +24,7 @@ the implementations. .. plot:: from SimPEG import Examples - Examples.Mesh_ThreeMeshes.run() - + Examples.Mesh_Basic_Types.run() Variable Locations and Terminology diff --git a/docs/api_MeshCode.rst b/docs/api_MeshCode.rst index a3813c13..2d7cab9f 100644 --- a/docs/api_MeshCode.rst +++ b/docs/api_MeshCode.rst @@ -9,6 +9,15 @@ Tensor Mesh :undoc-members: +Cylindrical Mesh +================ + +.. automodule:: SimPEG.Mesh.CylMesh + :show-inheritance: + :members: + :undoc-members: + + Tree Mesh ========= @@ -21,16 +30,7 @@ Tree Mesh Curvilinear Mesh ================ -.. automodule:: SimPEG.Mesh.Curvilinear - :show-inheritance: - :members: - :undoc-members: - - -Cylindrical Mesh -================ - -.. automodule:: SimPEG.Mesh.CylMesh +.. automodule:: SimPEG.Mesh.CurvilinearMesh :show-inheritance: :members: :undoc-members: diff --git a/docs/api_Optimization.rst b/docs/api_Optimization.rst new file mode 100644 index 00000000..85ec523f --- /dev/null +++ b/docs/api_Optimization.rst @@ -0,0 +1,9 @@ + +Optimize +******** + +.. automodule:: SimPEG.Optimization + :show-inheritance: + :private-members: + :members: + :undoc-members: diff --git a/docs/api_Regularization.rst b/docs/api_Regularization.rst new file mode 100644 index 00000000..4ea1a554 --- /dev/null +++ b/docs/api_Regularization.rst @@ -0,0 +1,100 @@ + +Regularization +************** + +If there is one model that has a misfit that equals the desired tolerance, then there are infinitely many other models which can fit to the same degree. The challenge is to find that model which has the desired characteristics and is compatible with a priori information. A single model can be selected from an infinite ensemble by measuring the length, or norm, of each model. Then a smallest, or sometimes largest, member can be isolated. Our goal is to design a norm that embodies our prior knowledge and, when minimized, yields a realistic candidate for the solution of our problem. The norm can penalize variation from a reference model, spatial derivatives of the model, or some combination of these. + +Tikhonov Regularization +======================= + +Here we will define regularization of a model, m, in general however, this should be thought of as (m-m_ref) but otherwise it is exactly the same: + +.. math:: + + R(m) = \int_\Omega \frac{\alpha_x}{2}\left(\frac{\partial m}{\partial x}\right)^2 + \frac{\alpha_y}{2}\left(\frac{\partial m}{\partial y}\right)^2 \partial v + +Our discrete gradient operator works on cell centers and gives the derivative on the cell faces, which is not where we want to be evaluating this integral. We need to average the values back to the cell-centers before we integrate. To avoid null spaces, we square first and then average. In 2D with ij notation it looks like this: + +.. math:: + + R(m) \approx \sum_{ij} \left[\frac{\alpha_x}{2}\left[\left(\frac{m_{i+1,j} - m_{i,j}}{h}\right)^2 + \left(\frac{m_{i,j} - m_{i-1,j}}{h}\right)^2\right] \\ + + \frac{\alpha_y}{2}\left[\left(\frac{m_{i,j+1} - m_{i,j}}{h}\right)^2 + \left(\frac{m_{i,j} - m_{i,j-1}}{h}\right)^2\right] + \right]h^2 + +If we let D_1 be the derivative matrix in the x direction + +.. math:: + + \mathbf{D}_1 = \mathbf{I}_2\otimes\mathbf{d}_1 + +.. math:: + + \mathbf{D}_2 = \mathbf{d}_2\otimes\mathbf{I}_1 + +Where d_1 is the one dimensional derivative: + +.. math:: + + \mathbf{d}_1 = \frac{1}{h} \left[ \begin{array}{cccc} + -1 & 1 & & \\ + & \ddots & \ddots&\\ + & & -1 & 1\end{array} \right] + +.. math:: + + R(m) \approx \mathbf{v}^\top \left[\frac{\alpha_x}{2}\mathbf{A}_1 (\mathbf{D}_1 m) \odot (\mathbf{D}_1 m) + \frac{\alpha_y}{2}\mathbf{A}_2 (\mathbf{D}_2 m) \odot (\mathbf{D}_2 m) \right] + +Recall that this is really a just point wise multiplication, or a diagonal matrix times a vector. When we multiply by something in a diagonal we can interchange and it gives the same results (i.e. it is point wise) + +.. math:: + + \mathbf{a\odot b} = \text{diag}(\mathbf{a})\mathbf{b} = \text{diag}(\mathbf{b})\mathbf{a} = \mathbf{b\odot a} + +and the transpose also is true (but the sizes have to make sense...): + +.. math:: + + \mathbf{a}^\top\text{diag}(\mathbf{b}) = \mathbf{b}^\top\text{diag}(\mathbf{a}) + +So R(m) can simplify to: + +.. math:: + + R(m) \approx \mathbf{m}^\top \left[\frac{\alpha_x}{2}\mathbf{D}_1^\top \text{diag}(\mathbf{A}_1^\top\mathbf{v}) \mathbf{D}_1 + \frac{\alpha_y}{2}\mathbf{D}_2^\top \text{diag}(\mathbf{A}_2^\top \mathbf{v}) \mathbf{D}_2 \right] \mathbf{m} + +We will define W_x as: + +.. math:: + + \mathbf{W}_x = \sqrt{\alpha_x}\text{diag}\left(\sqrt{\mathbf{A}_1^\top\mathbf{v}}\right) \mathbf{D}_1 + + +And then W as a tall matrix of all of the different regularization terms: + +.. math:: + + \mathbf{W} = \left[ \begin{array}{c} + \mathbf{W}_s\\ + \mathbf{W}_x\\ + \mathbf{W}_y\end{array} \right] + +Then we can write + +.. math:: + + R(m) \approx \frac{1}{2}\mathbf{m^\top W^\top W m} + +The API +------- + +.. autoclass:: SimPEG.Regularization.BaseRegularization + :members: + :undoc-members: + + +.. autoclass:: SimPEG.Regularization.Tikhonov + :show-inheritance: + :members: + + + diff --git a/docs/api_Utilities.rst b/docs/api_Utilities.rst new file mode 100644 index 00000000..c3a08b30 --- /dev/null +++ b/docs/api_Utilities.rst @@ -0,0 +1,10 @@ +Utilities +********* + +.. toctree:: + :maxdepth: 2 + + api_Solver + api_Maps + api_Utils + api_Tests diff --git a/docs/api_Utils.rst b/docs/api_Utils.rst index 042aef67..1bf86516 100644 --- a/docs/api_Utils.rst +++ b/docs/api_Utils.rst @@ -1,8 +1,5 @@ -.. _api_Utils: - - -Utilities -********* +Utils +***** .. automodule:: SimPEG.Utils :members: @@ -52,7 +49,7 @@ Interpolation Utilities :undoc-members: Counter Utilities -======================= +================= :: class MyClass(object): diff --git a/docs/api_bigPicture.rst b/docs/api_bigPicture.rst index ef7c1174..90266650 100644 --- a/docs/api_bigPicture.rst +++ b/docs/api_bigPicture.rst @@ -1,17 +1,69 @@ -.. _api_license: - Why SimPEG? -*********** +=========== +Our essential functions as researchers are the pursuit and dissemination of knowledge through research and education. As scientists we +seek to find models that reproduce the observations that we make in the world. In geophysics, we use inverse theory to mathematically +create models of the earth from measured data. It is a difficult problem with many moving pieces: physics, discretization, simulation, +regularization, optimization, computer science, linear algebra, geology. Exploring each of these disciplines can take a career, if you +are so inclined, but as geophysicists we care about the combination: how to pull these disciplines together to answer our questions. +This is the first problem we hope to help solve: to create a toolbox for the geophysicist that allows you to work at a high level and +keep your geophysical question in focus. However, a toolbox is not enough. The research questions that we are interested in surround +the integration of information to make better decisions. + +We believe that the feedback loops in the geosciences could use some serious work. For example, collect multiple data-sets from the +same field area (geology, seismic, electromagnetics, hydrogeology), process the data separately, and then reconvene with your +multidisciplinary team. You may be rather surprised (or not) that the everyone has a (completely!?) different model. Dissonant at best, +but often conflicting in the details. Therein lies the second problem: how do we integrate these geoscience fields? Not by force or +even by default, but at least to have the option of quantitative communication and built in feedback loops. What we require is an +implementation that is inherently and unequivocally modular, with all pieces available to manipulation. Black-box software, where the +implementations are hidden, obfuscated, or difficult to manipulate, do not promote experimentation and investigation. We are working on +a framework that exposes the details of the implementation to the geophysicist in a manner that promotes productivity and question +based interrogation. This framework can be easily extended to encompass many geophysical problems and is built with the inverse problem +as the fundamental goal. + +The future we see is a mix of tools that span our disciplines, and a framework that allows us to integrate many different types of +geophysical data so that we can communicate effectively and experiment efficiently. A toolbox combined with a framework that allows you +to solve your own problems, and creates opportunities for us to work together to better image and understand the subsurface. What we +are building is called SimPEG, simulation and parameter estimation in geophysics. We are building it in the open. We are testing it. +Breaking it. Building it. Fixing it. Using it. If you believe, like we do, that geophysics can be more innovative and informative in +the open and that these tools are necessary and invaluable in education as well as research, then you should get in touch. There is a +lot of work to do! The Big Picture -=============== +--------------- + +Defining a well-posed inverse problem and solving it is a complex task that requires many components that must interact. It is helpful +to view this task as a workflow in which various elements are explicitly identified and integrated. The figure below outlines the inversion components that consists of inputs, implementation, and evaluation. The inputs are composed of the geophysical data, the equations which are a mathematical description of the governing physics, and prior knowledge or assumptions about the setting. The implementation consists of two broad categories: the forward simulation and the inversion. The **forward simulation** is the means by which we solve the governing equations given a model and the **inversion components** evaluate and update this model. We are considering a gradient based approach, which updates the model through an optimization routine. The output of this implementation is a model, which, prior to interpretation, must be evaluated. This requires considering, and often re-assessing, the choices and assumptions made in both the input and implementation stages. + +.. image:: InversionWorkflow-PreSimPEG.png + :width: 400 px + :alt: Components + :align: center + + +A Comprehensive Framework +------------------------- + +There are an overwhelming amount of choices to be made as one works through the forward modeling and inversion process (see figure above). As a result, software implementations of this workflow often become complex and highly interdependent, making it difficult to interact with and to ask other scientists to pick up and change. Our approach to handling this complexity is to propose a framework, (see below), that compartmentalizes the implementation of inversions into various units. We present it in this specific modular style, as each unit contains a targeted subset of choices crucial to the inversion process. .. image:: InversionWorkflow.png :width: 400 px :alt: Framework :align: center +The process of obtaining an acceptable model from an inversion generally requires the geophysicist to perform several iterations of the inversion workflow, rethinking and redesigning each piece of the framework to ensure it is appropriate in the current context. Inversions are experimental and empirical by nature and our software package is designed to facilitate this iterative process. To accomplish this, we have divided the inversion methodology into eight major components (See figure above). The (:class:`SimPEG.Mesh.BaseMesh`) class handles the discretization of the earth and also provides numerical operators. The forward simulation is split into two classes, the (:class:`SimPEG.Survey.BaseSurvey`) and the (:class:`SimPEG.Problem.BaseProblem`). The (:class:`SimPEG.Survey.BaseSurvey`) class handles the geometry of a geophysical problem as well as sources. The (:class:`SimPEG.Problem.BaseProblem`) class handles the simulation of the physics for the geophysical problem of interest. Although created independently, these two classes must be paired to form all of the components necessary for a geophysical forward simulation and calculation of the sensitivity. The (:class:`SimPEG.Problem.BaseProblem`) creates geophysical fields given a source from the (:class:`SimPEG.Survey.BaseSurvey`). The (:class:`SimPEG.Survey.BaseSurvey`) interpolates these fields to the receiver locations and converts them to the appropriate data type, for example, by selecting only the measured components of the field. Each of these operations may have associated derivatives with respect to the model and the computed field; these are included in the calculation of the sensitivity. For the inversion, a (:class:`SimPEG.DataMisfit.BaseDataMisfit`) is chosen to capture the goodness of fit of the predicted data and a (:class:`SimPEG.Regularization.BaseRegularization`) is chosen to handle the non-uniqueness. These inversion elements and an Optimization routine are combined into an inverse problem class (:class:`SimPEG.InvProblem.BaseInvProblem`). (:class:`SimPEG.InvProblem.BaseInvProblem`) is the mathematical statement that will be numerically solved by running an Inversion. The (:class:`SimPEG.Inversion.BaseInversion`) class handles organization and dispatch of directives between all of the various pieces of the framework. -Explaining The Big Picture -========================== +The arrows in the figure above indicate what each class takes as a primary argument. For example, both the (:class:`SimPEG.Problem.BaseProblem`) and (:class:`SimPEG.Regularization.BaseRegularization`) classes take a (:class:`SimPEG.Mesh.BaseMesh`) class as an argument. The diagram does not show class inheritance, as each of the base classes outlined have many subtypes that can be interchanged. The (:class:`SimPEG.Mesh.BaseMesh`) class, for example, could be a regular Cartesian mesh (:class:`SimPEG.Mesh.TensorMesh`) or a cylindrical coordinate mesh (:class:`SimPEG.Mesh.CylMesh`), which have many properties in common. These common features, such as both meshes being created from tensor products, can be exploited through inheritance of base classes, and differences can be expressed through subtype polymorphism. Please look at the documentation here for more in-depth information. + + +.. include:: ../CITATION.rst + +Authors +------- + +.. include:: ../AUTHORS.rst + +License +------- + +.. include:: ../LICENSE diff --git a/docs/api_installing.rst b/docs/api_installing.rst index e0e7ab59..23938dee 100644 --- a/docs/api_installing.rst +++ b/docs/api_installing.rst @@ -1,7 +1,7 @@ .. _api_installing: -Installation -************ +Getting Started with SimPEG +*************************** Dependencies ============ diff --git a/docs/api_license.rst b/docs/api_license.rst deleted file mode 100644 index b8f2d439..00000000 --- a/docs/api_license.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. _api_license: - -License -******* - -.. include:: ../LICENSE - -Authors -******* - -.. include:: ../AUTHORS.rst - - -Projects Using SimPEG -********************* - -.. include:: ../PROJECTS.rst diff --git a/docs/em/api_Utils.rst b/docs/em/api_Utils.rst index b7e9ea45..8ae98855 100644 --- a/docs/em/api_Utils.rst +++ b/docs/em/api_Utils.rst @@ -7,7 +7,7 @@ sources, and analytic functions. Analytic Functions - Time ========================= -.. automodule:: SimPEG.EM.Utils.Ana.TEM +.. automodule:: SimPEG.EM.Analytics.TDEM :show-inheritance: :members: :undoc-members: @@ -17,7 +17,7 @@ Analytic Functions - Time Analytic Functions - Frequency ============================== -.. automodule:: SimPEG.EM.Utils.Ana.FEM +.. automodule:: SimPEG.EM.Analytics.FDEM :show-inheritance: :members: :undoc-members: @@ -27,8 +27,7 @@ Analytic Functions - Frequency Sources ======= -.. automodule:: SimPEG.EM.Utils.Sources.magneticDipole +.. autoclass:: SimPEG.EM.FDEM.SrcFDEM.MagDipole :show-inheritance: :members: :undoc-members: - :inherited-members: diff --git a/docs/em/index.rst b/docs/em/index.rst index 8cbb8619..fdf4dc19 100644 --- a/docs/em/index.rst +++ b/docs/em/index.rst @@ -9,7 +9,7 @@ Time Domian Electromagnetics ---------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 2 api_TDEM_derivation @@ -18,7 +18,7 @@ Code for Time Domian Electromagnetics ------------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 2 api_TDEM @@ -28,7 +28,6 @@ Frequency Domian Electromagnetics .. toctree:: :maxdepth: 2 - api_ForwardProblem api_FDEM diff --git a/docs/index.rst b/docs/index.rst index d3ec4e8c..be173aae 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,25 +1,38 @@ +.. image:: https://raw.github.com/simpeg/simpeg/master/docs/simpeg-logo.png + :alt: SimPEG Logo + SimPEG Documentation ******************** -.. image:: simpeg-logo.png - :width: 300 px - :alt: SimPEG - :align: center +.. image:: https://img.shields.io/pypi/v/SimPEG.svg + :target: https://crate.io/packages/SimPEG/ + :alt: Latest PyPI version -SimPEG: Simulation and Parameter Estimation in Geophysics +.. image:: https://img.shields.io/pypi/dm/SimPEG.svg + :target: https://crate.io/packages/SimPEG/ + :alt: Number of PyPI downloads -SimPEG is a framework and a collection of tools that aid in the development of -large-scale geophysical inversion codes. -The vision is to create a modular and extensible package for -finite volume simulation and parameter estimation with -applications to geophysical imaging and subsurface flow. To enable -these goals, this package has the following features: +.. image:: https://img.shields.io/badge/license-MIT-blue.svg + :target: https://github.com/simpeg/simpeg/blob/master/LICENSE + :alt: BSD 3 clause license. -- is modular with respect to ... everything! -- is built with the (large-scale) inverse problem in mind -- provides a framework for geophysical and hydrogeologic problems -- supports 1D, 2D and 3D problems -- provides a set of commonly used visualization utilities +.. image:: https://img.shields.io/travis/simpeg/simpeg.svg + :target: https://travis-ci.org/simpeg/simpeg + :alt: Travis CI build status + +.. image:: https://img.shields.io/coveralls/simpeg/simpeg.svg + :target: https://coveralls.io/r/simpeg/simpeg?branch=master + :alt: Coverage status + +Simulation and Parameter Estimation in Geophysics - A python package for simulation and gradient based parameter estimation in the context of geophysical applications. + +Our vision is to create a package for finite volume simulation with applications to geophysical imaging and subsurface flow. To enable the understanding of the many different components, this package has the following features: + +* modular with respect to the spacial discretization, optimization routine, and geophysical problem +* built with the inverse problem in mind +* provides a framework for geophysical and hydrogeologic problems +* supports 1D, 2D and 3D problems +* designed for large-scale inversions About SimPEG @@ -29,56 +42,8 @@ About SimPEG :maxdepth: 2 api_bigPicture - api_license - - -Getting Started with SimPEG -*************************** - -.. toctree:: - :maxdepth: 2 - api_installing -Discretization -************** - -.. toctree:: - :maxdepth: 3 - - api_Mesh - api_DiffOps - api_InnerProducts - -Forward Problems -**************** - -.. toctree:: - :maxdepth: 2 - - api_ForwardProblem - -Inversion -********* - -.. toctree:: - :maxdepth: 3 - - api_DataMisfit - api_Inverse - -Utility Codes -************* - -.. toctree:: - :maxdepth: 2 - - api_Solver - api_Maps - api_Utils - api_Tests - - Packages ******** @@ -88,20 +53,46 @@ Packages em/index flow/index -Developer's Documentation -************************* +Examples +******** -* Travis-CI Testing - .. image:: https://travis-ci.org/simpeg/simpeg.svg?branch=master - :target: https://travis-ci.org/simpeg/simpeg - :alt: Master Branch - :align: center +.. toctree:: + :maxdepth: 2 -* Coveralls Testing - .. image:: https://coveralls.io/repos/simpeg/simpeg/badge.png?branch=master - :target: https://coveralls.io/r/simpeg/simpeg?branch=master - :alt: Coveralls - :align: center + api_Examples + + +Finite Volume +************* + +.. toctree:: + :maxdepth: 3 + + api_FiniteVolume + +Forward Problems +**************** + +.. toctree:: + :maxdepth: 3 + + api_ForwardProblem + +Inversion Components +******************** + +.. toctree:: + :maxdepth: 3 + + api_InversionComponents + +Utility Codes +************* + +.. toctree:: + :maxdepth: 3 + + api_Utilities Project Index & Search @@ -110,11 +101,3 @@ Project Index & Search * :ref:`genindex` * :ref:`modindex` * :ref:`search` - -Examples -******** - -.. toctree:: - :maxdepth: 2 - - api_Examples From dedabcc15f7dc97d3f3346045b721669d0335bf9 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sun, 10 Jan 2016 18:36:02 -0800 Subject: [PATCH 39/77] Allow solver kwargs to go to the class directly. --- SimPEG/InvProblem.py | 4 ++-- SimPEG/Utils/SolverUtils.py | 21 +++++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/SimPEG/InvProblem.py b/SimPEG/InvProblem.py index 32a2195c..0296bf4b 100644 --- a/SimPEG/InvProblem.py +++ b/SimPEG/InvProblem.py @@ -66,8 +66,8 @@ class BaseInvProblem(object): self.curModel = m0 print """SimPEG.InvProblem is setting bfgsH0 to the inverse of the eval2Deriv. - ***Done using same solver as the problem***""" - self.opt.bfgsH0 = self.prob.Solver(self.reg.eval2Deriv(self.curModel)) + ***Done using same Solver and solverOpts as the problem***""" + self.opt.bfgsH0 = self.prob.Solver(self.reg.eval2Deriv(self.curModel), **self.prob.solverOpts) @property def warmstart(self): diff --git a/SimPEG/Utils/SolverUtils.py b/SimPEG/Utils/SolverUtils.py index 26ff3e2a..47c899d3 100644 --- a/SimPEG/Utils/SolverUtils.py +++ b/SimPEG/Utils/SolverUtils.py @@ -26,7 +26,14 @@ def SolverWrapD(fun, factorize=True, checkAccuracy=True, accuracyTol=1e-6): def __init__(self, A, **kwargs): self.A = A.tocsc() + + self.checkAccuracy = kwargs.get("checkAccuracy", checkAccuracy) + if kwargs.has_key("checkAccuracy"): del kwargs["checkAccuracy"] + self.accuracyTol = kwargs.get("accuracyTol", accuracyTol) + if kwargs.has_key("accuracyTol"): del kwargs["accuracyTol"] + self.kwargs = kwargs + if factorize: self.solver = fun(self.A, **kwargs) @@ -57,8 +64,8 @@ def SolverWrapD(fun, factorize=True, checkAccuracy=True, accuracyTol=1e-6): else: X[:,i] = fun(self.A, b[:,i], **self.kwargs) - if checkAccuracy: - _checkAccuracy(self.A, b, X, accuracyTol) + if self.checkAccuracy: + _checkAccuracy(self.A, b, X, self.accuracyTol) return X def clean(self): @@ -81,6 +88,12 @@ def SolverWrapI(fun, checkAccuracy=True, accuracyTol=1e-5): def __init__(self, A, **kwargs): self.A = A + + self.checkAccuracy = kwargs.get("checkAccuracy", checkAccuracy) + if kwargs.has_key("checkAccuracy"): del kwargs["checkAccuracy"] + self.accuracyTol = kwargs.get("accuracyTol", accuracyTol) + if kwargs.has_key("accuracyTol"): del kwargs["accuracyTol"] + self.kwargs = kwargs def __mul__(self, b): @@ -108,8 +121,8 @@ def SolverWrapI(fun, checkAccuracy=True, accuracyTol=1e-5): else: X[:,i] = out - if checkAccuracy: - _checkAccuracy(self.A, b, X, accuracyTol) + if self.checkAccuracy: + _checkAccuracy(self.A, b, X, self.accuracyTol) return X def clean(self): From 32f3ef301aaa54e98589e07507c87fb665b73cda Mon Sep 17 00:00:00 2001 From: seogi_macbook Date: Tue, 12 Jan 2016 18:52:39 -0800 Subject: [PATCH 40/77] Modifications for general waveform --- SimPEG/EM/TDEM/BaseTDEM.py | 4 ++-- SimPEG/EM/TDEM/SurveyTDEM.py | 30 ++++++++++++++++++++++++------ SimPEG/Problem.py | 8 ++++++++ 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/SimPEG/EM/TDEM/BaseTDEM.py b/SimPEG/EM/TDEM/BaseTDEM.py index e36d76b8..4d4b39a7 100644 --- a/SimPEG/EM/TDEM/BaseTDEM.py +++ b/SimPEG/EM/TDEM/BaseTDEM.py @@ -31,7 +31,7 @@ class FieldsTDEM(Problem.TimeFields): class BaseTDEMProblem(BaseTimeProblem, BaseEMProblem): - """docstring for ProblemTDEM1D""" + """docstring for BaseTDEMProblem""" def __init__(self, mesh, mapping=None, **kwargs): BaseTimeProblem.__init__(self, mesh, mapping=mapping, **kwargs) @@ -43,7 +43,7 @@ class BaseTDEMProblem(BaseTimeProblem, BaseEMProblem): # Create a fields storage object F = self._FieldsForward_pair(self.mesh, self.survey) for src in self.survey.srcList: - # Set the initial conditions + # Set the initial conditions F[src,:,0] = src.getInitialFields(self.mesh) F = self.forward(m, self.getRHS, F=F) if self.verbose: print '%s\nDone calculating fields(m)\n%s'%('*'*50,'*'*50) diff --git a/SimPEG/EM/TDEM/SurveyTDEM.py b/SimPEG/EM/TDEM/SurveyTDEM.py index 7f6e5c04..6a65c0fe 100644 --- a/SimPEG/EM/TDEM/SurveyTDEM.py +++ b/SimPEG/EM/TDEM/SurveyTDEM.py @@ -99,14 +99,33 @@ class SrcTDEM_VMD_MVP(SrcTDEM): class SrcTDEM_CircularLoop_MVP(SrcTDEM): - - def __init__(self,rxList,loc,radius): + def __init__(self,rxList,loc,radius,waveformType): self.loc = loc self.radius = radius - SrcTDEM.__init__(self,rxList) + self.waveformType = waveformType + SrcTDEM.__init__(self,rxList) def getInitialFields(self, mesh): """Circular Loop, magnetic vector potential""" + if self.waveformType == "STEPOFF": + print ">> Step waveform: Non-zero initial condition" + if mesh._meshType is 'CYL': + if mesh.isSymmetric: + MVP = MagneticLoopVectorPotential(self.loc, mesh, 'Ey', self.radius) + else: + raise NotImplementedError('Non-symmetric cyl mesh not implemented yet!') + elif mesh._meshType is 'TENSOR': + MVP = MagneticLoopVectorPotential(self.loc, mesh, ['Ex','Ey','Ez'], self.radius) + else: + raise Exception('Unknown mesh for CircularLoop') + return {"b": mesh.edgeCurl*MVP} + elif self.waveformType == "GENERAL": + print ">> General waveform: Zero initial condition" + return {"b": np.zeros(mesh.nF)} + else: + raise NotImplementedError("Only use STEPOFF or GENERAL") + + def getMeS(self, mesh, MfMui): if mesh._meshType is 'CYL': if mesh.isSymmetric: MVP = MagneticLoopVectorPotential(self.loc, mesh, 'Ey', self.radius) @@ -115,9 +134,8 @@ class SrcTDEM_CircularLoop_MVP(SrcTDEM): elif mesh._meshType is 'TENSOR': MVP = MagneticLoopVectorPotential(self.loc, mesh, ['Ex','Ey','Ez'], self.radius) else: - raise Exception('Unknown mesh for CircularLoop') - - return {"b": mesh.edgeCurl*MVP} + raise Exception('Unknown mesh for CircularLoop') + return mesh.edgeCurl.T*MfMui*mesh.edgeCurl*MVP class SurveyTDEM(Survey.BaseSurvey): diff --git a/SimPEG/Problem.py b/SimPEG/Problem.py index 607cff5b..be29ff01 100644 --- a/SimPEG/Problem.py +++ b/SimPEG/Problem.py @@ -158,6 +158,9 @@ class BaseProblem(object): class BaseTimeProblem(BaseProblem): """Sets up that basic needs of a time domain problem.""" + + waveformType = "STEPOFF" + current = None @property def timeSteps(self): @@ -184,6 +187,11 @@ class BaseTimeProblem(BaseProblem): self._timeSteps = Utils.meshTensor(value) del self.timeMesh + def currentwaveform(self, wave): + self._timeSteps = np.diff(wave[:,0]) + self.current = wave[:,1] + self.waveformType = "GENERAL" + @property def nT(self): "Number of time steps." From 1700f4f9c068464b770f7f9909d6eb20a29331b7 Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Thu, 14 Jan 2016 14:00:55 -0800 Subject: [PATCH 41/77] add Ainv.clean() to fdem fields, jvec, jtvec --- SimPEG/EM/FDEM/FDEM.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SimPEG/EM/FDEM/FDEM.py b/SimPEG/EM/FDEM/FDEM.py index f2167fd8..3295fd0c 100644 --- a/SimPEG/EM/FDEM/FDEM.py +++ b/SimPEG/EM/FDEM/FDEM.py @@ -50,7 +50,7 @@ class BaseFDEMProblem(BaseEMProblem): Srcs = self.survey.getSrcByFreq(freq) ftype = self._fieldType + 'Solution' F[Srcs, ftype] = sol - + Ainv.clean() return F def Jvec(self, m, v, f=None): @@ -89,6 +89,7 @@ class BaseFDEMProblem(BaseEMProblem): Jv[src, rx] = P(Df_Dm) + Ainv.clean() return Utils.mkvc(Jv) def Jtvec(self, m, v, f=None): @@ -139,7 +140,8 @@ class BaseFDEMProblem(BaseEMProblem): Jtv += - np.array(du_dmT,dtype=complex).real else: raise Exception('Must be real or imag') - + + ATinv.clean() return Jtv def getSourceTerm(self, freq): From 01b1122fcff1be9034e3a5f7ffd7841b6f74859e Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Thu, 14 Jan 2016 15:12:09 -0800 Subject: [PATCH 42/77] Add default interpolation location (CC). --- SimPEG/Mesh/TensorMesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SimPEG/Mesh/TensorMesh.py b/SimPEG/Mesh/TensorMesh.py index c76306fe..5ea2d86a 100644 --- a/SimPEG/Mesh/TensorMesh.py +++ b/SimPEG/Mesh/TensorMesh.py @@ -215,7 +215,7 @@ class BaseTensorMesh(BaseMesh): inside = inside & (pts[:,i] >= tensor.min()-TOL) & (pts[:,i] <= tensor.max()+TOL) return inside - def getInterpolationMat(self, loc, locType, zerosOutside=False): + def getInterpolationMat(self, loc, locType='CC', zerosOutside=False): """ Produces interpolation matrix :param numpy.ndarray loc: Location of points to interpolate to From e15913cf8488daf1dc3c940af903b991ee089bc9 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Thu, 14 Jan 2016 15:12:34 -0800 Subject: [PATCH 43/77] Import all code utils into the utils namespace. --- SimPEG/Utils/__init__.py | 2 +- SimPEG/Utils/codeutils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SimPEG/Utils/__init__.py b/SimPEG/Utils/__init__.py index 250146c1..18c1994f 100644 --- a/SimPEG/Utils/__init__.py +++ b/SimPEG/Utils/__init__.py @@ -1,6 +1,6 @@ from matutils import * from codeutils import * -from meshutils import exampleLrmGrid, meshTensor, closestPoints, readUBCTensorMesh, writeUBCTensorMesh, writeUBCTensorModel, readVTRFile, writeVTRFile +from meshutils import * from curvutils import volTetra, faceInfo, indexCube from interputils import interpmat from CounterUtils import * diff --git a/SimPEG/Utils/codeutils.py b/SimPEG/Utils/codeutils.py index 4a9a28a7..bfd00889 100644 --- a/SimPEG/Utils/codeutils.py +++ b/SimPEG/Utils/codeutils.py @@ -17,7 +17,7 @@ def memProfileWrapper(towrap, *funNames): For example:: - foo_mem = memProfile(foo,'my_func') + foo_mem = memProfileWrapper(foo,['my_func']) fooi = foo_mem() for i in range(5): fooi.my_func() From c6e90230d4b22a615cebfcffee43c624fc646863 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Thu, 14 Jan 2016 15:57:15 -0800 Subject: [PATCH 44/77] Updates to the correct pointer for the flow module (docs) --- docs/flow/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/flow/index.rst b/docs/flow/index.rst index 3c8083fc..d4299b0d 100644 --- a/docs/flow/index.rst +++ b/docs/flow/index.rst @@ -41,7 +41,7 @@ Here we reproduce the results from Celia et al. (1990): Richards ======== -.. automodule:: simpegFLOW.Richards.Empirical +.. automodule:: SimPEG.FLOW.Richards.Empirical :show-inheritance: :members: :undoc-members: From 7a4aa4ddc3140b5c9293f85e7f589e82f4428132 Mon Sep 17 00:00:00 2001 From: Lindsey Date: Fri, 15 Jan 2016 10:07:11 -0800 Subject: [PATCH 45/77] Update and rename EM_FDEM_1D_Inversion.py to EM_TDEM_1D_Inversion.py - this is a TDEM inversion (not FDEM) --- .../{EM_FDEM_1D_Inversion.py => EM_TDEM_1D_Inversion.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename SimPEG/Examples/{EM_FDEM_1D_Inversion.py => EM_TDEM_1D_Inversion.py} (97%) diff --git a/SimPEG/Examples/EM_FDEM_1D_Inversion.py b/SimPEG/Examples/EM_TDEM_1D_Inversion.py similarity index 97% rename from SimPEG/Examples/EM_FDEM_1D_Inversion.py rename to SimPEG/Examples/EM_TDEM_1D_Inversion.py index aba70f4b..d4d80e55 100644 --- a/SimPEG/Examples/EM_FDEM_1D_Inversion.py +++ b/SimPEG/Examples/EM_TDEM_1D_Inversion.py @@ -5,10 +5,10 @@ from scipy.constants import mu_0 def run(plotIt=True): """ - EM: FDEM: 1D: Inversion + EM: TDEM: 1D: Inversion ======================= - Here we will create and run a FDEM 1D inversion. + Here we will create and run a TDEM 1D inversion. """ From 0b971f4a50315d439c82672cbc6a885a9712e106 Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Fri, 15 Jan 2016 10:46:31 -0800 Subject: [PATCH 46/77] updated examples init to also update docs --- SimPEG/Examples/__init__.py | 4 ++-- ..._FDEM_1D_Inversion.rst => EM_TDEM_1D_Inversion.rst} | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) rename docs/examples/{EM_FDEM_1D_Inversion.rst => EM_TDEM_1D_Inversion.rst} (64%) diff --git a/SimPEG/Examples/__init__.py b/SimPEG/Examples/__init__.py index 8431e4ba..78b7c5a6 100644 --- a/SimPEG/Examples/__init__.py +++ b/SimPEG/Examples/__init__.py @@ -1,7 +1,7 @@ # Run this file to add imports. ##### AUTOIMPORTS ##### -import EM_FDEM_1D_Inversion +import EM_TDEM_1D_Inversion import FLOW_Richards_1D_Celia1990 import Forward_BasicDirectCurrent import Inversion_Linear @@ -13,7 +13,7 @@ import Mesh_QuadTree_FaceDiv import Mesh_QuadTree_HangingNodes import Mesh_Tensor_Creation -__examples__ = ["EM_FDEM_1D_Inversion", "FLOW_Richards_1D_Celia1990", "Forward_BasicDirectCurrent", "Inversion_Linear", "Mesh_Basic_PlotImage", "Mesh_Basic_Types", "Mesh_Operators_CahnHilliard", "Mesh_QuadTree_Creation", "Mesh_QuadTree_FaceDiv", "Mesh_QuadTree_HangingNodes", "Mesh_Tensor_Creation"] +__examples__ = ["EM_TDEM_1D_Inversion", "FLOW_Richards_1D_Celia1990", "Forward_BasicDirectCurrent", "Inversion_Linear", "Mesh_Basic_PlotImage", "Mesh_Basic_Types", "Mesh_Operators_CahnHilliard", "Mesh_QuadTree_Creation", "Mesh_QuadTree_FaceDiv", "Mesh_QuadTree_HangingNodes", "Mesh_Tensor_Creation"] ##### AUTOIMPORTS ##### diff --git a/docs/examples/EM_FDEM_1D_Inversion.rst b/docs/examples/EM_TDEM_1D_Inversion.rst similarity index 64% rename from docs/examples/EM_FDEM_1D_Inversion.rst rename to docs/examples/EM_TDEM_1D_Inversion.rst index acbc8cdc..53f6f9ef 100644 --- a/docs/examples/EM_FDEM_1D_Inversion.rst +++ b/docs/examples/EM_TDEM_1D_Inversion.rst @@ -1,4 +1,4 @@ -.. _examples_EM_FDEM_1D_Inversion: +.. _examples_EM_TDEM_1D_Inversion: .. --------------------------------- .. .. .. @@ -9,18 +9,18 @@ .. --------------------------------- .. -EM: FDEM: 1D: Inversion +EM: TDEM: 1D: Inversion ======================= -Here we will create and run a FDEM 1D inversion. +Here we will create and run a TDEM 1D inversion. .. plot:: from SimPEG import Examples - Examples.EM_FDEM_1D_Inversion.run() + Examples.EM_TDEM_1D_Inversion.run() -.. literalinclude:: ../../SimPEG/Examples/EM_FDEM_1D_Inversion.py +.. literalinclude:: ../../SimPEG/Examples/EM_TDEM_1D_Inversion.py :language: python :linenos: From 7107cd9d94c392d37b007ddea36a05d5fae3bca3 Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Tue, 19 Jan 2016 23:57:41 -0800 Subject: [PATCH 47/77] plot a harmonic mag dipole --- .../EM_Analytic_MagDipoleWholespace.py | 47 +++++++++++++++++++ SimPEG/Examples/__init__.py | 3 +- .../EM_Analytic_MagDipoleWholespace.rst | 26 ++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 SimPEG/Examples/EM_Analytic_MagDipoleWholespace.py create mode 100644 docs/examples/EM_Analytic_MagDipoleWholespace.rst diff --git a/SimPEG/Examples/EM_Analytic_MagDipoleWholespace.py b/SimPEG/Examples/EM_Analytic_MagDipoleWholespace.py new file mode 100644 index 00000000..31408bcc --- /dev/null +++ b/SimPEG/Examples/EM_Analytic_MagDipoleWholespace.py @@ -0,0 +1,47 @@ +from SimPEG import * +import SimPEG.EM as EM + +import matplotlib.pyplot as plt +from matplotlib.colors import LogNorm + +x = np.arange(-100.5,100.5,step = 1.) #(avoid putting measurement points where source is located) +y = np.r_[0] +z = x + +sig = 1. +freq = 1. + +XYZ = Utils.ndgrid(x,y,z) + + +def run(XYZ=XYZ,sig=sig,freq=freq,orientation='Z', plotIt=True): + """ + EM: Magnetic Dipole in a Whole-Space + ==================================== + + Here we plot the magnetic flux density from a harmonic dipole in a wholespace. + + """ + + Bx, By, Bz = EM.Analytics.FDEM.MagneticDipoleWholeSpace(XYZ, np.r_[0.,0.,0.], sig, freq,orientation=orientation) + absB = np.sqrt(Bx*Bx.conj()+By*By.conj()+Bz*Bz.conj()).real + + + if plotIt: + fig, ax = plt.subplots(1,1,figsize=(6,5)) + bxplt = Bx.reshape(x.size,z.size) + bzplt = Bz.reshape(x.size,z.size) + pc = ax.pcolor(x,z,absB.reshape(x.size,z.size),norm=LogNorm()) + ax.streamplot(x,z,bxplt.real,bzplt.real,color='k',density=1) + ax.set_xlim([x.min(),x.max()]) + ax.set_ylim([z.min(),z.max()]) + ax.set_xlabel('x') + ax.set_ylabel('z') + cb = plt.colorbar(pc,ax = ax) + cb.set_label('|B| (T)') + plt.show() + + return fig, ax + +if __name__ == '__main__': + run() \ No newline at end of file diff --git a/SimPEG/Examples/__init__.py b/SimPEG/Examples/__init__.py index 78b7c5a6..055feeac 100644 --- a/SimPEG/Examples/__init__.py +++ b/SimPEG/Examples/__init__.py @@ -1,6 +1,7 @@ # Run this file to add imports. ##### AUTOIMPORTS ##### +import EM_Analytic_MagDipoleWholespace import EM_TDEM_1D_Inversion import FLOW_Richards_1D_Celia1990 import Forward_BasicDirectCurrent @@ -13,7 +14,7 @@ import Mesh_QuadTree_FaceDiv import Mesh_QuadTree_HangingNodes import Mesh_Tensor_Creation -__examples__ = ["EM_TDEM_1D_Inversion", "FLOW_Richards_1D_Celia1990", "Forward_BasicDirectCurrent", "Inversion_Linear", "Mesh_Basic_PlotImage", "Mesh_Basic_Types", "Mesh_Operators_CahnHilliard", "Mesh_QuadTree_Creation", "Mesh_QuadTree_FaceDiv", "Mesh_QuadTree_HangingNodes", "Mesh_Tensor_Creation"] +__examples__ = ["EM_Analytic_MagDipoleWholespace", "EM_TDEM_1D_Inversion", "FLOW_Richards_1D_Celia1990", "Forward_BasicDirectCurrent", "Inversion_Linear", "Mesh_Basic_PlotImage", "Mesh_Basic_Types", "Mesh_Operators_CahnHilliard", "Mesh_QuadTree_Creation", "Mesh_QuadTree_FaceDiv", "Mesh_QuadTree_HangingNodes", "Mesh_Tensor_Creation"] ##### AUTOIMPORTS ##### diff --git a/docs/examples/EM_Analytic_MagDipoleWholespace.rst b/docs/examples/EM_Analytic_MagDipoleWholespace.rst new file mode 100644 index 00000000..bfe8d33b --- /dev/null +++ b/docs/examples/EM_Analytic_MagDipoleWholespace.rst @@ -0,0 +1,26 @@ +.. _examples_EM_Analytic_MagDipoleWholespace: + +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + + +EM: Magnetic Dipole in a Whole-Space +==================================== + +Here we plot the magnetic flux density from a harmonic dipole in a wholespace. + + + +.. plot:: + + from SimPEG import Examples + Examples.EM_Analytic_MagDipoleWholespace.run() + +.. literalinclude:: ../../SimPEG/Examples/EM_Analytic_MagDipoleWholespace.py + :language: python + :linenos: From 98f209d1f0a368912a42442aab7a5b9aa03d993f Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Wed, 20 Jan 2016 08:04:01 -0800 Subject: [PATCH 48/77] specify that example is FDEM --- ...holespace.py => EM_FDEM_Analytic_MagDipoleWholespace.py} | 0 SimPEG/Examples/__init__.py | 4 ++-- ...lespace.rst => EM_FDEM_Analytic_MagDipoleWholespace.rst} | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) rename SimPEG/Examples/{EM_Analytic_MagDipoleWholespace.py => EM_FDEM_Analytic_MagDipoleWholespace.py} (100%) rename docs/examples/{EM_Analytic_MagDipoleWholespace.rst => EM_FDEM_Analytic_MagDipoleWholespace.rst} (73%) diff --git a/SimPEG/Examples/EM_Analytic_MagDipoleWholespace.py b/SimPEG/Examples/EM_FDEM_Analytic_MagDipoleWholespace.py similarity index 100% rename from SimPEG/Examples/EM_Analytic_MagDipoleWholespace.py rename to SimPEG/Examples/EM_FDEM_Analytic_MagDipoleWholespace.py diff --git a/SimPEG/Examples/__init__.py b/SimPEG/Examples/__init__.py index 055feeac..894d6ea0 100644 --- a/SimPEG/Examples/__init__.py +++ b/SimPEG/Examples/__init__.py @@ -1,7 +1,7 @@ # Run this file to add imports. ##### AUTOIMPORTS ##### -import EM_Analytic_MagDipoleWholespace +import EM_FDEM_Analytic_MagDipoleWholespace import EM_TDEM_1D_Inversion import FLOW_Richards_1D_Celia1990 import Forward_BasicDirectCurrent @@ -14,7 +14,7 @@ import Mesh_QuadTree_FaceDiv import Mesh_QuadTree_HangingNodes import Mesh_Tensor_Creation -__examples__ = ["EM_Analytic_MagDipoleWholespace", "EM_TDEM_1D_Inversion", "FLOW_Richards_1D_Celia1990", "Forward_BasicDirectCurrent", "Inversion_Linear", "Mesh_Basic_PlotImage", "Mesh_Basic_Types", "Mesh_Operators_CahnHilliard", "Mesh_QuadTree_Creation", "Mesh_QuadTree_FaceDiv", "Mesh_QuadTree_HangingNodes", "Mesh_Tensor_Creation"] +__examples__ = ["EM_FDEM_Analytic_MagDipoleWholespace", "EM_TDEM_1D_Inversion", "FLOW_Richards_1D_Celia1990", "Forward_BasicDirectCurrent", "Inversion_Linear", "Mesh_Basic_PlotImage", "Mesh_Basic_Types", "Mesh_Operators_CahnHilliard", "Mesh_QuadTree_Creation", "Mesh_QuadTree_FaceDiv", "Mesh_QuadTree_HangingNodes", "Mesh_Tensor_Creation"] ##### AUTOIMPORTS ##### diff --git a/docs/examples/EM_Analytic_MagDipoleWholespace.rst b/docs/examples/EM_FDEM_Analytic_MagDipoleWholespace.rst similarity index 73% rename from docs/examples/EM_Analytic_MagDipoleWholespace.rst rename to docs/examples/EM_FDEM_Analytic_MagDipoleWholespace.rst index bfe8d33b..0e1b2d93 100644 --- a/docs/examples/EM_Analytic_MagDipoleWholespace.rst +++ b/docs/examples/EM_FDEM_Analytic_MagDipoleWholespace.rst @@ -1,4 +1,4 @@ -.. _examples_EM_Analytic_MagDipoleWholespace: +.. _examples_EM_FDEM_Analytic_MagDipoleWholespace: .. --------------------------------- .. .. .. @@ -19,8 +19,8 @@ Here we plot the magnetic flux density from a harmonic dipole in a wholespace. .. plot:: from SimPEG import Examples - Examples.EM_Analytic_MagDipoleWholespace.run() + Examples.EM_FDEM_Analytic_MagDipoleWholespace.run() -.. literalinclude:: ../../SimPEG/Examples/EM_Analytic_MagDipoleWholespace.py +.. literalinclude:: ../../SimPEG/Examples/EM_FDEM_Analytic_MagDipoleWholespace.py :language: python :linenos: From 91e37c5fa737f4215bfe2b73146f09e3d146b6fa Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Wed, 20 Jan 2016 12:52:51 -0700 Subject: [PATCH 49/77] Clean up init code, put inside run, remove mpl from TL import. --- .../EM_FDEM_Analytic_MagDipoleWholespace.py | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/SimPEG/Examples/EM_FDEM_Analytic_MagDipoleWholespace.py b/SimPEG/Examples/EM_FDEM_Analytic_MagDipoleWholespace.py index 31408bcc..791f8c63 100644 --- a/SimPEG/Examples/EM_FDEM_Analytic_MagDipoleWholespace.py +++ b/SimPEG/Examples/EM_FDEM_Analytic_MagDipoleWholespace.py @@ -1,33 +1,29 @@ from SimPEG import * import SimPEG.EM as EM -import matplotlib.pyplot as plt -from matplotlib.colors import LogNorm - -x = np.arange(-100.5,100.5,step = 1.) #(avoid putting measurement points where source is located) -y = np.r_[0] -z = x - -sig = 1. -freq = 1. - -XYZ = Utils.ndgrid(x,y,z) - - -def run(XYZ=XYZ,sig=sig,freq=freq,orientation='Z', plotIt=True): +def run(XYZ=None, sig=1.0, freq=1.0, orientation='Z', plotIt=True): """ EM: Magnetic Dipole in a Whole-Space ==================================== - Here we plot the magnetic flux density from a harmonic dipole in a wholespace. + Here we plot the magnetic flux density from a harmonic dipole in a wholespace. """ - Bx, By, Bz = EM.Analytics.FDEM.MagneticDipoleWholeSpace(XYZ, np.r_[0.,0.,0.], sig, freq,orientation=orientation) + if XYZ is None: + x = np.arange(-100.5,100.5,step = 1.) #(avoid putting measurement points where source is located) + y = np.r_[0] + z = x + XYZ = Utils.ndgrid(x,y,z) + + + Bx, By, Bz = EM.Analytics.FDEM.MagneticDipoleWholeSpace(XYZ, np.r_[0.,0.,0.], sig, freq, orientation=orientation) absB = np.sqrt(Bx*Bx.conj()+By*By.conj()+Bz*Bz.conj()).real - if plotIt: + if plotIt: + import matplotlib.pyplot as plt + from matplotlib.colors import LogNorm fig, ax = plt.subplots(1,1,figsize=(6,5)) bxplt = Bx.reshape(x.size,z.size) bzplt = Bz.reshape(x.size,z.size) @@ -44,4 +40,4 @@ def run(XYZ=XYZ,sig=sig,freq=freq,orientation='Z', plotIt=True): return fig, ax if __name__ == '__main__': - run() \ No newline at end of file + run() From 84400650fa35240c4f2bc3b8b0ab90a995c741cc Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sat, 23 Jan 2016 16:37:11 -0700 Subject: [PATCH 50/77] Clean up init file in examples. --- SimPEG/Examples/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/SimPEG/Examples/__init__.py b/SimPEG/Examples/__init__.py index 894d6ea0..7ef15451 100644 --- a/SimPEG/Examples/__init__.py +++ b/SimPEG/Examples/__init__.py @@ -29,16 +29,17 @@ if __name__ == '__main__': from SimPEG import Examples # Create the examples dir in the docs folder. - docExamplesDir = os.path.sep.join(os.path.realpath(__file__).split(os.path.sep)[:-3] + ['docs', 'examples']) + fName = os.path.realpath(__file__) + docExamplesDir = os.path.sep.join(fName.split(os.path.sep)[:-3] + ['docs', 'examples']) shutil.rmtree(docExamplesDir) os.makedirs(docExamplesDir) # Get all the python examples in this folder - thispath = os.path.sep.join(__file__.split(os.path.sep)[:-1]) + thispath = os.path.sep.join(fName.split(os.path.sep)[:-1]) exfiles = [f[:-3] for f in os.listdir(thispath) if os.path.isfile(os.path.join(thispath, f)) and f.endswith('.py') and not f.startswith('_')] # Add the imports to the top in the AUTOIMPORTS section - f = file(__file__, 'r') + f = file(fName, 'r') inimports = False out = '' for line in f: @@ -53,7 +54,7 @@ if __name__ == '__main__': out += '\n##### AUTOIMPORTS #####\n' f.close() - f = file(__file__, 'w') + f = file(fName, 'w') f.write(out) f.close() From 814bd003463c18265787a03c3b1ff748a36f18dd Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Tue, 26 Jan 2016 22:47:11 -0800 Subject: [PATCH 51/77] move examples right after getting started with SimPEG --- docs/index.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index be173aae..4ce06163 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -44,6 +44,15 @@ About SimPEG api_bigPicture api_installing +Examples +******** + +.. toctree:: + :maxdepth: 2 + + api_Examples + + Packages ******** @@ -53,14 +62,6 @@ Packages em/index flow/index -Examples -******** - -.. toctree:: - :maxdepth: 2 - - api_Examples - Finite Volume ************* From a8539cc23439795d1bb5b84e648fbd8e556dabe4 Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Wed, 27 Jan 2016 08:41:28 -0800 Subject: [PATCH 52/77] pass loc as a variable --- SimPEG/Examples/EM_FDEM_Analytic_MagDipoleWholespace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SimPEG/Examples/EM_FDEM_Analytic_MagDipoleWholespace.py b/SimPEG/Examples/EM_FDEM_Analytic_MagDipoleWholespace.py index 791f8c63..15ff9f62 100644 --- a/SimPEG/Examples/EM_FDEM_Analytic_MagDipoleWholespace.py +++ b/SimPEG/Examples/EM_FDEM_Analytic_MagDipoleWholespace.py @@ -1,7 +1,7 @@ from SimPEG import * import SimPEG.EM as EM -def run(XYZ=None, sig=1.0, freq=1.0, orientation='Z', plotIt=True): +def run(XYZ=None, loc=np.r_[0.,0.,0.], sig=1.0, freq=1.0, orientation='Z', plotIt=True): """ EM: Magnetic Dipole in a Whole-Space ==================================== @@ -17,7 +17,7 @@ def run(XYZ=None, sig=1.0, freq=1.0, orientation='Z', plotIt=True): XYZ = Utils.ndgrid(x,y,z) - Bx, By, Bz = EM.Analytics.FDEM.MagneticDipoleWholeSpace(XYZ, np.r_[0.,0.,0.], sig, freq, orientation=orientation) + Bx, By, Bz = EM.Analytics.FDEM.MagneticDipoleWholeSpace(XYZ, loc, sig, freq, orientation=orientation) absB = np.sqrt(Bx*Bx.conj()+By*By.conj()+Bz*Bz.conj()).real From d7be0ada312c1258a5d875bbd76c4d403d953904 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Thu, 28 Jan 2016 12:52:21 -0800 Subject: [PATCH 53/77] Update README.rst Fix the branch that SimPEG travis is pointing --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 71d5af88..1c645334 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,7 @@ SimPEG :target: https://github.com/simpeg/simpeg/blob/master/LICENSE :alt: BSD 3 clause license. -.. image:: https://img.shields.io/travis/simpeg/simpeg.svg +.. image:: https://api.travis-ci.org/simpeg/simpeg.svg?branch=master :target: https://travis-ci.org/simpeg/simpeg :alt: Travis CI build status From d50232b385e3f7b3b4ae5c96119d235a64b040c1 Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Thu, 28 Jan 2016 13:04:13 -0800 Subject: [PATCH 54/77] give Zero a transpose --- SimPEG/Utils/matutils.py | 12 ++++++++++++ tests/utils/test_Zero.py | 7 ++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/SimPEG/Utils/matutils.py b/SimPEG/Utils/matutils.py index b38bb4a1..ba900c72 100644 --- a/SimPEG/Utils/matutils.py +++ b/SimPEG/Utils/matutils.py @@ -26,6 +26,9 @@ def mkvc(x, numDims=1): if hasattr(x, 'tovec'): x = x.tovec() + if isinstance(x, Zero): + return x + assert isinstance(x, np.ndarray), "Vector must be a numpy array" if numDims == 1: @@ -37,6 +40,9 @@ def mkvc(x, numDims=1): def sdiag(h): """Sparse diagonal matrix""" + if isinstance(h, Zero): + return h + return sp.spdiags(mkvc(h), 0, h.size, h.size, format="csr") def sdInv(M): @@ -417,6 +423,12 @@ class Zero(object): def __ge__(self, v):return 0 >= v def __gt__(self, v):return 0 > v + @property + def transpose(self): return Zero() + + @property + def T(self): return Zero() + class Identity(object): _positive = True def __init__(self, positive=True): diff --git a/tests/utils/test_Zero.py b/tests/utils/test_Zero.py index 594de6a6..7b3c6e5d 100644 --- a/tests/utils/test_Zero.py +++ b/tests/utils/test_Zero.py @@ -1,5 +1,5 @@ import unittest -from SimPEG.Utils import Zero, Identity, sdiag +from SimPEG.Utils import Zero, Identity, sdiag, mkvc from SimPEG import np, sp class Tests(unittest.TestCase): @@ -29,6 +29,11 @@ class Tests(unittest.TestCase): assert a == 1 self.assertRaises(ZeroDivisionError, lambda:3/z) + assert mkvc(z) == 0 + assert sdiag(z)*a == 0 + assert z.T == 0 + assert z.transpose == 0 + def test_mat_zero(self): z = Zero() S = sdiag(np.r_[2,3]) From f5333f35a241c4bba3e6dd92ebc0da6637d1fecc Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Thu, 28 Jan 2016 13:07:48 -0800 Subject: [PATCH 55/77] use survey.createSyntheticData to make synthetic data --- SimPEG/Examples/EM_TDEM_1D_Inversion.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/SimPEG/Examples/EM_TDEM_1D_Inversion.py b/SimPEG/Examples/EM_TDEM_1D_Inversion.py index d4d80e55..86198064 100644 --- a/SimPEG/Examples/EM_TDEM_1D_Inversion.py +++ b/SimPEG/Examples/EM_TDEM_1D_Inversion.py @@ -50,20 +50,15 @@ def run(plotIt=True): prb.Solver = SolverLU prb.timeSteps = [(1e-06, 20),(1e-05, 20), (0.0001, 20)] prb.pair(survey) - dtrue = survey.dpred(mtrue) - - survey.dtrue = dtrue + # create observed data std = 0.05 - noise = std*abs(survey.dtrue)*np.random.randn(*survey.dtrue.shape) - survey.dobs = survey.dtrue+noise - survey.std = survey.dobs*0 + std - survey.Wd = 1/(abs(survey.dobs)*std) + survey.dobs = survey.makeSyntheticData(mtrue,std) if plotIt: import matplotlib.pyplot as plt fig, ax = plt.subplots(1,1, figsize = (10, 6)) - ax.loglog(rx.times, dtrue, 'b.-') + ax.loglog(rx.times, survey.dtrue, 'b.-') ax.loglog(rx.times, survey.dobs, 'r.-') ax.legend(('Noisefree', '$d^{obs}$'), fontsize = 16) ax.set_xlabel('Time (s)', fontsize = 14) @@ -76,6 +71,7 @@ def run(plotIt=True): reg = Regularization.Tikhonov(regMesh) opt = Optimization.InexactGaussNewton(maxIter = 5) invProb = InvProblem.BaseInvProblem(dmisfit, reg, opt) + # Create an inversion object beta = Directives.BetaSchedule(coolingFactor=5, coolingRate=2) betaest = Directives.BetaEstimate_ByEig(beta0_ratio=1e0) From c24416ea126dc046c26fa5b4afbecb207cc6778b Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Thu, 28 Jan 2016 13:20:48 -0800 Subject: [PATCH 56/77] use the std and eps from survey in definition of data misfit if defined (and demonstrate it with the TDEM example) --- SimPEG/DataMisfit.py | 28 ++++++++++--------------- SimPEG/Examples/EM_TDEM_1D_Inversion.py | 3 +++ SimPEG/Survey.py | 1 + 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/SimPEG/DataMisfit.py b/SimPEG/DataMisfit.py index c1203d47..425fe4ce 100644 --- a/SimPEG/DataMisfit.py +++ b/SimPEG/DataMisfit.py @@ -59,20 +59,6 @@ class BaseDataMisfit(object): """ raise NotImplementedError('This method should be overwritten.') - # TODO: implement target misfit as a property, or possibly as an inversion directive. - - # def target(self, forward): - # """target(forward) - - # Target for data misfit. By default this is the number of data, - # which satisfies the Discrepancy Principle. - - # :rtype: float - # :return: data misfit target - - # """ - # prob, survey = self.splitForward(forward) - # return survey.nD class l2_DataMisfit(BaseDataMisfit): @@ -103,10 +89,18 @@ class l2_DataMisfit(BaseDataMisfit): """ if getattr(self, '_Wd', None) is None: - print 'SimPEG.l2_DataMisfit is creating default weightings for Wd.' + survey = self.survey - eps = np.linalg.norm(Utils.mkvc(survey.dobs),2)*1e-5 - self._Wd = Utils.sdiag(1/(abs(survey.dobs)*survey.std+eps)) + + if getattr(survey,'std', None) is None: + print 'SimPEG.DataMisfit.l2_DataMisfit assigning default std of 5%' + survey.std = 0.05 + + if getattr(survey, 'eps', None) is None: + print 'SimPEG.DataMisfit.l2_DataMisfit assigning default eps of 1e-5 * ||dobs||' + survey.eps = np.linalg.norm(Utils.mkvc(survey.dobs),2)*1e-5 + + self._Wd = Utils.sdiag(1/(abs(survey.dobs)*survey.std+survey.eps)) return self._Wd @Wd.setter diff --git a/SimPEG/Examples/EM_TDEM_1D_Inversion.py b/SimPEG/Examples/EM_TDEM_1D_Inversion.py index 86198064..f217912d 100644 --- a/SimPEG/Examples/EM_TDEM_1D_Inversion.py +++ b/SimPEG/Examples/EM_TDEM_1D_Inversion.py @@ -53,7 +53,10 @@ def run(plotIt=True): # create observed data std = 0.05 + survey.dobs = survey.makeSyntheticData(mtrue,std) + survey.std = std + survey.eps = 1e-5*np.linalg.norm(survey.dobs) if plotIt: import matplotlib.pyplot as plt diff --git a/SimPEG/Survey.py b/SimPEG/Survey.py index 88355df1..47a88ae2 100644 --- a/SimPEG/Survey.py +++ b/SimPEG/Survey.py @@ -205,6 +205,7 @@ class BaseSurvey(object): __metaclass__ = Utils.SimPEGMetaClass std = None #: Estimated Standard Deviations + eps = None #: Estimated Noise Floor dobs = None #: Observed data dtrue = None #: True data, if data is synthetic mtrue = None #: True model, if data is synthetic From 09161ff68e657e95a66dc283914f309ad903eac7 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Thu, 28 Jan 2016 13:58:32 -0800 Subject: [PATCH 57/77] Hopefully get a better error message on travis. --- tests/mesh/test_MeshIO.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/mesh/test_MeshIO.py b/tests/mesh/test_MeshIO.py index 43c0e33e..52e5740d 100644 --- a/tests/mesh/test_MeshIO.py +++ b/tests/mesh/test_MeshIO.py @@ -35,12 +35,7 @@ class TestOcTreeIO(unittest.TestCase): def test_VTUfiles(self): mesh = self.mesh vec = np.arange(mesh.nC) - try: - simpeg.Utils.meshutils.writeVTUFile('temp.vtu',mesh,{'arange':vec}) - run = True - except: - run = False - assert run + simpeg.Utils.meshutils.writeVTUFile('temp.vtu',mesh,{'arange':vec}) print 'Writing of VTU files is working' os.remove('temp.vtu') From 4bcc9e08502c99a732646b215adff5f1f54d61ec Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Thu, 28 Jan 2016 14:00:47 -0800 Subject: [PATCH 58/77] - name cleanup in Jvec and Jtvec (use u instead of f to be consistent with the rest of SimPEG) - example 1D inversion for FDEM --- SimPEG/EM/FDEM/FDEM.py | 30 +++--- SimPEG/Examples/EM_FDEM_1D_Inversion.py | 116 ++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 SimPEG/Examples/EM_FDEM_1D_Inversion.py diff --git a/SimPEG/EM/FDEM/FDEM.py b/SimPEG/EM/FDEM/FDEM.py index f2167fd8..cb6adc12 100644 --- a/SimPEG/EM/FDEM/FDEM.py +++ b/SimPEG/EM/FDEM/FDEM.py @@ -53,13 +53,13 @@ class BaseFDEMProblem(BaseEMProblem): return F - def Jvec(self, m, v, f=None): + def Jvec(self, m, v, u=None): """ Sensitivity times a vector """ - if f is None: - f = self.fields(m) + if u is None: + u = self.fields(m) self.curModel = m @@ -71,33 +71,33 @@ class BaseFDEMProblem(BaseEMProblem): for src in self.survey.getSrcByFreq(freq): ftype = self._fieldType + 'Solution' - u_src = f[src, ftype] + u_src = u[src, ftype] dA_dm = self.getADeriv_m(freq, u_src, v) dRHS_dm = self.getRHSDeriv_m(freq, src, v) du_dm = Ainv * ( - dA_dm + dRHS_dm ) for rx in src.rxList: - df_duFun = getattr(f, '_%sDeriv_u'%rx.projField, None) + df_duFun = getattr(u, '_%sDeriv_u'%rx.projField, None) df_dudu_dm = df_duFun(src, du_dm, adjoint=False) - df_dmFun = getattr(f, '_%sDeriv_m'%rx.projField, None) + df_dmFun = getattr(u, '_%sDeriv_m'%rx.projField, None) df_dm = df_dmFun(src, v, adjoint=False) Df_Dm = np.array(df_dudu_dm + df_dm,dtype=complex) - P = lambda v: rx.projectFieldsDeriv(src, self.mesh, f, v) # wrt u, also have wrt m + P = lambda v: rx.projectFieldsDeriv(src, self.mesh, u, v) # wrt u, also have wrt m Jv[src, rx] = P(Df_Dm) return Utils.mkvc(Jv) - def Jtvec(self, m, v, f=None): + def Jtvec(self, m, v, u=None): """ Sensitivity transpose times a vector """ - if f is None: - f = self.fields(m) + if u is None: + u = self.fields(m) self.curModel = m @@ -113,12 +113,12 @@ class BaseFDEMProblem(BaseEMProblem): for src in self.survey.getSrcByFreq(freq): ftype = self._fieldType + 'Solution' - u_src = f[src, ftype] + u_src = u[src, ftype] for rx in src.rxList: - PTv = rx.projectFieldsDeriv(src, self.mesh, f, v[src, rx], adjoint=True) # wrt u, need possibility wrt m + PTv = rx.projectFieldsDeriv(src, self.mesh, u, v[src, rx], adjoint=True) # wrt u, need possibility wrt m - df_duTFun = getattr(f, '_%sDeriv_u'%rx.projField, None) + df_duTFun = getattr(u, '_%sDeriv_u'%rx.projField, None) df_duT = df_duTFun(src, PTv, adjoint=True) ATinvdf_duT = ATinv * df_duT @@ -127,7 +127,7 @@ class BaseFDEMProblem(BaseEMProblem): dRHS_dmT = self.getRHSDeriv_m(freq,src, ATinvdf_duT, adjoint=True) du_dmT = -dA_dmT + dRHS_dmT - df_dmFun = getattr(f, '_%sDeriv_m'%rx.projField, None) + df_dmFun = getattr(u, '_%sDeriv_m'%rx.projField, None) dfT_dm = df_dmFun(src, PTv, adjoint=True) du_dmT += dfT_dm @@ -140,7 +140,7 @@ class BaseFDEMProblem(BaseEMProblem): else: raise Exception('Must be real or imag') - return Jtv + return Utils.mkvc(Jtv) def getSourceTerm(self, freq): """ diff --git a/SimPEG/Examples/EM_FDEM_1D_Inversion.py b/SimPEG/Examples/EM_FDEM_1D_Inversion.py new file mode 100644 index 00000000..a6478016 --- /dev/null +++ b/SimPEG/Examples/EM_FDEM_1D_Inversion.py @@ -0,0 +1,116 @@ +from SimPEG import * +import SimPEG.EM as EM +from scipy.constants import mu_0 + + +def run(plotIt=True): + """ + EM: FDEM: 1D: Inversion + ======================= + + Here we will create and run a FDEM 1D inversion. + + """ + + cs, ncx, ncz, npad = 5., 25, 15, 15 + hx = [(cs,ncx), (cs,npad,1.3)] + hz = [(cs,npad,-1.3), (cs,ncz), (cs,npad,1.3)] + mesh = Mesh.CylMesh([hx,1,hz], '00C') + + layerz = -100. + + active = mesh.vectorCCz<0. + layer = (mesh.vectorCCz<0.) & (mesh.vectorCCz>=layerz) + actMap = Maps.ActiveCells(mesh, active, np.log(1e-8), nC=mesh.nCz) + mapping = Maps.ExpMap(mesh) * Maps.Vertical1DMap(mesh) * actMap + sig_half = 2e-2 + sig_air = 1e-8 + sig_layer = 1e-2 + sigma = np.ones(mesh.nCz)*sig_air + sigma[active] = sig_half + sigma[layer] = sig_layer + mtrue = np.log(sigma[active]) + + if plotIt: + import matplotlib.pyplot as plt + fig, ax = plt.subplots(1,1, figsize = (3, 6)) + plt.semilogx(sigma[active], mesh.vectorCCz[active]) + ax.set_ylim(-500, 0) + ax.set_xlim(1e-3, 1e-1) + ax.set_xlabel('Conductivity (S/m)', fontsize = 14) + ax.set_ylabel('Depth (m)', fontsize = 14) + ax.grid(color='k', alpha=0.5, linestyle='dashed', linewidth=0.5) + + + rxOffset=10. + bzi = EM.FDEM.Rx(np.array([[rxOffset, 0., 1e-3]]), 'bzi') + + freqs = np.logspace(1,3,10) + srcLoc = np.array([0., 0., 10.]) + + srcList = [] + [srcList.append(EM.FDEM.Src.MagDipole([bzi],freq, srcLoc,orientation='Z')) for freq in freqs] + + survey = EM.FDEM.Survey(srcList) + prb = EM.FDEM.Problem_b(mesh, mapping=mapping) + + try: + from pymatsolver import MumpsSolver + prb.Solver = MumpsSolver + except ImportError, e: + prb.Solver = SolverLU + + prb.pair(survey) + + std = 0.05 + survey.makeSyntheticData(mtrue, std) + + survey.std = std + survey.eps = np.linalg.norm(survey.dtrue)*1e-5 + + if plotIt: + import matplotlib.pyplot as plt + fig, ax = plt.subplots(1,1, figsize = (6, 6)) + ax.semilogx(freqs,survey.dtrue[:freqs.size], 'b.-') + ax.semilogx(freqs,survey.dobs[:freqs.size], 'r.-') + ax.legend(('Noisefree', '$d^{obs}$'), fontsize = 16) + ax.set_xlabel('Time (s)', fontsize = 14) + ax.set_ylabel('$B_z$ (T)', fontsize = 16) + ax.set_xlabel('Time (s)', fontsize = 14) + ax.grid(color='k', alpha=0.5, linestyle='dashed', linewidth=0.5) + + dmisfit = DataMisfit.l2_DataMisfit(survey) + regMesh = Mesh.TensorMesh([mesh.hz[mapping.maps[-1].indActive]]) + reg = Regularization.Tikhonov(regMesh) + opt = Optimization.InexactGaussNewton(maxIter = 6) + invProb = InvProblem.BaseInvProblem(dmisfit, reg, opt) + + # Create an inversion object + beta = Directives.BetaSchedule(coolingFactor=5, coolingRate=2) + betaest = Directives.BetaEstimate_ByEig(beta0_ratio=1e0) + inv = Inversion.BaseInversion(invProb, directiveList=[beta,betaest]) + m0 = np.log(np.ones(mtrue.size)*sig_half) + reg.alpha_s = 1e-3 + reg.alpha_x = 1. + prb.counter = opt.counter = Utils.Counter() + opt.LSshorten = 0.5 + opt.remember('xc') + + mopt = inv.run(m0) + + if plotIt: + import matplotlib.pyplot as plt + fig, ax = plt.subplots(1,1, figsize = (3, 6)) + plt.semilogx(sigma[active], mesh.vectorCCz[active]) + plt.semilogx(np.exp(mopt), mesh.vectorCCz[active]) + ax.set_ylim(-500, 0) + ax.set_xlim(1e-3, 1e-1) + ax.set_xlabel('Conductivity (S/m)', fontsize = 14) + ax.set_ylabel('Depth (m)', fontsize = 14) + ax.grid(color='k', alpha=0.5, linestyle='dashed', linewidth=0.5) + plt.legend(['$\sigma_{true}$', '$\sigma_{pred}$'],loc='best') + plt.show() + + +if __name__ == '__main__': + run() From 860bd5638ab056f90c3b2a85c9165f4625e667af Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Thu, 28 Jan 2016 14:19:29 -0800 Subject: [PATCH 59/77] Add VTK to the travis dependencies. I am not adding this to the requirements.txt file. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c2c672bf..de145ecb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ before_install: # Install packages install: - - conda install --yes pip python=$TRAVIS_PYTHON_VERSION numpy scipy matplotlib cython ipython + - conda install --yes pip python=$TRAVIS_PYTHON_VERSION numpy scipy matplotlib cython ipython vtk - pip install nose-cov python-coveralls # - pip install -r requirements.txt - python setup.py install From 1bcb572c459c215b451bbff9127af26d7f8be9f0 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Thu, 28 Jan 2016 14:29:45 -0800 Subject: [PATCH 60/77] Remove ifmain from TreeMesh --- .gitignore | 1 - SimPEG/Mesh/TreeMesh.py | 125 ---------------------------------------- 2 files changed, 126 deletions(-) diff --git a/.gitignore b/.gitignore index 6a826230..fc798cea 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,3 @@ nosetests.xml *.sublime-workspace docs/_build/ Makefile -diff.temp diff --git a/SimPEG/Mesh/TreeMesh.py b/SimPEG/Mesh/TreeMesh.py index a4c4e845..0205e972 100644 --- a/SimPEG/Mesh/TreeMesh.py +++ b/SimPEG/Mesh/TreeMesh.py @@ -2335,128 +2335,3 @@ class NotBalancedException(TreeException): pass class CellLookUpException(TreeException): pass - -if __name__ == '__main__': - - - import matplotlib.pyplot as plt - import matplotlib - from mpl_toolkits.mplot3d import Axes3D - import matplotlib.colors as colors - import matplotlib.cm as cmx - - def topo(x): - return np.sin(x*(2.*np.pi))*0.3 + 0.5 - - def function(cell): - r = cell.center - np.array([0.5]*len(cell.center)) - dist = np.sqrt(r.dot(r)) - # dist2 = np.abs(cell.center[-1] - topo(cell.center[0])) - - # dist = min([dist1,dist2]) - # if dist < 0.05: - # return 5 - if dist < 0.1: - return 5 - if dist < 0.2: - return 4 - if dist < 0.4: - return 3 - return 2 - - # T = TreeMesh([[(1,128)],[(1,128)],[(1,128)]],levels=7) - # T = TreeMesh([128,128,128]) - # T = TreeMesh([64,64],levels=6) - T = TreeMesh([8,8]) - # T = TreeMesh([[(1,128)],[(1,128)]],levels=7) - # T.refine(lambda xc:2, balance=False) - # T._index([0,0,0]) - # T._pointer(0) - - - # tic = time.time() - T.refine(function)#, balance=False) - # print time.time() - tic - # print T.nC - # T.plotSlice(np.log(T.vol))#np.random.rand(T.nC)) - T.plotGrid() - # print [c for c in T] - c = T[0] - plt.plot(c.center[0],c.center[1],'r.') - nodes = c.nodes - for n in nodes: - _ = T._gridN[n,:] - plt.plot(_[0],_[1],'gs') - plt.show() - blah - - # T.plotImage(np.arange(len(T.vol)),showIt=True) - - # print T.getFaceInnerProduct() - # print T.gridFz - - - # T._refineCell([8,0,1]) - # T._refineCell([8,0,2]) - # T._refineCell([12,0,2]) - # T._refineCell([8,4,2]) - # T._refineCell([6,0,3]) - # T._refineCell([8,8,1]) - # T._refineCell([0,0,0,1]) - # T.__dirty__ = True - - - # print T.gridFx.shape[0], T.nFx - - - - ax = plt.subplot(211) - ax.spy(T.edgeCurl) - - # print Mesh.TensorMesh([2,2,2]).edgeCurl.todense() - # print T.edgeCurl.todense() - # print Mesh.TensorMesh([2,2,2]).edgeCurl.todense() - T.edgeCurl.todense() - # print T.gridEy - Mesh.TensorMesh([2,2,2]).gridEy - - # print T.edge - # T.plotGrid(ax=ax) - - # R = deflationMatrix(T._facesX, T._hangingFx, T._fx2i) - # print R - - ax = plt.subplot(212)#, projection='3d') - ax.spy(Mesh.TensorMesh([2,2,2]).edgeCurl) - - # ax = plt.subplot(313) - # ax.spy(T.faceDiv[:,:T.nFx] * R) - - - # T.balance() - # T.plotGrid(ax=ax) - - # cx = T._getNextCell([0,0,1],direction=0,positive=True) - # print cx - # # print [T._asPointer(_) for _ in cx] - # cx = T._getNextCell([8,0,3],direction=0,positive=False) - # print T._asPointer(cx) - # cx = T._getNextCell([8,8,1],direction=1,positive=False) - # print cx, #[T._asPointer(_) for _ in cx] - # cm = T._getNextCell([64,80,4],direction=0,positive=False) - # cy = T._getNextCell([64,80,4],direction=1,positive=True) - # cp = T._getNextCell([64,80,4],direction=1,positive=False) - - # ax.plot( T._cellN([4,0,1])[0],T._cellN([4,0,1])[1], 'yd') - # ax.plot( T._cellN(cx)[0],T._cellN(cx)[1], 'ys') - # ax.plot( T._cellN(cm)[0],T._cellN(cm)[1], 'ys') - # ax.plot( T._cellN(cy)[0],T._cellN(cy)[1], 'ys') - # ax.plot( T._cellN(cp[0])[0],T._cellN(cp[0])[1], 'ys') - # ax.plot( T._cellN(cp[1])[0],T._cellN(cp[1])[1], 'ys') - - - - - - # print T.nN - - plt.show() - From 43c49d5f157dc7ea803fd6a420a0c38cb4449c00 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Thu, 28 Jan 2016 17:53:10 -0800 Subject: [PATCH 61/77] Address mesh IO #212 --- SimPEG/Mesh/MeshIO.py | 416 ++++++++++++++ SimPEG/Mesh/TensorMesh.py | 1117 +++++++++++++++++++------------------ SimPEG/Mesh/TreeMesh.py | 3 +- SimPEG/Utils/meshutils.py | 402 ------------- tests/mesh/test_MeshIO.py | 67 ++- 5 files changed, 1038 insertions(+), 967 deletions(-) create mode 100644 SimPEG/Mesh/MeshIO.py diff --git a/SimPEG/Mesh/MeshIO.py b/SimPEG/Mesh/MeshIO.py new file mode 100644 index 00000000..7501a66f --- /dev/null +++ b/SimPEG/Mesh/MeshIO.py @@ -0,0 +1,416 @@ +import numpy as np, os +from SimPEG import Utils + +class TensorMeshIO(object): + + @classmethod + def readUBC(TensorMesh, fileName): + """ + Read UBC GIF 3DTensor mesh and generate 3D Tensor mesh in simpegTD + + Input: + :param fileName, path to the UBC GIF mesh file + + Output: + :param SimPEG TensorMesh object + """ + + # Interal function to read cell size lines for the UBC mesh files. + def readCellLine(line): + for seg in line.split(): + if '*' in seg: + st = seg + sp = seg.split('*') + re = np.array(sp[0],dtype=int)*(' ' + sp[1]) + line = line.replace(st,re.strip()) + return np.array(line.split(),dtype=float) + + # Read the file as line strings, remove lines with comment = ! + msh = np.genfromtxt(fileName,delimiter='\n',dtype=np.str,comments='!') + + # Fist line is the size of the model + sizeM = np.array(msh[0].split(),dtype=float) + # Second line is the South-West-Top corner coordinates. + x0 = np.array(msh[1].split(),dtype=float) + # Read the cell sizes + h1 = readCellLine(msh[2]) + h2 = readCellLine(msh[3]) + h3temp = readCellLine(msh[4]) + h3 = h3temp[::-1] # Invert the indexing of the vector to start from the bottom. + # Adjust the reference point to the bottom south west corner + x0[2] = x0[2] - np.sum(h3) + # Make the mesh + tensMsh = TensorMesh([h1,h2,h3],x0) + return tensMsh + + @classmethod + def readVTK(TensorMesh, fileName): + """ + Read VTK Rectilinear (vtr xml file) and return SimPEG Tensor mesh and model + + Input: + :param vtrFileName, path to the vtr model file to write to + + Output: + :return SimPEG TensorMesh object + :return SimPEG model dictionary + + """ + # Import + from vtk import vtkXMLRectilinearGridReader as vtrFileReader + from vtk.util.numpy_support import vtk_to_numpy + + # Read the file + vtrReader = vtrFileReader() + vtrReader.SetFileName(fileName) + vtrReader.Update() + vtrGrid = vtrReader.GetOutput() + # Sort information + hx = np.abs(np.diff(vtk_to_numpy(vtrGrid.GetXCoordinates()))) + xR = vtk_to_numpy(vtrGrid.GetXCoordinates())[0] + hy = np.abs(np.diff(vtk_to_numpy(vtrGrid.GetYCoordinates()))) + yR = vtk_to_numpy(vtrGrid.GetYCoordinates())[0] + zD = np.diff(vtk_to_numpy(vtrGrid.GetZCoordinates())) + # Check the direction of hz + if np.all(zD < 0): + hz = np.abs(zD[::-1]) + zR = vtk_to_numpy(vtrGrid.GetZCoordinates())[-1] + else: + hz = np.abs(zD) + zR = vtk_to_numpy(vtrGrid.GetZCoordinates())[0] + x0 = np.array([xR,yR,zR]) + + # Make the SimPEG object + tensMsh = TensorMesh([hx,hy,hz],x0) + + # Grap the models + models = {} + for i in np.arange(vtrGrid.GetCellData().GetNumberOfArrays()): + modelName = vtrGrid.GetCellData().GetArrayName(i) + if np.all(zD < 0): + modFlip = vtk_to_numpy(vtrGrid.GetCellData().GetArray(i)) + tM = tensMsh.r(modFlip,'CC','CC','M') + modArr = tensMsh.r(tM[:,:,::-1],'CC','CC','V') + else: + modArr = vtk_to_numpy(vtrGrid.GetCellData().GetArray(i)) + models[modelName] = modArr + + # Return the data + return tensMsh, models + + def writeVTK(mesh, fileName, models=None): + """ + Makes and saves a VTK rectilinear file (vtr) for a simpeg Tensor mesh and model. + + Input: + :param str, path to the output vtk file + :param mesh, SimPEG TensorMesh object - mesh to be transfer to VTK + :param models, dictionary of numpy.array - Name('s) and array('s). Match number of cells + + """ + # Import + from vtk import vtkRectilinearGrid as rectGrid, vtkXMLRectilinearGridWriter as rectWriter, VTK_VERSION + from vtk.util.numpy_support import numpy_to_vtk + + # Deal with dimensionalities + if mesh.dim >= 1: + vX = mesh.vectorNx + xD = mesh.nNx + yD,zD = 1,1 + vY, vZ = np.array([0,0]) + if mesh.dim >= 2: + vY = mesh.vectorNy + yD = mesh.nNy + if mesh.dim == 3: + vZ = mesh.vectorNz + zD = mesh.nNz + # Use rectilinear VTK grid. + # Assign the spatial information. + vtkObj = rectGrid() + vtkObj.SetDimensions(xD,yD,zD) + vtkObj.SetXCoordinates(numpy_to_vtk(vX,deep=1)) + vtkObj.SetYCoordinates(numpy_to_vtk(vY,deep=1)) + vtkObj.SetZCoordinates(numpy_to_vtk(vZ,deep=1)) + + # Assign the model('s) to the object + if models is not None: + for item in models.iteritems(): + # Convert numpy array + vtkDoubleArr = numpy_to_vtk(item[1],deep=1) + vtkDoubleArr.SetName(item[0]) + vtkObj.GetCellData().AddArray(vtkDoubleArr) + # Set the active scalar + vtkObj.GetCellData().SetActiveScalars(models.keys()[0]) + # vtkObj.Update() + + # Check the extension of the fileName + ext = os.path.splitext(fileName)[1] + if ext is '': + fileName = fileName + '.vtr' + elif ext not in '.vtr': + raise IOError('{:s} is an incorrect extension, has to be .vtr') + # Write the file. + vtrWriteFilter = rectWriter() + if float(VTK_VERSION.split('.')[0]) >=6: + vtrWriteFilter.SetInputData(vtkObj) + else: + vtuWriteFilter.SetInput(vtuObj) + vtrWriteFilter.SetFileName(fileName) + vtrWriteFilter.Update() + + + def readModelUBC(mesh, fileName): + """ + Read UBC 3DTensor mesh model and generate 3D Tensor mesh model in simpeg + + Input: + :param fileName, path to the UBC GIF mesh file to read + :param mesh, TensorMesh object, mesh that coresponds to the model + + Output: + :return numpy array, model with TensorMesh ordered + """ + f = open(fileName, 'r') + model = np.array(map(float, f.readlines())) + f.close() + model = np.reshape(model, (mesh.nCz, mesh.nCx, mesh.nCy), order = 'F') + model = model[::-1,:,:] + model = np.transpose(model, (1, 2, 0)) + model = Utils.mkvc(model) + return model + + def writeModelUBC(mesh, fileName, model): + """ + Writes a model associated with a SimPEG TensorMesh + to a UBC-GIF format model file. + + :param str fileName: File to write to + :param simpeg.Mesh.TensorMesh mesh: The mesh + :param numpy.ndarray model: The model + """ + + # Reshape model to a matrix + modelMat = mesh.r(model,'CC','CC','M') + # Transpose the axes + modelMatT = modelMat.transpose((2,0,1)) + # Flip z to positive down + modelMatTR = Utils.mkvc(modelMatT[::-1,:,:]) + + np.savetxt(fileName, modelMatTR.ravel()) + + def writeUBC(mesh, fileName, models=None): + """ + Writes a SimPEG TensorMesh to a UBC-GIF format mesh file. + + :param str fileName: File to write to + :param simpeg.Mesh.TensorMesh mesh: The mesh + + """ + assert mesh.dim == 3 + s = '' + s += '%i %i %i\n' %tuple(mesh.vnC) + origin = mesh.x0 + np.array([0,0,mesh.hz.sum()]) # Have to it in the same operation or use mesh.x0.copy(), otherwise the mesh.x0 is updated. + origin.dtype = float + + s += '%.2f %.2f %.2f\n' %tuple(origin) + s += ('%.2f '*mesh.nCx+'\n')%tuple(mesh.hx) + s += ('%.2f '*mesh.nCy+'\n')%tuple(mesh.hy) + s += ('%.2f '*mesh.nCz+'\n')%tuple(mesh.hz[::-1]) + f = open(fileName, 'w') + f.write(s) + f.close() + + if models is None: return + assert type(models) is dict, 'models must be a dict' + for key in models: + assert type(key) is str, 'The dict key is a file name' + mesh.writeModelUBC(key, models[key]) + +class TreeMeshIO(object): + + def writeUBC(mesh, fileName, models=None): + """ + Write UBC ocTree mesh and model files from a simpeg ocTree mesh and model. + + :param str fileName: File to write to + :param simpeg.Mesh.TreeMesh mesh: The mesh + :param dictionary models: The models in a dictionary, where the keys is the name of the of the model file + """ + + # Calculate information to write in the file. + # Number of cells in the underlying mesh + nCunderMesh = np.array([h.size for h in mesh.h],dtype=np.int64) + # The top-south-west most corner of the mesh + tswCorn = mesh.x0 + np.array([0,0,np.sum(mesh.h[2])]) + # Smallest cell size + smallCell = np.array([h.min() for h in mesh.h]) + # Number of cells + nrCells = mesh.nC + + ## Extract iformation about the cells. + # cell pointers + cellPointers = np.array([c._pointer for c in mesh]) + # cell with + cellW = np.array([ mesh._levelWidth(i) for i in cellPointers[:,-1] ]) + # Need to shift the pointers to work with UBC indexing + # UBC Octree indexes always the top-left-close (top-south-west) corner first and orders the cells in z(top-down),x,y vs x,y,z(bottom-up). + # Shift index up by 1 + ubcCellPt = cellPointers[:,0:-1].copy() + np.array([1.,1.,1.]) + # Need reindex the z index to be from the top-left-close corner and to be from the global top. + ubcCellPt[:,2] = ( nCunderMesh[-1] + 2) - (ubcCellPt[:,2] + cellW) + + # Reorder the ubcCellPt + ubcReorder = np.argsort(ubcCellPt.view(','.join(3*['float'])),axis=0,order=['f2','f1','f0'])[:,0] + # Make a array with the pointers and the withs, that are order in the ubc ordering + indArr = np.concatenate((ubcCellPt[ubcReorder,:],cellW[ubcReorder].reshape((-1,1)) ),axis=1) + + ## Write the UBC octree mesh file + with open(fileName,'w') as mshOut: + mshOut.write('{:.0f} {:.0f} {:.0f}\n'.format(nCunderMesh[0],nCunderMesh[1],nCunderMesh[2])) + mshOut.write('{:.4f} {:.4f} {:.4f}\n'.format(tswCorn[0],tswCorn[1],tswCorn[2])) + mshOut.write('{:.3f} {:.3f} {:.3f}\n'.format(smallCell[0],smallCell[1],smallCell[2])) + mshOut.write('{:.0f} \n'.format(nrCells)) + np.savetxt(mshOut,indArr,fmt='%i') + + ## Print the models + # Assign the model('s) to the object + if models is not None: + # indUBCvector = np.argsort(cX0[np.argsort(np.concatenate((cX0[:,0:2],cX0[:,2:3].max() - cX0[:,2:3]),axis=1).view(','.join(3*['float'])),axis=0,order=('f2','f1','f0'))[:,0]].view(','.join(3*['float'])),axis=0,order=('f2','f1','f0'))[:,0] + for item in models.iteritems(): + # Save the data + np.savetxt(item[0],item[1][ubcReorder],fmt='%3.5e') + + @classmethod + def readUBC(TreeMesh, meshFile): + """ + Read UBC 3D OcTree mesh and/or modelFiles + + Input: + :param str meshFile: path to the UBC GIF OcTree mesh file to read + + Output: + :return SimPEG.Mesh.TreeMesh mesh: The octree mesh + :return list of ndarray's: models as a list of numpy array's + """ + + ## Read the file lines + fileLines = np.genfromtxt(meshFile,dtype=str,delimiter='\n') + # Extract the data + nCunderMesh = np.array(fileLines[0].split(),dtype=float) + # I think this is the case? + if np.unique(nCunderMesh).size >1: + raise Exception('SimPEG TreeMeshes have the same number of cell in all directions') + tswCorn = np.array(fileLines[1].split(),dtype=float) + smallCell = np.array(fileLines[2].split(),dtype=float) + nrCells = np.array(fileLines[3].split(),dtype=float) + # Read the index array + indArr = np.genfromtxt(fileLines[4::],dtype=np.int) + + ## Calculate simpeg parameters + h1,h2,h3 = [np.ones(nr)*sz for nr,sz in zip(nCunderMesh,smallCell)] + x0 = tswCorn - np.array([0,0,np.sum(h3)]) + # Need to convert the index array to a points list that complies with SimPEG TreeMesh. + # Shift to start at 0 + simpegCellPt = indArr[:,0:-1].copy() + simpegCellPt[:,2] = ( nCunderMesh[-1] + 2) - (simpegCellPt[:,2] + indArr[:,3]) + # Need reindex the z index to be from the bottom-left-close corner and to be from the global bottom. + simpegCellPt = simpegCellPt - np.array([1.,1.,1.]) + + # Calculate the cell level + simpegLevel = np.log2(np.min(nCunderMesh)) - np.log2(indArr[:,3]) + # Make a pointer matrix + simpegPointers = np.concatenate((simpegCellPt,simpegLevel.reshape((-1,1))),axis=1) + + ## Make the tree mesh + mesh = TreeMesh([h1,h2,h3],x0) + mesh._cells = set([mesh._index(p) for p in simpegPointers.tolist()]) + + # Figure out the reordering + mesh._simpegReorderUBC = np.argsort(np.array([mesh._index(i) for i in simpegPointers.tolist()])) + # mesh._simpegReorderUBC = np.argsort((np.array([[1,1,1,-1]])*simpegPointers).view(','.join(4*['float'])),axis=0,order=['f3','f2','f1','f0'])[:,0] + + return mesh + + + def readModelUBC(mesh, fileName): + """ + Read UBC OcTree model and get vector + + Input: + :param fileName, path to the UBC GIF model file to read + + Output: + :return numpy array, OcTree model + """ + + if type(fileName) is list: + out = {} + for f in fileName: + out[f] = mesh.readModelUBC(f) + return out + + assert hasattr(mesh, '_simpegReorderUBC'), 'The file must have been loaded from a UBC format.' + assert mesh.dim == 3 + + modList = [] + modArr = np.loadtxt(fileName) + if len(modArr.shape) == 1: + modList.append(modArr[mesh._simpegReorderUBC]) + else: + modList.append(modArr[mesh._simpegReorderUBC,:]) + return modList + + def writeVTK(mesh, fileName, models=None): + """ + Function to write a VTU file from a SimPEG TreeMesh and model. + """ + import vtk + from vtk import vtkXMLUnstructuredGridWriter as Writer, VTK_VERSION + from vtk.util.numpy_support import numpy_to_vtk, numpy_to_vtkIdTypeArray + + if str(type(mesh)).split()[-1][1:-2] not in 'SimPEG.Mesh.TreeMesh.TreeMesh': + raise IOError('mesh is not a SimPEG TreeMesh.') + + # Make the data parts for the vtu object + # Points + mesh.number() + ptsMat = mesh._gridN + mesh.x0 + + vtkPts = vtk.vtkPoints() + vtkPts.SetData(numpy_to_vtk(ptsMat,deep=True)) + # Cells + cellConn = np.array([c.nodes for c in mesh],dtype=np.int64) + + cellsMat = np.concatenate((np.ones((cellConn.shape[0],1),dtype=np.int64)*cellConn.shape[1],cellConn),axis=1).ravel() + cellsArr = vtk.vtkCellArray() + cellsArr.SetNumberOfCells(cellConn.shape[0]) + cellsArr.SetCells(cellConn.shape[0],numpy_to_vtkIdTypeArray(cellsMat,deep=True)) + + # Make the object + vtuObj = vtk.vtkUnstructuredGrid() + vtuObj.SetPoints(vtkPts) + vtuObj.SetCells(vtk.VTK_VOXEL,cellsArr) + # Add the level of refinement as a cell array + cellSides = np.array([np.array(vtuObj.GetCell(i).GetBounds()).reshape((3,2)).dot(np.array([-1, 1])) for i in np.arange(vtuObj.GetNumberOfCells())]) + uniqueLevel, indLevel = np.unique(np.prod(cellSides,axis=1),return_inverse=True) + refineLevelArr = numpy_to_vtk(indLevel.max() - indLevel,deep=1) + refineLevelArr.SetName('octreeLevel') + vtuObj.GetCellData().AddArray(refineLevelArr) + # Assign the model('s) to the object + if models is not None: + for item in models.iteritems(): + # Convert numpy array + vtkDoubleArr = numpy_to_vtk(item[1],deep=1) + vtkDoubleArr.SetName(item[0]) + vtuObj.GetCellData().AddArray(vtkDoubleArr) + + # Make the writer + vtuWriteFilter = Writer() + if float(VTK_VERSION.split('.')[0]) >=6: + vtuWriteFilter.SetInputData(vtuObj) + else: + vtuWriteFilter.SetInput(vtuObj) + vtuWriteFilter.SetFileName(fileName) + # Write the file + vtuWriteFilter.Update() + diff --git a/SimPEG/Mesh/TensorMesh.py b/SimPEG/Mesh/TensorMesh.py index 5ea2d86a..508f015c 100644 --- a/SimPEG/Mesh/TensorMesh.py +++ b/SimPEG/Mesh/TensorMesh.py @@ -1,558 +1,559 @@ -from SimPEG import Utils, np, sp -from BaseMesh import BaseMesh, BaseRectangularMesh -from View import TensorView -from DiffOperators import DiffOperators -from InnerProducts import InnerProducts - -class BaseTensorMesh(BaseMesh): - - __metaclass__ = Utils.SimPEGMetaClass - - _meshType = 'BASETENSOR' - - _unitDimensions = [1, 1, 1] - - def __init__(self, h_in, x0_in=None): - assert type(h_in) in [list, tuple], 'h_in must be a list' - assert len(h_in) in [1,2,3], 'h_in must be of dimension 1, 2, or 3' - h = range(len(h_in)) - for i, h_i in enumerate(h_in): - if Utils.isScalar(h_i) and type(h_i) is not np.ndarray: - # This gives you something over the unit cube. - h_i = self._unitDimensions[i] * np.ones(int(h_i))/int(h_i) - elif type(h_i) is list: - h_i = Utils.meshTensor(h_i) - assert isinstance(h_i, np.ndarray), ("h[%i] is not a numpy array." % i) - assert len(h_i.shape) == 1, ("h[%i] must be a 1D numpy array." % i) - h[i] = h_i[:] # make a copy. - - x0 = np.zeros(len(h)) - if x0_in is not None: - assert len(h) == len(x0_in), "Dimension mismatch. x0 != len(h)" - for i in range(len(h)): - x_i, h_i = x0_in[i], h[i] - if Utils.isScalar(x_i): - x0[i] = x_i - elif x_i == '0': - x0[i] = 0.0 - elif x_i == 'C': - x0[i] = -h_i.sum()*0.5 - elif x_i == 'N': - x0[i] = -h_i.sum() - else: - raise Exception("x0[%i] must be a scalar or '0' to be zero, 'C' to center, or 'N' to be negative." % i) - - if isinstance(self, BaseRectangularMesh): - BaseRectangularMesh.__init__(self, np.array([x.size for x in h]), x0) - else: - BaseMesh.__init__(self, np.array([x.size for x in h]), x0) - - # Ensure h contains 1D vectors - self._h = [Utils.mkvc(x.astype(float)) for x in h] - - @property - def h(self): - """h is a list containing the cell widths of the tensor mesh in each dimension.""" - return self._h - - @property - def hx(self): - "Width of cells in the x direction" - return self._h[0] - - @property - def hy(self): - "Width of cells in the y direction" - return None if self.dim < 2 else self._h[1] - - @property - def hz(self): - "Width of cells in the z direction" - return None if self.dim < 3 else self._h[2] - - @property - def vectorNx(self): - """Nodal grid vector (1D) in the x direction.""" - return np.r_[0., self.hx.cumsum()] + self.x0[0] - - @property - def vectorNy(self): - """Nodal grid vector (1D) in the y direction.""" - return None if self.dim < 2 else np.r_[0., self.hy.cumsum()] + self.x0[1] - - @property - def vectorNz(self): - """Nodal grid vector (1D) in the z direction.""" - return None if self.dim < 3 else np.r_[0., self.hz.cumsum()] + self.x0[2] - - @property - def vectorCCx(self): - """Cell-centered grid vector (1D) in the x direction.""" - return np.r_[0, self.hx[:-1].cumsum()] + self.hx*0.5 + self.x0[0] - - @property - def vectorCCy(self): - """Cell-centered grid vector (1D) in the y direction.""" - return None if self.dim < 2 else np.r_[0, self.hy[:-1].cumsum()] + self.hy*0.5 + self.x0[1] - - @property - def vectorCCz(self): - """Cell-centered grid vector (1D) in the z direction.""" - return None if self.dim < 3 else np.r_[0, self.hz[:-1].cumsum()] + self.hz*0.5 + self.x0[2] - - @property - def gridCC(self): - """Cell-centered grid.""" - return self._getTensorGrid('CC') - - @property - def gridN(self): - """Nodal grid.""" - return self._getTensorGrid('N') - - @property - def gridFx(self): - """Face staggered grid in the x direction.""" - if self.nFx == 0: return - return self._getTensorGrid('Fx') - - @property - def gridFy(self): - """Face staggered grid in the y direction.""" - if self.nFy == 0 or self.dim < 2: return - return self._getTensorGrid('Fy') - - @property - def gridFz(self): - """Face staggered grid in the z direction.""" - if self.nFz == 0 or self.dim < 3: return - return self._getTensorGrid('Fz') - - @property - def gridEx(self): - """Edge staggered grid in the x direction.""" - if self.nEx == 0: return - return self._getTensorGrid('Ex') - - @property - def gridEy(self): - """Edge staggered grid in the y direction.""" - if self.nEy == 0 or self.dim < 2: return - return self._getTensorGrid('Ey') - - @property - def gridEz(self): - """Edge staggered grid in the z direction.""" - if self.nEz == 0 or self.dim < 3: return - return self._getTensorGrid('Ez') - - def _getTensorGrid(self, key): - if getattr(self, '_grid' + key, None) is None: - setattr(self, '_grid' + key, Utils.ndgrid(self.getTensor(key))) - return getattr(self, '_grid' + key) - - def getTensor(self, key): - """ Returns a tensor list. - - :param str key: What tensor (see below) - :rtype: list - :return: list of the tensors that make up the mesh. - - key can be:: - - 'CC' -> scalar field defined on cell centers - 'N' -> scalar field defined on nodes - 'Fx' -> x-component of field defined on faces - 'Fy' -> y-component of field defined on faces - 'Fz' -> z-component of field defined on faces - 'Ex' -> x-component of field defined on edges - 'Ey' -> y-component of field defined on edges - 'Ez' -> z-component of field defined on edges - - """ - - if key == 'Fx': - ten = [self.vectorNx , self.vectorCCy, self.vectorCCz] - elif key == 'Fy': - ten = [self.vectorCCx, self.vectorNy , self.vectorCCz] - elif key == 'Fz': - ten = [self.vectorCCx, self.vectorCCy, self.vectorNz ] - elif key == 'Ex': - ten = [self.vectorCCx, self.vectorNy , self.vectorNz ] - elif key == 'Ey': - ten = [self.vectorNx , self.vectorCCy, self.vectorNz ] - elif key == 'Ez': - ten = [self.vectorNx , self.vectorNy , self.vectorCCz] - elif key == 'CC': - ten = [self.vectorCCx, self.vectorCCy, self.vectorCCz] - elif key == 'N': - ten = [self.vectorNx , self.vectorNy , self.vectorNz ] - - return [t for t in ten if t is not None] - - # --------------- Methods --------------------- - - def isInside(self, pts, locType='N'): - """ - Determines if a set of points are inside a mesh. - - :param numpy.ndarray pts: Location of points to test - :rtype numpy.ndarray - :return inside, numpy array of booleans - """ - pts = Utils.asArray_N_x_Dim(pts, self.dim) - - tensors = self.getTensor(locType) - - if locType == 'N' and self._meshType == 'CYL': - #NOTE: for a CYL mesh we add a node to check if we are inside in the radial direction! - tensors[0] = np.r_[0.,tensors[0]] - tensors[1] = np.r_[tensors[1], 2.0*np.pi] - - inside = np.ones(pts.shape[0],dtype=bool) - for i, tensor in enumerate(tensors): - TOL = np.diff(tensor).min() * 1.0e-10 - inside = inside & (pts[:,i] >= tensor.min()-TOL) & (pts[:,i] <= tensor.max()+TOL) - return inside - - def getInterpolationMat(self, loc, locType='CC', zerosOutside=False): - """ Produces interpolation matrix - - :param numpy.ndarray loc: Location of points to interpolate to - :param str locType: What to interpolate (see below) - :rtype: scipy.sparse.csr.csr_matrix - :return: M, the interpolation matrix - - locType can be:: - - 'Ex' -> x-component of field defined on edges - 'Ey' -> y-component of field defined on edges - 'Ez' -> z-component of field defined on edges - 'Fx' -> x-component of field defined on faces - 'Fy' -> y-component of field defined on faces - 'Fz' -> z-component of field defined on faces - 'N' -> scalar field defined on nodes - 'CC' -> scalar field defined on cell centers - """ - if self._meshType == 'CYL' and self.isSymmetric and locType in ['Ex','Ez','Fy']: - raise Exception('Symmetric CylMesh does not support %s interpolation, as this variable does not exist.' % locType) - - loc = Utils.asArray_N_x_Dim(loc, self.dim) - - if zerosOutside is False: - assert np.all(self.isInside(loc)), "Points outside of mesh" - else: - indZeros = np.logical_not(self.isInside(loc)) - loc[indZeros, :] = np.array([v.mean() for v in self.getTensor('CC')]) - - if locType in ['Fx','Fy','Fz','Ex','Ey','Ez']: - ind = {'x':0, 'y':1, 'z':2}[locType[1]] - assert self.dim >= ind, 'mesh is not high enough dimension.' - nF_nE = self.vnF if 'F' in locType else self.vnE - components = [Utils.spzeros(loc.shape[0], n) for n in nF_nE] - components[ind] = Utils.interpmat(loc, *self.getTensor(locType)) - # remove any zero blocks (hstack complains) - components = [comp for comp in components if comp.shape[1] > 0] - Q = sp.hstack(components) - elif locType in ['CC', 'N']: - Q = Utils.interpmat(loc, *self.getTensor(locType)) - else: - raise NotImplementedError('getInterpolationMat: locType=='+locType+' and mesh.dim=='+str(self.dim)) - - if zerosOutside: - Q[indZeros, :] = 0 - - return Q.tocsr() - - - def _fastInnerProduct(self, projType, prop=None, invProp=False, invMat=False): - """ - Fast version of getFaceInnerProduct. - This does not handle the case of a full tensor prop. - - :param numpy.array prop: material property (tensor properties are possible) at each cell center (nC, (1, 3, or 6)) - :param str projType: 'E' or 'F' - :param bool returnP: returns the projection matrices - :param bool invProp: inverts the material property - :param bool invMat: inverts the matrix - :rtype: scipy.csr_matrix - :return: M, the inner product matrix (nF, nF) - """ - assert projType in ['F', 'E'], "projType must be 'F' for faces or 'E' for edges" - - if prop is None: - prop = np.ones(self.nC) - - if invProp: - prop = 1./prop - - if Utils.isScalar(prop): - prop = prop*np.ones(self.nC) - - if prop.size == self.nC: - Av = getattr(self, 'ave'+projType+'2CC') - Vprop = self.vol * Utils.mkvc(prop) - M = self.dim * Utils.sdiag(Av.T * Vprop) - elif prop.size == self.nC*self.dim: - Av = getattr(self, 'ave'+projType+'2CCV') - V = sp.kron(sp.identity(self.dim), Utils.sdiag(self.vol)) - M = Utils.sdiag(Av.T * V * Utils.mkvc(prop)) - else: - return None - - if invMat: - return Utils.sdInv(M) - else: - return M - - def _fastInnerProductDeriv(self, projType, prop, invProp=False, invMat=False): - """ - :param str projType: 'E' or 'F' - :param TensorType tensorType: type of the tensor - :param bool invProp: inverts the material property - :param bool invMat: inverts the matrix - :rtype: function - :return: dMdmu, the derivative of the inner product matrix - """ - assert projType in ['F', 'E'], "projType must be 'F' for faces or 'E' for edges" - tensorType = Utils.TensorType(self, prop) - - dMdprop = None - - if invMat: - MI = self._fastInnerProduct(projType, prop, invProp=invProp, invMat=invMat) - - if tensorType == 0: - Av = getattr(self, 'ave'+projType+'2CC') - V = Utils.sdiag(self.vol) - ones = sp.csr_matrix((np.ones(self.nC), (range(self.nC), np.zeros(self.nC))), shape=(self.nC,1)) - if not invMat and not invProp: - dMdprop = self.dim * Av.T * V * ones - elif invMat and invProp: - dMdprop = self.dim * Utils.sdiag(MI.diagonal()**2) * Av.T * V * ones * Utils.sdiag(1./prop**2) - - if tensorType == 1: - Av = getattr(self, 'ave'+projType+'2CC') - V = Utils.sdiag(self.vol) - if not invMat and not invProp: - dMdprop = self.dim * Av.T * V - elif invMat and invProp: - dMdprop = self.dim * Utils.sdiag(MI.diagonal()**2) * Av.T * V * Utils.sdiag(1./prop**2) - - if tensorType == 2: # anisotropic - Av = getattr(self, 'ave'+projType+'2CCV') - V = sp.kron(sp.identity(self.dim), Utils.sdiag(self.vol)) - if not invMat and not invProp: - dMdprop = Av.T * V - elif invMat and invProp: - dMdprop = Utils.sdiag(MI.diagonal()**2) * Av.T * V * Utils.sdiag(1./prop**2) - - if dMdprop is not None: - def innerProductDeriv(v=None): - if v is None: - print 'Depreciation Warning: TensorMesh.innerProductDeriv. You should be supplying a vector. Use: sdiag(u)*dMdprop' - return dMdprop - return Utils.sdiag(v) * dMdprop - return innerProductDeriv - else: - return None - - - -class TensorMesh(BaseTensorMesh, BaseRectangularMesh, TensorView, DiffOperators, InnerProducts): - """ - TensorMesh is a mesh class that deals with tensor product meshes. - - Any Mesh that has a constant width along the entire axis - such that it can defined by a single width vector, called 'h'. - - :: - - hx = np.array([1,1,1]) - hy = np.array([1,2]) - hz = np.array([1,1,1,1]) - - mesh = Mesh.TensorMesh([hx, hy, hz]) - - Example of a padded tensor mesh using :func:`SimPEG.Utils.meshutils.meshTensor`: - - .. plot:: - :include-source: - - from SimPEG import Mesh, Utils - M = Mesh.TensorMesh([[(10,10,-1.3),(10,40),(10,10,1.3)], [(10,10,-1.3),(10,20)]]) - M.plotGrid() - - For a quick tensor mesh on a (10x12x15) unit cube:: - - mesh = Mesh.TensorMesh([10, 12, 15]) - - """ - - __metaclass__ = Utils.SimPEGMetaClass - - _meshType = 'TENSOR' - - def __init__(self, h_in, x0=None): - BaseTensorMesh.__init__(self, h_in, x0) - - def __str__(self): - outStr = ' ---- {0:d}-D TensorMesh ---- '.format(self.dim) - def printH(hx, outStr=''): - i = -1 - while True: - i = i + 1 - if i > hx.size: - break - elif i == hx.size: - break - h = hx[i] - n = 1 - for j in range(i+1, hx.size): - if hx[j] == h: - n = n + 1 - i = i + 1 - else: - break - - if n == 1: - outStr += ' {0:.2f},'.format(h) - else: - outStr += ' {0:d}*{1:.2f},'.format(n,h) - - return outStr[:-1] - - if self.dim == 1: - outStr += '\n x0: {0:.2f}'.format(self.x0[0]) - outStr += '\n nCx: {0:d}'.format(self.nCx) - outStr += printH(self.hx, outStr='\n hx:') - pass - elif self.dim == 2: - outStr += '\n x0: {0:.2f}'.format(self.x0[0]) - outStr += '\n y0: {0:.2f}'.format(self.x0[1]) - outStr += '\n nCx: {0:d}'.format(self.nCx) - outStr += '\n nCy: {0:d}'.format(self.nCy) - outStr += printH(self.hx, outStr='\n hx:') - outStr += printH(self.hy, outStr='\n hy:') - elif self.dim == 3: - outStr += '\n x0: {0:.2f}'.format(self.x0[0]) - outStr += '\n y0: {0:.2f}'.format(self.x0[1]) - outStr += '\n z0: {0:.2f}'.format(self.x0[2]) - outStr += '\n nCx: {0:d}'.format(self.nCx) - outStr += '\n nCy: {0:d}'.format(self.nCy) - outStr += '\n nCz: {0:d}'.format(self.nCz) - outStr += printH(self.hx, outStr='\n hx:') - outStr += printH(self.hy, outStr='\n hy:') - outStr += printH(self.hz, outStr='\n hz:') - - return outStr - - - # --------------- Geometries --------------------- - @property - def vol(self): - """Construct cell volumes of the 3D model as 1d array.""" - if getattr(self, '_vol', None) is None: - vh = self.h - # Compute cell volumes - if self.dim == 1: - self._vol = Utils.mkvc(vh[0]) - elif self.dim == 2: - # Cell sizes in each direction - self._vol = Utils.mkvc(np.outer(vh[0], vh[1])) - elif self.dim == 3: - # Cell sizes in each direction - self._vol = Utils.mkvc(np.outer(Utils.mkvc(np.outer(vh[0], vh[1])), vh[2])) - return self._vol - - @property - def area(self): - """Construct face areas of the 3D model as 1d array.""" - if getattr(self, '_area', None) is None: - # Ensure that we are working with column vectors - vh = self.h - # The number of cell centers in each direction - n = self.vnC - # Compute areas of cell faces - if(self.dim == 1): - self._area = np.ones(n[0]+1) - elif(self.dim == 2): - area1 = np.outer(np.ones(n[0]+1), vh[1]) - area2 = np.outer(vh[0], np.ones(n[1]+1)) - self._area = np.r_[Utils.mkvc(area1), Utils.mkvc(area2)] - elif(self.dim == 3): - area1 = np.outer(np.ones(n[0]+1), Utils.mkvc(np.outer(vh[1], vh[2]))) - area2 = np.outer(vh[0], Utils.mkvc(np.outer(np.ones(n[1]+1), vh[2]))) - area3 = np.outer(vh[0], Utils.mkvc(np.outer(vh[1], np.ones(n[2]+1)))) - self._area = np.r_[Utils.mkvc(area1), Utils.mkvc(area2), Utils.mkvc(area3)] - return self._area - - @property - def edge(self): - """Construct edge legnths of the 3D model as 1d array.""" - if getattr(self, '_edge', None) is None: - # Ensure that we are working with column vectors - vh = self.h - # The number of cell centers in each direction - n = self.vnC - # Compute edge lengths - if(self.dim == 1): - self._edge = Utils.mkvc(vh[0]) - elif(self.dim == 2): - l1 = np.outer(vh[0], np.ones(n[1]+1)) - l2 = np.outer(np.ones(n[0]+1), vh[1]) - self._edge = np.r_[Utils.mkvc(l1), Utils.mkvc(l2)] - elif(self.dim == 3): - l1 = np.outer(vh[0], Utils.mkvc(np.outer(np.ones(n[1]+1), np.ones(n[2]+1)))) - l2 = np.outer(np.ones(n[0]+1), Utils.mkvc(np.outer(vh[1], np.ones(n[2]+1)))) - l3 = np.outer(np.ones(n[0]+1), Utils.mkvc(np.outer(np.ones(n[1]+1), vh[2]))) - self._edge = np.r_[Utils.mkvc(l1), Utils.mkvc(l2), Utils.mkvc(l3)] - return self._edge - - @property - def faceBoundaryInd(self): - """ - Find indices of boundary faces in each direction - """ - if self.dim==1: - indxd = (self.gridFx==min(self.gridFx)) - indxu = (self.gridFx==max(self.gridFx)) - return indxd, indxu - elif self.dim==2: - indxd = (self.gridFx[:,0]==min(self.gridFx[:,0])) - indxu = (self.gridFx[:,0]==max(self.gridFx[:,0])) - indyd = (self.gridFy[:,1]==min(self.gridFy[:,1])) - indyu = (self.gridFy[:,1]==max(self.gridFy[:,1])) - return indxd, indxu, indyd, indyu - elif self.dim==3: - indxd = (self.gridFx[:,0]==min(self.gridFx[:,0])) - indxu = (self.gridFx[:,0]==max(self.gridFx[:,0])) - indyd = (self.gridFy[:,1]==min(self.gridFy[:,1])) - indyu = (self.gridFy[:,1]==max(self.gridFy[:,1])) - indzd = (self.gridFz[:,2]==min(self.gridFz[:,2])) - indzu = (self.gridFz[:,2]==max(self.gridFz[:,2])) - return indxd, indxu, indyd, indyu, indzd, indzu - - @property - def cellBoundaryInd(self): - """ - Find indices of boundary faces in each direction - """ - if self.dim==1: - indxd = (self.gridCC==min(self.gridCC)) - indxu = (self.gridCC==max(self.gridCC)) - return indxd, indxu - elif self.dim==2: - indxd = (self.gridCC[:,0]==min(self.gridCC[:,0])) - indxu = (self.gridCC[:,0]==max(self.gridCC[:,0])) - indyd = (self.gridCC[:,1]==min(self.gridCC[:,1])) - indyu = (self.gridCC[:,1]==max(self.gridCC[:,1])) - return indxd, indxu, indyd, indyu - elif self.dim==3: - indxd = (self.gridCC[:,0]==min(self.gridCC[:,0])) - indxu = (self.gridCC[:,0]==max(self.gridCC[:,0])) - indyd = (self.gridCC[:,1]==min(self.gridCC[:,1])) - indyu = (self.gridCC[:,1]==max(self.gridCC[:,1])) - indzd = (self.gridCC[:,2]==min(self.gridCC[:,2])) - indzu = (self.gridCC[:,2]==max(self.gridCC[:,2])) - return indxd, indxu, indyd, indyu, indzd, indzu +from SimPEG import Utils, np, sp +from BaseMesh import BaseMesh, BaseRectangularMesh +from View import TensorView +from DiffOperators import DiffOperators +from InnerProducts import InnerProducts +from MeshIO import TensorMeshIO + +class BaseTensorMesh(BaseMesh): + + __metaclass__ = Utils.SimPEGMetaClass + + _meshType = 'BASETENSOR' + + _unitDimensions = [1, 1, 1] + + def __init__(self, h_in, x0_in=None): + assert type(h_in) in [list, tuple], 'h_in must be a list' + assert len(h_in) in [1,2,3], 'h_in must be of dimension 1, 2, or 3' + h = range(len(h_in)) + for i, h_i in enumerate(h_in): + if Utils.isScalar(h_i) and type(h_i) is not np.ndarray: + # This gives you something over the unit cube. + h_i = self._unitDimensions[i] * np.ones(int(h_i))/int(h_i) + elif type(h_i) is list: + h_i = Utils.meshTensor(h_i) + assert isinstance(h_i, np.ndarray), ("h[%i] is not a numpy array." % i) + assert len(h_i.shape) == 1, ("h[%i] must be a 1D numpy array." % i) + h[i] = h_i[:] # make a copy. + + x0 = np.zeros(len(h)) + if x0_in is not None: + assert len(h) == len(x0_in), "Dimension mismatch. x0 != len(h)" + for i in range(len(h)): + x_i, h_i = x0_in[i], h[i] + if Utils.isScalar(x_i): + x0[i] = x_i + elif x_i == '0': + x0[i] = 0.0 + elif x_i == 'C': + x0[i] = -h_i.sum()*0.5 + elif x_i == 'N': + x0[i] = -h_i.sum() + else: + raise Exception("x0[%i] must be a scalar or '0' to be zero, 'C' to center, or 'N' to be negative." % i) + + if isinstance(self, BaseRectangularMesh): + BaseRectangularMesh.__init__(self, np.array([x.size for x in h]), x0) + else: + BaseMesh.__init__(self, np.array([x.size for x in h]), x0) + + # Ensure h contains 1D vectors + self._h = [Utils.mkvc(x.astype(float)) for x in h] + + @property + def h(self): + """h is a list containing the cell widths of the tensor mesh in each dimension.""" + return self._h + + @property + def hx(self): + "Width of cells in the x direction" + return self._h[0] + + @property + def hy(self): + "Width of cells in the y direction" + return None if self.dim < 2 else self._h[1] + + @property + def hz(self): + "Width of cells in the z direction" + return None if self.dim < 3 else self._h[2] + + @property + def vectorNx(self): + """Nodal grid vector (1D) in the x direction.""" + return np.r_[0., self.hx.cumsum()] + self.x0[0] + + @property + def vectorNy(self): + """Nodal grid vector (1D) in the y direction.""" + return None if self.dim < 2 else np.r_[0., self.hy.cumsum()] + self.x0[1] + + @property + def vectorNz(self): + """Nodal grid vector (1D) in the z direction.""" + return None if self.dim < 3 else np.r_[0., self.hz.cumsum()] + self.x0[2] + + @property + def vectorCCx(self): + """Cell-centered grid vector (1D) in the x direction.""" + return np.r_[0, self.hx[:-1].cumsum()] + self.hx*0.5 + self.x0[0] + + @property + def vectorCCy(self): + """Cell-centered grid vector (1D) in the y direction.""" + return None if self.dim < 2 else np.r_[0, self.hy[:-1].cumsum()] + self.hy*0.5 + self.x0[1] + + @property + def vectorCCz(self): + """Cell-centered grid vector (1D) in the z direction.""" + return None if self.dim < 3 else np.r_[0, self.hz[:-1].cumsum()] + self.hz*0.5 + self.x0[2] + + @property + def gridCC(self): + """Cell-centered grid.""" + return self._getTensorGrid('CC') + + @property + def gridN(self): + """Nodal grid.""" + return self._getTensorGrid('N') + + @property + def gridFx(self): + """Face staggered grid in the x direction.""" + if self.nFx == 0: return + return self._getTensorGrid('Fx') + + @property + def gridFy(self): + """Face staggered grid in the y direction.""" + if self.nFy == 0 or self.dim < 2: return + return self._getTensorGrid('Fy') + + @property + def gridFz(self): + """Face staggered grid in the z direction.""" + if self.nFz == 0 or self.dim < 3: return + return self._getTensorGrid('Fz') + + @property + def gridEx(self): + """Edge staggered grid in the x direction.""" + if self.nEx == 0: return + return self._getTensorGrid('Ex') + + @property + def gridEy(self): + """Edge staggered grid in the y direction.""" + if self.nEy == 0 or self.dim < 2: return + return self._getTensorGrid('Ey') + + @property + def gridEz(self): + """Edge staggered grid in the z direction.""" + if self.nEz == 0 or self.dim < 3: return + return self._getTensorGrid('Ez') + + def _getTensorGrid(self, key): + if getattr(self, '_grid' + key, None) is None: + setattr(self, '_grid' + key, Utils.ndgrid(self.getTensor(key))) + return getattr(self, '_grid' + key) + + def getTensor(self, key): + """ Returns a tensor list. + + :param str key: What tensor (see below) + :rtype: list + :return: list of the tensors that make up the mesh. + + key can be:: + + 'CC' -> scalar field defined on cell centers + 'N' -> scalar field defined on nodes + 'Fx' -> x-component of field defined on faces + 'Fy' -> y-component of field defined on faces + 'Fz' -> z-component of field defined on faces + 'Ex' -> x-component of field defined on edges + 'Ey' -> y-component of field defined on edges + 'Ez' -> z-component of field defined on edges + + """ + + if key == 'Fx': + ten = [self.vectorNx , self.vectorCCy, self.vectorCCz] + elif key == 'Fy': + ten = [self.vectorCCx, self.vectorNy , self.vectorCCz] + elif key == 'Fz': + ten = [self.vectorCCx, self.vectorCCy, self.vectorNz ] + elif key == 'Ex': + ten = [self.vectorCCx, self.vectorNy , self.vectorNz ] + elif key == 'Ey': + ten = [self.vectorNx , self.vectorCCy, self.vectorNz ] + elif key == 'Ez': + ten = [self.vectorNx , self.vectorNy , self.vectorCCz] + elif key == 'CC': + ten = [self.vectorCCx, self.vectorCCy, self.vectorCCz] + elif key == 'N': + ten = [self.vectorNx , self.vectorNy , self.vectorNz ] + + return [t for t in ten if t is not None] + + # --------------- Methods --------------------- + + def isInside(self, pts, locType='N'): + """ + Determines if a set of points are inside a mesh. + + :param numpy.ndarray pts: Location of points to test + :rtype numpy.ndarray + :return inside, numpy array of booleans + """ + pts = Utils.asArray_N_x_Dim(pts, self.dim) + + tensors = self.getTensor(locType) + + if locType == 'N' and self._meshType == 'CYL': + #NOTE: for a CYL mesh we add a node to check if we are inside in the radial direction! + tensors[0] = np.r_[0.,tensors[0]] + tensors[1] = np.r_[tensors[1], 2.0*np.pi] + + inside = np.ones(pts.shape[0],dtype=bool) + for i, tensor in enumerate(tensors): + TOL = np.diff(tensor).min() * 1.0e-10 + inside = inside & (pts[:,i] >= tensor.min()-TOL) & (pts[:,i] <= tensor.max()+TOL) + return inside + + def getInterpolationMat(self, loc, locType='CC', zerosOutside=False): + """ Produces interpolation matrix + + :param numpy.ndarray loc: Location of points to interpolate to + :param str locType: What to interpolate (see below) + :rtype: scipy.sparse.csr.csr_matrix + :return: M, the interpolation matrix + + locType can be:: + + 'Ex' -> x-component of field defined on edges + 'Ey' -> y-component of field defined on edges + 'Ez' -> z-component of field defined on edges + 'Fx' -> x-component of field defined on faces + 'Fy' -> y-component of field defined on faces + 'Fz' -> z-component of field defined on faces + 'N' -> scalar field defined on nodes + 'CC' -> scalar field defined on cell centers + """ + if self._meshType == 'CYL' and self.isSymmetric and locType in ['Ex','Ez','Fy']: + raise Exception('Symmetric CylMesh does not support %s interpolation, as this variable does not exist.' % locType) + + loc = Utils.asArray_N_x_Dim(loc, self.dim) + + if zerosOutside is False: + assert np.all(self.isInside(loc)), "Points outside of mesh" + else: + indZeros = np.logical_not(self.isInside(loc)) + loc[indZeros, :] = np.array([v.mean() for v in self.getTensor('CC')]) + + if locType in ['Fx','Fy','Fz','Ex','Ey','Ez']: + ind = {'x':0, 'y':1, 'z':2}[locType[1]] + assert self.dim >= ind, 'mesh is not high enough dimension.' + nF_nE = self.vnF if 'F' in locType else self.vnE + components = [Utils.spzeros(loc.shape[0], n) for n in nF_nE] + components[ind] = Utils.interpmat(loc, *self.getTensor(locType)) + # remove any zero blocks (hstack complains) + components = [comp for comp in components if comp.shape[1] > 0] + Q = sp.hstack(components) + elif locType in ['CC', 'N']: + Q = Utils.interpmat(loc, *self.getTensor(locType)) + else: + raise NotImplementedError('getInterpolationMat: locType=='+locType+' and mesh.dim=='+str(self.dim)) + + if zerosOutside: + Q[indZeros, :] = 0 + + return Q.tocsr() + + + def _fastInnerProduct(self, projType, prop=None, invProp=False, invMat=False): + """ + Fast version of getFaceInnerProduct. + This does not handle the case of a full tensor prop. + + :param numpy.array prop: material property (tensor properties are possible) at each cell center (nC, (1, 3, or 6)) + :param str projType: 'E' or 'F' + :param bool returnP: returns the projection matrices + :param bool invProp: inverts the material property + :param bool invMat: inverts the matrix + :rtype: scipy.csr_matrix + :return: M, the inner product matrix (nF, nF) + """ + assert projType in ['F', 'E'], "projType must be 'F' for faces or 'E' for edges" + + if prop is None: + prop = np.ones(self.nC) + + if invProp: + prop = 1./prop + + if Utils.isScalar(prop): + prop = prop*np.ones(self.nC) + + if prop.size == self.nC: + Av = getattr(self, 'ave'+projType+'2CC') + Vprop = self.vol * Utils.mkvc(prop) + M = self.dim * Utils.sdiag(Av.T * Vprop) + elif prop.size == self.nC*self.dim: + Av = getattr(self, 'ave'+projType+'2CCV') + V = sp.kron(sp.identity(self.dim), Utils.sdiag(self.vol)) + M = Utils.sdiag(Av.T * V * Utils.mkvc(prop)) + else: + return None + + if invMat: + return Utils.sdInv(M) + else: + return M + + def _fastInnerProductDeriv(self, projType, prop, invProp=False, invMat=False): + """ + :param str projType: 'E' or 'F' + :param TensorType tensorType: type of the tensor + :param bool invProp: inverts the material property + :param bool invMat: inverts the matrix + :rtype: function + :return: dMdmu, the derivative of the inner product matrix + """ + assert projType in ['F', 'E'], "projType must be 'F' for faces or 'E' for edges" + tensorType = Utils.TensorType(self, prop) + + dMdprop = None + + if invMat: + MI = self._fastInnerProduct(projType, prop, invProp=invProp, invMat=invMat) + + if tensorType == 0: + Av = getattr(self, 'ave'+projType+'2CC') + V = Utils.sdiag(self.vol) + ones = sp.csr_matrix((np.ones(self.nC), (range(self.nC), np.zeros(self.nC))), shape=(self.nC,1)) + if not invMat and not invProp: + dMdprop = self.dim * Av.T * V * ones + elif invMat and invProp: + dMdprop = self.dim * Utils.sdiag(MI.diagonal()**2) * Av.T * V * ones * Utils.sdiag(1./prop**2) + + if tensorType == 1: + Av = getattr(self, 'ave'+projType+'2CC') + V = Utils.sdiag(self.vol) + if not invMat and not invProp: + dMdprop = self.dim * Av.T * V + elif invMat and invProp: + dMdprop = self.dim * Utils.sdiag(MI.diagonal()**2) * Av.T * V * Utils.sdiag(1./prop**2) + + if tensorType == 2: # anisotropic + Av = getattr(self, 'ave'+projType+'2CCV') + V = sp.kron(sp.identity(self.dim), Utils.sdiag(self.vol)) + if not invMat and not invProp: + dMdprop = Av.T * V + elif invMat and invProp: + dMdprop = Utils.sdiag(MI.diagonal()**2) * Av.T * V * Utils.sdiag(1./prop**2) + + if dMdprop is not None: + def innerProductDeriv(v=None): + if v is None: + print 'Depreciation Warning: TensorMesh.innerProductDeriv. You should be supplying a vector. Use: sdiag(u)*dMdprop' + return dMdprop + return Utils.sdiag(v) * dMdprop + return innerProductDeriv + else: + return None + + + +class TensorMesh(BaseTensorMesh, BaseRectangularMesh, TensorView, DiffOperators, InnerProducts, TensorMeshIO): + """ + TensorMesh is a mesh class that deals with tensor product meshes. + + Any Mesh that has a constant width along the entire axis + such that it can defined by a single width vector, called 'h'. + + :: + + hx = np.array([1,1,1]) + hy = np.array([1,2]) + hz = np.array([1,1,1,1]) + + mesh = Mesh.TensorMesh([hx, hy, hz]) + + Example of a padded tensor mesh using :func:`SimPEG.Utils.meshutils.meshTensor`: + + .. plot:: + :include-source: + + from SimPEG import Mesh, Utils + M = Mesh.TensorMesh([[(10,10,-1.3),(10,40),(10,10,1.3)], [(10,10,-1.3),(10,20)]]) + M.plotGrid() + + For a quick tensor mesh on a (10x12x15) unit cube:: + + mesh = Mesh.TensorMesh([10, 12, 15]) + + """ + + __metaclass__ = Utils.SimPEGMetaClass + + _meshType = 'TENSOR' + + def __init__(self, h_in, x0=None): + BaseTensorMesh.__init__(self, h_in, x0) + + def __str__(self): + outStr = ' ---- {0:d}-D TensorMesh ---- '.format(self.dim) + def printH(hx, outStr=''): + i = -1 + while True: + i = i + 1 + if i > hx.size: + break + elif i == hx.size: + break + h = hx[i] + n = 1 + for j in range(i+1, hx.size): + if hx[j] == h: + n = n + 1 + i = i + 1 + else: + break + + if n == 1: + outStr += ' {0:.2f},'.format(h) + else: + outStr += ' {0:d}*{1:.2f},'.format(n,h) + + return outStr[:-1] + + if self.dim == 1: + outStr += '\n x0: {0:.2f}'.format(self.x0[0]) + outStr += '\n nCx: {0:d}'.format(self.nCx) + outStr += printH(self.hx, outStr='\n hx:') + pass + elif self.dim == 2: + outStr += '\n x0: {0:.2f}'.format(self.x0[0]) + outStr += '\n y0: {0:.2f}'.format(self.x0[1]) + outStr += '\n nCx: {0:d}'.format(self.nCx) + outStr += '\n nCy: {0:d}'.format(self.nCy) + outStr += printH(self.hx, outStr='\n hx:') + outStr += printH(self.hy, outStr='\n hy:') + elif self.dim == 3: + outStr += '\n x0: {0:.2f}'.format(self.x0[0]) + outStr += '\n y0: {0:.2f}'.format(self.x0[1]) + outStr += '\n z0: {0:.2f}'.format(self.x0[2]) + outStr += '\n nCx: {0:d}'.format(self.nCx) + outStr += '\n nCy: {0:d}'.format(self.nCy) + outStr += '\n nCz: {0:d}'.format(self.nCz) + outStr += printH(self.hx, outStr='\n hx:') + outStr += printH(self.hy, outStr='\n hy:') + outStr += printH(self.hz, outStr='\n hz:') + + return outStr + + + # --------------- Geometries --------------------- + @property + def vol(self): + """Construct cell volumes of the 3D model as 1d array.""" + if getattr(self, '_vol', None) is None: + vh = self.h + # Compute cell volumes + if self.dim == 1: + self._vol = Utils.mkvc(vh[0]) + elif self.dim == 2: + # Cell sizes in each direction + self._vol = Utils.mkvc(np.outer(vh[0], vh[1])) + elif self.dim == 3: + # Cell sizes in each direction + self._vol = Utils.mkvc(np.outer(Utils.mkvc(np.outer(vh[0], vh[1])), vh[2])) + return self._vol + + @property + def area(self): + """Construct face areas of the 3D model as 1d array.""" + if getattr(self, '_area', None) is None: + # Ensure that we are working with column vectors + vh = self.h + # The number of cell centers in each direction + n = self.vnC + # Compute areas of cell faces + if(self.dim == 1): + self._area = np.ones(n[0]+1) + elif(self.dim == 2): + area1 = np.outer(np.ones(n[0]+1), vh[1]) + area2 = np.outer(vh[0], np.ones(n[1]+1)) + self._area = np.r_[Utils.mkvc(area1), Utils.mkvc(area2)] + elif(self.dim == 3): + area1 = np.outer(np.ones(n[0]+1), Utils.mkvc(np.outer(vh[1], vh[2]))) + area2 = np.outer(vh[0], Utils.mkvc(np.outer(np.ones(n[1]+1), vh[2]))) + area3 = np.outer(vh[0], Utils.mkvc(np.outer(vh[1], np.ones(n[2]+1)))) + self._area = np.r_[Utils.mkvc(area1), Utils.mkvc(area2), Utils.mkvc(area3)] + return self._area + + @property + def edge(self): + """Construct edge legnths of the 3D model as 1d array.""" + if getattr(self, '_edge', None) is None: + # Ensure that we are working with column vectors + vh = self.h + # The number of cell centers in each direction + n = self.vnC + # Compute edge lengths + if(self.dim == 1): + self._edge = Utils.mkvc(vh[0]) + elif(self.dim == 2): + l1 = np.outer(vh[0], np.ones(n[1]+1)) + l2 = np.outer(np.ones(n[0]+1), vh[1]) + self._edge = np.r_[Utils.mkvc(l1), Utils.mkvc(l2)] + elif(self.dim == 3): + l1 = np.outer(vh[0], Utils.mkvc(np.outer(np.ones(n[1]+1), np.ones(n[2]+1)))) + l2 = np.outer(np.ones(n[0]+1), Utils.mkvc(np.outer(vh[1], np.ones(n[2]+1)))) + l3 = np.outer(np.ones(n[0]+1), Utils.mkvc(np.outer(np.ones(n[1]+1), vh[2]))) + self._edge = np.r_[Utils.mkvc(l1), Utils.mkvc(l2), Utils.mkvc(l3)] + return self._edge + + @property + def faceBoundaryInd(self): + """ + Find indices of boundary faces in each direction + """ + if self.dim==1: + indxd = (self.gridFx==min(self.gridFx)) + indxu = (self.gridFx==max(self.gridFx)) + return indxd, indxu + elif self.dim==2: + indxd = (self.gridFx[:,0]==min(self.gridFx[:,0])) + indxu = (self.gridFx[:,0]==max(self.gridFx[:,0])) + indyd = (self.gridFy[:,1]==min(self.gridFy[:,1])) + indyu = (self.gridFy[:,1]==max(self.gridFy[:,1])) + return indxd, indxu, indyd, indyu + elif self.dim==3: + indxd = (self.gridFx[:,0]==min(self.gridFx[:,0])) + indxu = (self.gridFx[:,0]==max(self.gridFx[:,0])) + indyd = (self.gridFy[:,1]==min(self.gridFy[:,1])) + indyu = (self.gridFy[:,1]==max(self.gridFy[:,1])) + indzd = (self.gridFz[:,2]==min(self.gridFz[:,2])) + indzu = (self.gridFz[:,2]==max(self.gridFz[:,2])) + return indxd, indxu, indyd, indyu, indzd, indzu + + @property + def cellBoundaryInd(self): + """ + Find indices of boundary faces in each direction + """ + if self.dim==1: + indxd = (self.gridCC==min(self.gridCC)) + indxu = (self.gridCC==max(self.gridCC)) + return indxd, indxu + elif self.dim==2: + indxd = (self.gridCC[:,0]==min(self.gridCC[:,0])) + indxu = (self.gridCC[:,0]==max(self.gridCC[:,0])) + indyd = (self.gridCC[:,1]==min(self.gridCC[:,1])) + indyu = (self.gridCC[:,1]==max(self.gridCC[:,1])) + return indxd, indxu, indyd, indyu + elif self.dim==3: + indxd = (self.gridCC[:,0]==min(self.gridCC[:,0])) + indxu = (self.gridCC[:,0]==max(self.gridCC[:,0])) + indyd = (self.gridCC[:,1]==min(self.gridCC[:,1])) + indyu = (self.gridCC[:,1]==max(self.gridCC[:,1])) + indzd = (self.gridCC[:,2]==min(self.gridCC[:,2])) + indzu = (self.gridCC[:,2]==max(self.gridCC[:,2])) + return indxd, indxu, indyd, indyu, indzd, indzu diff --git a/SimPEG/Mesh/TreeMesh.py b/SimPEG/Mesh/TreeMesh.py index 0205e972..02c23dee 100644 --- a/SimPEG/Mesh/TreeMesh.py +++ b/SimPEG/Mesh/TreeMesh.py @@ -100,11 +100,12 @@ except Exception, e: from InnerProducts import InnerProducts from TensorMesh import TensorMesh, BaseTensorMesh +from MeshIO import TreeMeshIO import time MAX_BITS = 20 -class TreeMesh(BaseTensorMesh, InnerProducts): +class TreeMesh(BaseTensorMesh, InnerProducts, TreeMeshIO): _meshType = 'TREE' diff --git a/SimPEG/Utils/meshutils.py b/SimPEG/Utils/meshutils.py index c43dfd13..eb5d13a1 100644 --- a/SimPEG/Utils/meshutils.py +++ b/SimPEG/Utils/meshutils.py @@ -102,408 +102,6 @@ def closestPoints(mesh, pts, gridLoc='CC'): return nodeInds -def readUBCTensorMesh(fileName): - """ - Read UBC GIF 3DTensor mesh and generate 3D Tensor mesh in simpegTD - - Input: - :param fileName, path to the UBC GIF mesh file - - Output: - :param SimPEG TensorMesh object - :return - """ - - # Interal function to read cell size lines for the UBC mesh files. - def readCellLine(line): - for seg in line.split(): - if '*' in seg: - st = seg - sp = seg.split('*') - re = np.array(sp[0],dtype=int)*(' ' + sp[1]) - line = line.replace(st,re.strip()) - return np.array(line.split(),dtype=float) - - # Read the file as line strings, remove lines with comment = ! - msh = np.genfromtxt(fileName,delimiter='\n',dtype=np.str,comments='!') - - # Fist line is the size of the model - sizeM = np.array(msh[0].split(),dtype=float) - # Second line is the South-West-Top corner coordinates. - x0 = np.array(msh[1].split(),dtype=float) - # Read the cell sizes - h1 = readCellLine(msh[2]) - h2 = readCellLine(msh[3]) - h3temp = readCellLine(msh[4]) - h3 = h3temp[::-1] # Invert the indexing of the vector to start from the bottom. - # Adjust the reference point to the bottom south west corner - x0[2] = x0[2] - np.sum(h3) - # Make the mesh - from SimPEG import Mesh - tensMsh = Mesh.TensorMesh([h1,h2,h3],x0) - return tensMsh - -def readUBCTensorModel(fileName, mesh): - """ - Read UBC 3DTensor mesh model and generate 3D Tensor mesh model in simpeg - - Input: - :param fileName, path to the UBC GIF mesh file to read - :param mesh, TensorMesh object, mesh that coresponds to the model - - Output: - :return numpy array, model with TensorMesh ordered - """ - f = open(fileName, 'r') - model = np.array(map(float, f.readlines())) - f.close() - model = np.reshape(model, (mesh.nCz, mesh.nCx, mesh.nCy), order = 'F') - model = model[::-1,:,:] - model = np.transpose(model, (1, 2, 0)) - model = mkvc(model) - - return model - -def writeUBCTensorMesh(fileName, mesh): - """ - Writes a SimPEG TensorMesh to a UBC-GIF format mesh file. - - :param str fileName: File to write to - :param simpeg.Mesh.TensorMesh mesh: The mesh - - """ - assert mesh.dim == 3 - s = '' - s += '%i %i %i\n' %tuple(mesh.vnC) - origin = mesh.x0 + np.array([0,0,mesh.hz.sum()]) # Have to it in the same operation or use mesh.x0.copy(), otherwise the mesh.x0 is updated. - origin.dtype = float - - s += '%.2f %.2f %.2f\n' %tuple(origin) - s += ('%.2f '*mesh.nCx+'\n')%tuple(mesh.hx) - s += ('%.2f '*mesh.nCy+'\n')%tuple(mesh.hy) - s += ('%.2f '*mesh.nCz+'\n')%tuple(mesh.hz[::-1]) - f = open(fileName, 'w') - f.write(s) - f.close() - -def writeUBCTensorModel(fileName, mesh, model): - """ - Writes a model associated with a SimPEG TensorMesh - to a UBC-GIF format model file. - - :param str fileName: File to write to - :param simpeg.Mesh.TensorMesh mesh: The mesh - :param numpy.ndarray model: The model - """ - - # Reshape model to a matrix - modelMat = mesh.r(model,'CC','CC','M') - # Transpose the axes - modelMatT = modelMat.transpose((2,0,1)) - # Flip z to positive down - modelMatTR = mkvc(modelMatT[::-1,:,:]) - - np.savetxt(fileName, modelMatTR.ravel()) - -def writeUBCocTreeFiles(fileName,mesh,modelDict=None): - ''' - Write UBC ocTree mesh and model files from a simpeg ocTree mesh and model. - - :param str fileName: File to write to - :param simpeg.Mesh.TreeMesh mesh: The mesh - :param dictionary modelDict: The models in a dictionary, where the keys is the name of the of the model file - - ''' - - # Calculate information to write in the file. - # Number of cells in the underlying mesh - nCunderMesh = np.array([h.size for h in mesh.h],dtype=np.int64) - # The top-south-west most corner of the mesh - tswCorn = mesh.x0 + np.array([0,0,np.sum(mesh.h[2])]) - # Smallest cell size - smallCell = np.array([h.min() for h in mesh.h]) - # Number of cells - nrCells = mesh.nC - - ## Extract iformation about the cells. - # cell pointers - cellPointers = np.array([c._pointer for c in mesh]) - # cell with - cellW = np.array([ mesh._levelWidth(i) for i in cellPointers[:,-1] ]) - # Need to shift the pointers to work with UBC indexing - # UBC Octree indexes always the top-left-close (top-south-west) corner first and orders the cells in z(top-down),x,y vs x,y,z(bottom-up). - # Shift index up by 1 - ubcCellPt = cellPointers[:,0:-1].copy() + np.array([1.,1.,1.]) - # Need reindex the z index to be from the top-left-close corner and to be from the global top. - ubcCellPt[:,2] = ( nCunderMesh[-1] + 2) - (ubcCellPt[:,2] + cellW) - - # Reorder the ubcCellPt - ubcReorder = np.argsort(ubcCellPt.view(','.join(3*['float'])),axis=0,order=['f2','f1','f0'])[:,0] - # Make a array with the pointers and the withs, that are order in the ubc ordering - indArr = np.concatenate((ubcCellPt[ubcReorder,:],cellW[ubcReorder].reshape((-1,1)) ),axis=1) - - ## Write the UBC octree mesh file - with open(fileName,'w') as mshOut: - mshOut.write('{:.0f} {:.0f} {:.0f}\n'.format(nCunderMesh[0],nCunderMesh[1],nCunderMesh[2])) - mshOut.write('{:.4f} {:.4f} {:.4f}\n'.format(tswCorn[0],tswCorn[1],tswCorn[2])) - mshOut.write('{:.3f} {:.3f} {:.3f}\n'.format(smallCell[0],smallCell[1],smallCell[2])) - mshOut.write('{:.0f} \n'.format(nrCells)) - np.savetxt(mshOut,indArr,fmt='%i') - - ## Print the models - # Assign the model('s) to the object - if modelDict is not None: - # indUBCvector = np.argsort(cX0[np.argsort(np.concatenate((cX0[:,0:2],cX0[:,2:3].max() - cX0[:,2:3]),axis=1).view(','.join(3*['float'])),axis=0,order=('f2','f1','f0'))[:,0]].view(','.join(3*['float'])),axis=0,order=('f2','f1','f0'))[:,0] - for item in modelDict.iteritems(): - # Save the data - np.savetxt(item[0],item[1][ubcReorder],fmt='%3.5e') - -def readUBCocTreeFiles(meshFile,modelFiles=None): - """ - Read UBC 3D OcTree mesh and/or modelFiles - - Input: - :param str meshFile: path to the UBC GIF OcTree mesh file to read - :param list of str modelFiles: list of paths modelFiles - - Output: - :return SimPEG.Mesh.TreeMesh mesh: The octree mesh - :return list of ndarray's: models as a list of numpy array's - """ - - ## Read the file lines - fileLines = np.genfromtxt(meshFile,dtype=str,delimiter='\n') - # Extract the data - nCunderMesh = np.array(fileLines[0].split(),dtype=float) - # I think this is the case? - if np.unique(nCunderMesh).size >1: - raise Exception('SimPEG TreeMeshes have the same number of cell in all directions') - tswCorn = np.array(fileLines[1].split(),dtype=float) - smallCell = np.array(fileLines[2].split(),dtype=float) - nrCells = np.array(fileLines[3].split(),dtype=float) - # Read the index array - indArr = np.genfromtxt(fileLines[4::],dtype=np.int) - - ## Calculate simpeg parameters - h1,h2,h3 = [np.ones(nr)*sz for nr,sz in zip(nCunderMesh,smallCell)] - x0 = tswCorn - np.array([0,0,np.sum(h3)]) - # Need to convert the index array to a points list that complies with SimPEG TreeMesh. - # Shift to start at 0 - simpegCellPt = indArr[:,0:-1].copy() - simpegCellPt[:,2] = ( nCunderMesh[-1] + 2) - (simpegCellPt[:,2] + indArr[:,3]) - # Need reindex the z index to be from the bottom-left-close corner and to be from the global bottom. - simpegCellPt = simpegCellPt - np.array([1.,1.,1.]) - - # Calculate the cell level - simpegLevel = np.log2(np.min(nCunderMesh)) - np.log2(indArr[:,3]) - # Make a pointer matrix - simpegPointers = np.concatenate((simpegCellPt,simpegLevel.reshape((-1,1))),axis=1) - - ## Make the tree mesh - from SimPEG.Mesh import TreeMesh - mesh = TreeMesh([h1,h2,h3],x0) - mesh._cells = set([mesh._index(p) for p in simpegPointers.tolist()]) - - # Figure out the reordering - simpegReorder = np.argsort(np.array([mesh._index(i) for i in simpegPointers.tolist()])) - # simpegReorder = np.argsort((np.array([[1,1,1,-1]])*simpegPointers).view(','.join(4*['float'])),axis=0,order=['f3','f2','f1','f0'])[:,0] - - if modelFiles is None: - return mesh - else: - modList = [] - for modFile in modelFiles: - modArr = np.loadtxt(modFile) - if len(modArr.shape) == 1: - modList.append(modArr[simpegReorder]) - else: - modList.append(modArr[simpegReorder,:]) - return mesh, modList - -def readVTRFile(fileName): - """ - Read VTK Rectilinear (vtr xml file) and return SimPEG Tensor mesh and model - - Input: - :param vtrFileName, path to the vtr model file to write to - - Output: - :return SimPEG TensorMesh object - :return SimPEG model dictionary - - """ - # Import - from vtk import vtkXMLRectilinearGridReader as vtrFileReader - from vtk.util.numpy_support import vtk_to_numpy - - # Read the file - vtrReader = vtrFileReader() - vtrReader.SetFileName(fileName) - vtrReader.Update() - vtrGrid = vtrReader.GetOutput() - # Sort information - hx = np.abs(np.diff(vtk_to_numpy(vtrGrid.GetXCoordinates()))) - xR = vtk_to_numpy(vtrGrid.GetXCoordinates())[0] - hy = np.abs(np.diff(vtk_to_numpy(vtrGrid.GetYCoordinates()))) - yR = vtk_to_numpy(vtrGrid.GetYCoordinates())[0] - zD = np.diff(vtk_to_numpy(vtrGrid.GetZCoordinates())) - # Check the direction of hz - if np.all(zD < 0): - hz = np.abs(zD[::-1]) - zR = vtk_to_numpy(vtrGrid.GetZCoordinates())[-1] - else: - hz = np.abs(zD) - zR = vtk_to_numpy(vtrGrid.GetZCoordinates())[0] - x0 = np.array([xR,yR,zR]) - - # Make the SimPEG object - from SimPEG import Mesh - tensMsh = Mesh.TensorMesh([hx,hy,hz],x0) - - # Grap the models - modelDict = {} - for i in np.arange(vtrGrid.GetCellData().GetNumberOfArrays()): - modelName = vtrGrid.GetCellData().GetArrayName(i) - if np.all(zD < 0): - modFlip = vtk_to_numpy(vtrGrid.GetCellData().GetArray(i)) - tM = tensMsh.r(modFlip,'CC','CC','M') - modArr = tensMsh.r(tM[:,:,::-1],'CC','CC','V') - else: - modArr = vtk_to_numpy(vtrGrid.GetCellData().GetArray(i)) - modelDict[modelName] = modArr - - # Return the data - return tensMsh, modelDict - -def writeVTRFile(fileName,mesh,model=None): - """ - Makes and saves a VTK rectilinear file (vtr) for a simpeg Tensor mesh and model. - - Input: - :param str, path to the output vtk file - :param mesh, SimPEG TensorMesh object - mesh to be transfer to VTK - :param model, dictionary of numpy.array - Name('s) and array('s). Match number of cells - - """ - # Import - from vtk import vtkRectilinearGrid as rectGrid, vtkXMLRectilinearGridWriter as rectWriter - from vtk.util.numpy_support import numpy_to_vtk - - # Deal with dimensionalities - if mesh.dim >= 1: - vX = mesh.vectorNx - xD = mesh.nNx - yD,zD = 1,1 - vY, vZ = np.array([0,0]) - if mesh.dim >= 2: - vY = mesh.vectorNy - yD = mesh.nNy - if mesh.dim == 3: - vZ = mesh.vectorNz - zD = mesh.nNz - # Use rectilinear VTK grid. - # Assign the spatial information. - vtkObj = rectGrid() - vtkObj.SetDimensions(xD,yD,zD) - vtkObj.SetXCoordinates(numpy_to_vtk(vX,deep=1)) - vtkObj.SetYCoordinates(numpy_to_vtk(vY,deep=1)) - vtkObj.SetZCoordinates(numpy_to_vtk(vZ,deep=1)) - - # Assign the model('s) to the object - if model is not None: - for item in model.iteritems(): - # Convert numpy array - vtkDoubleArr = numpy_to_vtk(item[1],deep=1) - vtkDoubleArr.SetName(item[0]) - vtkObj.GetCellData().AddArray(vtkDoubleArr) - # Set the active scalar - vtkObj.GetCellData().SetActiveScalars(model.keys()[0]) - vtkObj.Update() - - - # Check the extension of the fileName - ext = os.path.splitext(fileName)[1] - if ext is '': - fileName = fileName + '.vtr' - elif ext not in '.vtr': - raise IOError('{:s} is an incorrect extension, has to be .vtr') - # Write the file. - vtrWriteFilter = rectWriter() - if float(VTK_VERSION.split('.')[0]) >=6: - vtrWriteFilter.SetInputData(vtkObj) - else: - vtuWriteFilter.SetInput(vtuObj) - vtrWriteFilter.SetFileName(fileName) - vtrWriteFilter.Update() - -def writeVTUFile(fileName,ocTreeMesh,modelDict=None): - ''' - Function to write a VTU file from a SimPEG TreeMesh and model. - ''' - from vtk import vtkXMLUnstructuredGridWriter as Writer, VTK_VERSION - from vtk.util.numpy_support import numpy_to_vtk - - # Make the object - vtuObj = simpegOcTree2vtuObj(ocTreeMesh,modelDict) - - # Make the writer - vtuWriteFilter = Writer() - if float(VTK_VERSION.split('.')[0]) >=6: - vtuWriteFilter.SetInputData(vtuObj) - else: - vtuWriteFilter.SetInput(vtuObj) - vtuWriteFilter.SetFileName(fileName) - # Write the file - vtuWriteFilter.Update() - -def simpegOcTree2vtuObj(simpegOcTreeMesh,modelDict=None): - ''' - Convert simpeg OcTree mesh and model to a VTK vtu object. - - ''' - import vtk - from vtk.util.numpy_support import numpy_to_vtk, numpy_to_vtkIdTypeArray - - if str(type(simpegOcTreeMesh)).split()[-1][1:-2] not in 'SimPEG.Mesh.TreeMesh.TreeMesh': - raise IOError('simpegOcTreeMesh is not a SimPEG TreeMesh.') - - # Make the data parts for the vtu object - # Points - try: - ptsMat = simpegOcTreeMesh._gridN + simpegOcTreeMesh.x0 - except: - simpegOcTreeMesh.number() - ptsMat = simpegOcTreeMesh._gridN + simpegOcTreeMesh.x0 - vtkPts = vtk.vtkPoints() - vtkPts.SetData(numpy_to_vtk(ptsMat,deep=True)) - # Cells - cellConn = np.array([c.nodes for c in simpegOcTreeMesh],dtype=np.int64) - - cellsMat = np.concatenate((np.ones((cellConn.shape[0],1),dtype=np.int64)*cellConn.shape[1],cellConn),axis=1).ravel() - cellsArr = vtk.vtkCellArray() - cellsArr.SetNumberOfCells(cellConn.shape[0]) - cellsArr.SetCells(cellConn.shape[0],numpy_to_vtkIdTypeArray(cellsMat,deep=True)) - - # Make the object - vtuObj = vtk.vtkUnstructuredGrid() - vtuObj.SetPoints(vtkPts) - vtuObj.SetCells(vtk.VTK_VOXEL,cellsArr) - # Add the level of refinement as a cell array - cellSides = np.array([np.array(vtuObj.GetCell(i).GetBounds()).reshape((3,2)).dot(np.array([-1, 1])) for i in np.arange(vtuObj.GetNumberOfCells())]) - uniqueLevel, indLevel = np.unique(np.prod(cellSides,axis=1),return_inverse=True) - refineLevelArr = numpy_to_vtk(indLevel.max() - indLevel,deep=1) - refineLevelArr.SetName('octreeLevel') - vtuObj.GetCellData().AddArray(refineLevelArr) - # Assign the model('s) to the object - if modelDict is not None: - for item in modelDict.iteritems(): - # Convert numpy array - vtkDoubleArr = numpy_to_vtk(item[1],deep=1) - vtkDoubleArr.SetName(item[0]) - vtuObj.GetCellData().AddArray(vtkDoubleArr) - - return vtuObj - def ExtractCoreMesh(xyzlim, mesh, meshType='tensor'): """ Extracts Core Mesh from Global mesh diff --git a/tests/mesh/test_MeshIO.py b/tests/mesh/test_MeshIO.py index 52e5740d..e8dc0748 100644 --- a/tests/mesh/test_MeshIO.py +++ b/tests/mesh/test_MeshIO.py @@ -4,11 +4,65 @@ import SimPEG as simpeg from SimPEG.Mesh import TensorMesh, TreeMesh -class TestOcTreeIO(unittest.TestCase): +class TestTensorMeshIO(unittest.TestCase): def setUp(self): h = np.ones(16) - mesh = simpeg.Mesh.TreeMesh([h,2*h,3*h]) + mesh = TensorMesh([h,2*h,3*h]) + self.mesh = mesh + + def test_UBCfiles(self): + + mesh = self.mesh + # Make a vector + vec = np.arange(mesh.nC) + # Write and read + mesh.writeUBC('temp.msh', {'arange.txt':vec}) + meshUBC = TensorMesh.readUBC('temp.msh') + vecUBC = meshUBC.readModelUBC('arange.txt') + + # The mesh + assert mesh.__str__() == meshUBC.__str__() + assert np.sum(mesh.gridCC - meshUBC.gridCC) == 0 + assert np.sum(vec - vecUBC) == 0 + assert np.all(np.array(mesh.h) - np.array(meshUBC.h) == 0) + + + vecUBC = mesh.readModelUBC('arange.txt') + assert np.sum(vec - vecUBC) == 0 + + mesh.writeModelUBC('arange2.txt', vec + 1) + vec2UBC = mesh.readModelUBC('arange2.txt') + assert np.sum(vec + 1 - vec2UBC) == 0 + + print 'IO of UBC tensor mesh files is working' + os.remove('temp.msh') + os.remove('arange.txt') + os.remove('arange2.txt') + + def test_VTKfiles(self): + mesh = self.mesh + vec = np.arange(mesh.nC) + + mesh.writeVTK('temp.vtr', {'arange.txt':vec}) + meshVTR, models = TensorMesh.readVTK('temp.vtr') + + assert mesh.__str__() == meshVTR.__str__() + assert np.all(np.array(mesh.h) - np.array(meshVTR.h) == 0) + + assert 'arange.txt' in models + vecVTK = models['arange.txt'] + assert np.sum(vec - vecVTK) == 0 + + print 'IO of VTR tensor mesh files is working' + os.remove('temp.vtr') + + +class TestOcTreeMeshIO(unittest.TestCase): + + def setUp(self): + h = np.ones(16) + mesh = TreeMesh([h,2*h,3*h]) mesh.refine(3) mesh._refineCell([0,0,0,3]) mesh._refineCell([0,2,0,3]) @@ -19,9 +73,10 @@ class TestOcTreeIO(unittest.TestCase): mesh = self.mesh # Make a vector vec = np.arange(mesh.nC) - # Write aand read - simpeg.Utils.meshutils.writeUBCocTreeFiles('temp.msh',mesh,{'arange.txt':vec}) - meshUBC, vecUBC = simpeg.Utils.meshutils.readUBCocTreeFiles('temp.msh',['arange.txt']) + # Write and read + mesh.writeUBC('temp.msh', {'arange.txt':vec}) + meshUBC = TreeMesh.readUBC('temp.msh') + vecUBC = meshUBC.readModelUBC('arange.txt') # The mesh assert mesh.__str__() == meshUBC.__str__() @@ -35,7 +90,7 @@ class TestOcTreeIO(unittest.TestCase): def test_VTUfiles(self): mesh = self.mesh vec = np.arange(mesh.nC) - simpeg.Utils.meshutils.writeVTUFile('temp.vtu',mesh,{'arange':vec}) + mesh.writeVTK('temp.vtu',{'arange':vec}) print 'Writing of VTU files is working' os.remove('temp.vtu') From b0135082707961b27f5a45cf3ca872b2ad0ed019 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Thu, 28 Jan 2016 22:33:04 -0800 Subject: [PATCH 62/77] Fixes #215 --- SimPEG/EM/TDEM/BaseTDEM.py | 10 +++++++++- SimPEG/Problem.py | 12 ++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SimPEG/EM/TDEM/BaseTDEM.py b/SimPEG/EM/TDEM/BaseTDEM.py index 4d4b39a7..2efb10ec 100644 --- a/SimPEG/EM/TDEM/BaseTDEM.py +++ b/SimPEG/EM/TDEM/BaseTDEM.py @@ -37,13 +37,21 @@ class BaseTDEMProblem(BaseTimeProblem, BaseEMProblem): _FieldsForward_pair = FieldsTDEM #: used for the forward calculation only + waveformType = "STEPOFF" + current = None + + def currentwaveform(self, wave): + self._timeSteps = np.diff(wave[:,0]) + self.current = wave[:,1] + self.waveformType = "GENERAL" + def fields(self, m): if self.verbose: print '%s\nCalculating fields(m)\n%s'%('*'*50,'*'*50) self.curModel = m # Create a fields storage object F = self._FieldsForward_pair(self.mesh, self.survey) for src in self.survey.srcList: - # Set the initial conditions + # Set the initial conditions F[src,:,0] = src.getInitialFields(self.mesh) F = self.forward(m, self.getRHS, F=F) if self.verbose: print '%s\nDone calculating fields(m)\n%s'%('*'*50,'*'*50) diff --git a/SimPEG/Problem.py b/SimPEG/Problem.py index be29ff01..f24b57de 100644 --- a/SimPEG/Problem.py +++ b/SimPEG/Problem.py @@ -32,8 +32,8 @@ class BaseProblem(object): val._assertMatchesPair(self.mapPair) self._mapping = val else: - self._mapping = self.PropMap(val) - + self._mapping = self.PropMap(val) + def __init__(self, mesh, mapping=None, **kwargs): Utils.setKwargs(self, **kwargs) assert isinstance(mesh, Mesh.BaseMesh), "mesh must be a SimPEG.Mesh object." @@ -158,9 +158,6 @@ class BaseProblem(object): class BaseTimeProblem(BaseProblem): """Sets up that basic needs of a time domain problem.""" - - waveformType = "STEPOFF" - current = None @property def timeSteps(self): @@ -187,11 +184,6 @@ class BaseTimeProblem(BaseProblem): self._timeSteps = Utils.meshTensor(value) del self.timeMesh - def currentwaveform(self, wave): - self._timeSteps = np.diff(wave[:,0]) - self.current = wave[:,1] - self.waveformType = "GENERAL" - @property def nT(self): "Number of time steps." From 37d37369d89958f88fb4d9817517739e41578a27 Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Thu, 28 Jan 2016 22:36:39 -0800 Subject: [PATCH 63/77] added example to docs --- SimPEG/Examples/__init__.py | 3 ++- docs/examples/EM_FDEM_1D_Inversion.rst | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 docs/examples/EM_FDEM_1D_Inversion.rst diff --git a/SimPEG/Examples/__init__.py b/SimPEG/Examples/__init__.py index 7ef15451..47721ccf 100644 --- a/SimPEG/Examples/__init__.py +++ b/SimPEG/Examples/__init__.py @@ -1,6 +1,7 @@ # Run this file to add imports. ##### AUTOIMPORTS ##### +import EM_FDEM_1D_Inversion import EM_FDEM_Analytic_MagDipoleWholespace import EM_TDEM_1D_Inversion import FLOW_Richards_1D_Celia1990 @@ -14,7 +15,7 @@ import Mesh_QuadTree_FaceDiv import Mesh_QuadTree_HangingNodes import Mesh_Tensor_Creation -__examples__ = ["EM_FDEM_Analytic_MagDipoleWholespace", "EM_TDEM_1D_Inversion", "FLOW_Richards_1D_Celia1990", "Forward_BasicDirectCurrent", "Inversion_Linear", "Mesh_Basic_PlotImage", "Mesh_Basic_Types", "Mesh_Operators_CahnHilliard", "Mesh_QuadTree_Creation", "Mesh_QuadTree_FaceDiv", "Mesh_QuadTree_HangingNodes", "Mesh_Tensor_Creation"] +__examples__ = ["EM_FDEM_1D_Inversion", "EM_FDEM_Analytic_MagDipoleWholespace", "EM_TDEM_1D_Inversion", "FLOW_Richards_1D_Celia1990", "Forward_BasicDirectCurrent", "Inversion_Linear", "Mesh_Basic_PlotImage", "Mesh_Basic_Types", "Mesh_Operators_CahnHilliard", "Mesh_QuadTree_Creation", "Mesh_QuadTree_FaceDiv", "Mesh_QuadTree_HangingNodes", "Mesh_Tensor_Creation"] ##### AUTOIMPORTS ##### diff --git a/docs/examples/EM_FDEM_1D_Inversion.rst b/docs/examples/EM_FDEM_1D_Inversion.rst new file mode 100644 index 00000000..acbc8cdc --- /dev/null +++ b/docs/examples/EM_FDEM_1D_Inversion.rst @@ -0,0 +1,26 @@ +.. _examples_EM_FDEM_1D_Inversion: + +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + + +EM: FDEM: 1D: Inversion +======================= + +Here we will create and run a FDEM 1D inversion. + + + +.. plot:: + + from SimPEG import Examples + Examples.EM_FDEM_1D_Inversion.run() + +.. literalinclude:: ../../SimPEG/Examples/EM_FDEM_1D_Inversion.py + :language: python + :linenos: From 1457e51fc1e8949a3cf6e765a92a2c7c6c09aab3 Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Thu, 28 Jan 2016 23:44:02 -0800 Subject: [PATCH 64/77] from SimPEG.EM import mu_0 --- SimPEG/EM/FDEM/FDEM.py | 2 +- SimPEG/EM/__init__.py | 2 +- SimPEG/Examples/EM_FDEM_1D_Inversion.py | 4 ++-- SimPEG/Examples/EM_TDEM_1D_Inversion.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SimPEG/EM/FDEM/FDEM.py b/SimPEG/EM/FDEM/FDEM.py index ed440144..4c8b0c89 100644 --- a/SimPEG/EM/FDEM/FDEM.py +++ b/SimPEG/EM/FDEM/FDEM.py @@ -142,7 +142,7 @@ class BaseFDEMProblem(BaseEMProblem): raise Exception('Must be real or imag') ATinv.clean() - + return Utils.mkvc(Jtv) def getSourceTerm(self, freq): diff --git a/SimPEG/EM/__init__.py b/SimPEG/EM/__init__.py index 6a1ca774..565f63a8 100644 --- a/SimPEG/EM/__init__.py +++ b/SimPEG/EM/__init__.py @@ -1,6 +1,6 @@ -# from EM import * import TDEM import FDEM import Base import Analytics import Utils +from scipy.constants import mu_0, epsilon_0 diff --git a/SimPEG/Examples/EM_FDEM_1D_Inversion.py b/SimPEG/Examples/EM_FDEM_1D_Inversion.py index a6478016..ff87b6a6 100644 --- a/SimPEG/Examples/EM_FDEM_1D_Inversion.py +++ b/SimPEG/Examples/EM_FDEM_1D_Inversion.py @@ -1,6 +1,6 @@ from SimPEG import * import SimPEG.EM as EM -from scipy.constants import mu_0 +from SimPEG.EM import mu_0 def run(plotIt=True): @@ -59,7 +59,7 @@ def run(plotIt=True): prb.Solver = MumpsSolver except ImportError, e: prb.Solver = SolverLU - + prb.pair(survey) std = 0.05 diff --git a/SimPEG/Examples/EM_TDEM_1D_Inversion.py b/SimPEG/Examples/EM_TDEM_1D_Inversion.py index f217912d..65ae6669 100644 --- a/SimPEG/Examples/EM_TDEM_1D_Inversion.py +++ b/SimPEG/Examples/EM_TDEM_1D_Inversion.py @@ -1,6 +1,6 @@ from SimPEG import * import SimPEG.EM as EM -from scipy.constants import mu_0 +from SimPEG.EM import mu_0 def run(plotIt=True): From 2adc7d4eb925e95a2262ef21f8c12b6cfbdb9fdc Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Mon, 1 Feb 2016 22:31:12 -0800 Subject: [PATCH 65/77] make travis badge look at master for docs --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 4ce06163..485abf64 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,7 +17,7 @@ SimPEG Documentation :alt: BSD 3 clause license. .. image:: https://img.shields.io/travis/simpeg/simpeg.svg - :target: https://travis-ci.org/simpeg/simpeg + :target: https://travis-ci.org/simpeg/simpeg?branch=master :alt: Travis CI build status .. image:: https://img.shields.io/coveralls/simpeg/simpeg.svg From 32c936c4e32aadc1ffd67d84a2618064b192b27e Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Wed, 3 Feb 2016 19:55:42 -0800 Subject: [PATCH 66/77] add test to ensure examples are up to date --- tests/examples/test_examples.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/examples/test_examples.py b/tests/examples/test_examples.py index 2e4803b1..8decc683 100644 --- a/tests/examples/test_examples.py +++ b/tests/examples/test_examples.py @@ -1,8 +1,25 @@ import unittest import sys +import os from SimPEG import Examples import numpy as np +class compareInitFiles(unittest.TestCase): + def test_compareInitFiles(self): + print 'Checking that __init__.py up-to-date in SimPEG/Examples' + fName = os.path.realpath(__file__) + ExamplesDir = os.path.sep.join(fName.split(os.path.sep)[:-3] + ['SimPEG', 'Examples']) + + files = os.listdir(ExamplesDir) + + pyfiles = [] + [pyfiles.append(py.rstrip('.py')) for py in files if py.endswith('.py') and py != '__init__.py'] + + + didpass = pyfiles == Examples.__examples__ + + self.assertTrue(didpass, "Examples not up to date, run 'python __init__.py' from SimPEG/Examples to update") + def get(test): def test_func(self): print '\nTesting %s.run(plotIt=False)\n'%test @@ -10,11 +27,11 @@ def get(test): self.assertTrue(True) return test_func attrs = dict() + for test in Examples.__examples__: attrs['test_'+test] = get(test) TestExamples = type('TestExamples', (unittest.TestCase,), attrs) - if __name__ == '__main__': unittest.main() From b81b5af46179569ec6821a001eb338002cb5a3f7 Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Wed, 3 Feb 2016 20:23:31 -0800 Subject: [PATCH 67/77] use abspath for travis --- tests/examples/test_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/examples/test_examples.py b/tests/examples/test_examples.py index 8decc683..1e1da875 100644 --- a/tests/examples/test_examples.py +++ b/tests/examples/test_examples.py @@ -7,7 +7,7 @@ import numpy as np class compareInitFiles(unittest.TestCase): def test_compareInitFiles(self): print 'Checking that __init__.py up-to-date in SimPEG/Examples' - fName = os.path.realpath(__file__) + fName = os.path.abspath(__file__) ExamplesDir = os.path.sep.join(fName.split(os.path.sep)[:-3] + ['SimPEG', 'Examples']) files = os.listdir(ExamplesDir) From 5b78d69c6dd8dd71d2e0f918feb203fa5e811e0a Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Thu, 4 Feb 2016 09:11:23 -0800 Subject: [PATCH 68/77] use set diff for testing if all examples have been included --- tests/examples/test_examples.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/examples/test_examples.py b/tests/examples/test_examples.py index 1e1da875..edb5600c 100644 --- a/tests/examples/test_examples.py +++ b/tests/examples/test_examples.py @@ -15,8 +15,11 @@ class compareInitFiles(unittest.TestCase): pyfiles = [] [pyfiles.append(py.rstrip('.py')) for py in files if py.endswith('.py') and py != '__init__.py'] + setdiff = set(pyfiles) - set(Examples.__examples__) - didpass = pyfiles == Examples.__examples__ + print ' Any missing files? ', setdiff + + didpass = (setdiff == set()) self.assertTrue(didpass, "Examples not up to date, run 'python __init__.py' from SimPEG/Examples to update") From 2fb8de708a35067cd8e0301c8eea4cfa31039d3e Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Thu, 4 Feb 2016 18:49:42 -0800 Subject: [PATCH 69/77] use Zero in sdiag instead of returing h to avoid confusion --- SimPEG/Utils/matutils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SimPEG/Utils/matutils.py b/SimPEG/Utils/matutils.py index ba900c72..3a6030fa 100644 --- a/SimPEG/Utils/matutils.py +++ b/SimPEG/Utils/matutils.py @@ -2,7 +2,6 @@ import numpy as np import scipy.sparse as sp from codeutils import isScalar - def mkvc(x, numDims=1): """Creates a vector with the number of dimension specified @@ -41,7 +40,7 @@ def mkvc(x, numDims=1): def sdiag(h): """Sparse diagonal matrix""" if isinstance(h, Zero): - return h + return Zero() return sp.spdiags(mkvc(h), 0, h.size, h.size, format="csr") From a80eac7dc3d9b38e71ba6ccd8ae2ba094414d3a3 Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Sat, 6 Feb 2016 15:05:15 -0800 Subject: [PATCH 70/77] documentation for FDEM.py --- SimPEG/EM/FDEM/FDEM.py | 300 +++++++++++++++++++++++++++++------------ 1 file changed, 217 insertions(+), 83 deletions(-) diff --git a/SimPEG/EM/FDEM/FDEM.py b/SimPEG/EM/FDEM/FDEM.py index 4c8b0c89..843ab245 100644 --- a/SimPEG/EM/FDEM/FDEM.py +++ b/SimPEG/EM/FDEM/FDEM.py @@ -36,7 +36,11 @@ class BaseFDEMProblem(BaseEMProblem): def fields(self, m=None): """ - Solve the forward problem for the fields. + Solve the forward problem for the fields. + + :param numpy.array m: inversion model (nP,) + :rtype numpy.array: + :return F: forward solution """ self.curModel = m @@ -55,7 +59,13 @@ class BaseFDEMProblem(BaseEMProblem): def Jvec(self, m, v, u=None): """ - Sensitivity times a vector + Sensitivity times a vector. + + :param numpy.array m: inversion model (nP,) + :param numpy.array v: vector which we take sensitivity product with (nP,) + :param SimPEG.EM.FDEM.Fields u: fields object + :rtype numpy.array: + :return: Jv (ndata,) """ if u is None: @@ -83,6 +93,7 @@ class BaseFDEMProblem(BaseEMProblem): df_dmFun = getattr(u, '_%sDeriv_m'%rx.projField, None) df_dm = df_dmFun(src, v, adjoint=False) + Df_Dm = np.array(df_dudu_dm + df_dm,dtype=complex) P = lambda v: rx.projectFieldsDeriv(src, self.mesh, u, v) # wrt u, also have wrt m @@ -94,7 +105,13 @@ class BaseFDEMProblem(BaseEMProblem): def Jtvec(self, m, v, u=None): """ - Sensitivity transpose times a vector + Sensitivity transpose times a vector + + :param numpy.array m: inversion model (nP,) + :param numpy.array v: vector which we take adjoint product with (nP,) + :param SimPEG.EM.FDEM.Fields u: fields object + :rtype numpy.array: + :return: Jv (ndata,) """ if u is None: @@ -133,6 +150,7 @@ class BaseFDEMProblem(BaseEMProblem): du_dmT += dfT_dm + # TODO: this should be taken care of by the reciever real_or_imag = rx.projComp if real_or_imag is 'real': Jtv += np.array(du_dmT,dtype=complex).real @@ -147,11 +165,11 @@ class BaseFDEMProblem(BaseEMProblem): def getSourceTerm(self, freq): """ - Evaluates the sources for a given frequency and puts them in matrix form + Evaluates the sources for a given frequency and puts them in matrix form - :param float freq: Frequency - :rtype: numpy.ndarray (nE or nF, nSrc) - :return: S_m, S_e + :param float freq: Frequency + :rtype: (numpy.ndarray, numpy.ndarray) + :return: S_m, S_e (nE or nF, nSrc) """ Srcs = self.survey.getSrcByFreq(freq) if self._eqLocs is 'FE': @@ -175,20 +193,22 @@ class BaseFDEMProblem(BaseEMProblem): class Problem_e(BaseFDEMProblem): """ - By eliminating the magnetic flux density using - - .. math :: - - \mathbf{b} = \\frac{1}{i \omega}\\left(-\mathbf{C} \mathbf{e} + \mathbf{s_m}\\right) - - - we can write Maxwell's equations as a second order system in \\\(\\\mathbf{e}\\\) only: + By eliminating the magnetic flux density using .. math :: - \\left(\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{C}+ i \omega \mathbf{M^e_{\sigma}} \\right)\mathbf{e} = \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f}\mathbf{s_m} -i\omega\mathbf{M^e}\mathbf{s_e} + \mathbf{b} = \\frac{1}{i \omega}\\left(-\mathbf{C} \mathbf{e} + \mathbf{s_m}\\right) - which we solve for \\\(\\\mathbf{e}\\\). + + we can write Maxwell's equations as a second order system in \\\(\\\mathbf{e}\\\) only: + + .. math :: + + \\left(\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{C}+ i \omega \mathbf{M^e_{\sigma}} \\right)\mathbf{e} = \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f}\mathbf{s_m} -i\omega\mathbf{M^e}\mathbf{s_e} + + which we solve for :math:`\mathbf{e}`. + + :param SimPEG.Mesh mesh: mesh """ _fieldType = 'e' @@ -200,13 +220,16 @@ class Problem_e(BaseFDEMProblem): def getA(self, freq): """ - .. math :: - \mathbf{A} = \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{C} + i \omega \mathbf{M^e_{\sigma}} + System matrix + + .. math :: + \mathbf{A} = \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{C} + i \omega \mathbf{M^e_{\sigma}} - :param float freq: Frequency - :rtype: scipy.sparse.csr_matrix - :return: A + :param float freq: Frequency + :rtype: scipy.sparse.csr_matrix + :return: A """ + MfMui = self.MfMui MeSigma = self.MeSigma C = self.mesh.edgeCurl @@ -215,6 +238,20 @@ class Problem_e(BaseFDEMProblem): def getADeriv_m(self, freq, u, v, adjoint=False): + """ + Product of the derivative of our system matrix with respect to the model and a vector + + .. math :: + \\frac{\mathbf{A}(\mathbf{m}) \mathbf{v}}{d \mathbf{m}} = i \omega \\frac{d \mathbf{M^e_{\sigma}}\mathbf{v} }{d\mathbf{m}} + + :param float freq: frequency + :param numpy.ndarray u: solution vector (nE,) + :param numpy.ndarray v: vector to take prodct with (nP,) or (nD,) for adjoint + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: derivative of the system matrix times a vector (nP,) or adjoint (nD,) + """ + dsig_dm = self.curModel.sigmaDeriv dMe_dsig = self.MeSigmaDeriv(u) @@ -225,12 +262,14 @@ class Problem_e(BaseFDEMProblem): def getRHS(self, freq): """ - .. math :: - \mathbf{RHS} = \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f}\mathbf{s_m} -i\omega\mathbf{M_e}\mathbf{s_e} + Right hand side for the system - :param float freq: Frequency - :rtype: numpy.ndarray (nE, nSrc) - :return: RHS + .. math :: + \mathbf{RHS} = \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f}\mathbf{s_m} -i\omega\mathbf{M_e}\mathbf{s_e} + + :param float freq: Frequency + :rtype: numpy.ndarray + :return: RHS (nE, nSrc) """ S_m, S_e = self.getSourceTerm(freq) @@ -242,6 +281,17 @@ class Problem_e(BaseFDEMProblem): return RHS def getRHSDeriv_m(self, freq, src, v, adjoint=False): + """ + Derivative of the right hand side with respect to the model + + :param float freq: frequency + :param SimPEG.EM.FDEM.Src src: FDEM source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of rhs deriv with a vector + """ + C = self.mesh.edgeCurl MfMui = self.MfMui S_mDeriv, S_eDeriv = src.evalDeriv(self, adjoint) @@ -256,20 +306,22 @@ class Problem_e(BaseFDEMProblem): class Problem_b(BaseFDEMProblem): """ - We eliminate \\\(\\\mathbf{e}\\\) using + We eliminate :math:`\mathbf{e}` using - .. math :: + .. math :: - \mathbf{e} = \mathbf{M^e_{\sigma}}^{-1} \\left(\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{b} - \mathbf{s_e}\\right) + \mathbf{e} = \mathbf{M^e_{\sigma}}^{-1} \\left(\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{b} - \mathbf{s_e}\\right) - and solve for \\\(\\\mathbf{b}\\\) using: + and solve for :math:`\mathbf{b}` using: - .. math :: + .. math :: - \\left(\mathbf{C} \mathbf{M^e_{\sigma}}^{-1} \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} + i \omega \\right)\mathbf{b} = \mathbf{s_m} + \mathbf{M^e_{\sigma}}^{-1}\mathbf{M^e}\mathbf{s_e} + \\left(\mathbf{C} \mathbf{M^e_{\sigma}}^{-1} \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} + i \omega \\right)\mathbf{b} = \mathbf{s_m} + \mathbf{M^e_{\sigma}}^{-1}\mathbf{M^e}\mathbf{s_e} - .. note :: - The inverse problem will not work with full anisotropy + .. note :: + The inverse problem will not work with full anisotropy + + :param SimPEG.Mesh mesh: mesh """ _fieldType = 'b' @@ -281,12 +333,14 @@ class Problem_b(BaseFDEMProblem): def getA(self, freq): """ - .. math :: - \mathbf{A} = \mathbf{C} \mathbf{M^e_{\sigma}}^{-1} \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} + i \omega + System matrix - :param float freq: Frequency - :rtype: scipy.sparse.csr_matrix - :return: A + .. math :: + \mathbf{A} = \mathbf{C} \mathbf{M^e_{\sigma}}^{-1} \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} + i \omega + + :param float freq: Frequency + :rtype: scipy.sparse.csr_matrix + :return: A """ MfMui = self.MfMui @@ -302,6 +356,20 @@ class Problem_b(BaseFDEMProblem): def getADeriv_m(self, freq, u, v, adjoint=False): + """ + Product of the derivative of our system matrix with respect to the model and a vector + + .. math :: + \\frac{\mathbf{A}(\mathbf{m}) \mathbf{v}}{d \mathbf{m}} = \mathbf{C} \\frac{\mathbf{M^e_{\sigma}} \mathbf{v}}{d\mathbf{m}} + + :param float freq: frequency + :param numpy.ndarray u: solution vector (nF,) + :param numpy.ndarray v: vector to take prodct with (nP,) or (nD,) for adjoint + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: derivative of the system matrix times a vector (nP,) or adjoint (nD,) + """ + MfMui = self.MfMui C = self.mesh.edgeCurl MeSigmaIDeriv = self.MeSigmaIDeriv @@ -321,12 +389,14 @@ class Problem_b(BaseFDEMProblem): def getRHS(self, freq): """ - .. math :: - \mathbf{RHS} = \mathbf{s_m} + \mathbf{M^e_{\sigma}}^{-1}\mathbf{s_e} + Right hand side for the system - :param float freq: Frequency - :rtype: numpy.ndarray (nE, nSrc) - :return: RHS + .. math :: + \mathbf{RHS} = \mathbf{s_m} + \mathbf{M^e_{\sigma}}^{-1}\mathbf{s_e} + + :param float freq: Frequency + :rtype: numpy.ndarray + :return: RHS (nE, nSrc) """ S_m, S_e = self.getSourceTerm(freq) @@ -342,6 +412,17 @@ class Problem_b(BaseFDEMProblem): return RHS def getRHSDeriv_m(self, freq, src, v, adjoint=False): + """ + Derivative of the right hand side with respect to the model + + :param float freq: frequency + :param SimPEG.EM.FDEM.Src src: FDEM source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of rhs deriv with a vector + """ + C = self.mesh.edgeCurl S_m, S_e = src.eval(self) MfMui = self.MfMui @@ -373,21 +454,22 @@ class Problem_b(BaseFDEMProblem): class Problem_j(BaseFDEMProblem): """ - We eliminate \\\(\\\mathbf{h}\\\) using + We eliminate \\\(\\\mathbf{h}\\\) using - .. math :: + .. math :: - \mathbf{h} = \\frac{1}{i \omega} \mathbf{M_{\mu}^e}^{-1} \\left(-\mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{j} + \mathbf{M^e} \mathbf{s_m} \\right) + \mathbf{h} = \\frac{1}{i \omega} \mathbf{M_{\mu}^e}^{-1} \\left(-\mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{j} + \mathbf{M^e} \mathbf{s_m} \\right) - and solve for \\\(\\\mathbf{j}\\\) using + and solve for \\\(\\\mathbf{j}\\\) using - .. math :: + .. math :: - \\left(\mathbf{C} \mathbf{M_{\mu}^e}^{-1} \mathbf{C}^T \mathbf{M_{\\rho}^f} + i \omega\\right)\mathbf{j} = \mathbf{C} \mathbf{M_{\mu}^e}^{-1} \mathbf{M^e} \mathbf{s_m} -i\omega\mathbf{s_e} + \\left(\mathbf{C} \mathbf{M_{\mu}^e}^{-1} \mathbf{C}^T \mathbf{M_{\\rho}^f} + i \omega\\right)\mathbf{j} = \mathbf{C} \mathbf{M_{\mu}^e}^{-1} \mathbf{M^e} \mathbf{s_m} -i\omega\mathbf{s_e} - .. note:: - This implementation does not yet work with full anisotropy!! + .. note:: + This implementation does not yet work with full anisotropy!! + :param SimPEG.Mesh mesh: mesh """ _fieldType = 'j' @@ -399,12 +481,14 @@ class Problem_j(BaseFDEMProblem): def getA(self, freq): """ - .. math :: - \\mathbf{A} = \\mathbf{C} \\mathbf{M^e_{mu^{-1}}} \\mathbf{C}^T \\mathbf{M^f_{\\sigma^{-1}}} + i\\omega + System matrix - :param float freq: Frequency - :rtype: scipy.sparse.csr_matrix - :return: A + .. math :: + \\mathbf{A} = \\mathbf{C} \\mathbf{M^e_{\\mu^{-1}}} \\mathbf{C}^T \\mathbf{M^f_{\\sigma^{-1}}} + i\\omega + + :param float freq: Frequency + :rtype: scipy.sparse.csr_matrix + :return: A """ MeMuI = self.MeMuI @@ -421,12 +505,20 @@ class Problem_j(BaseFDEMProblem): def getADeriv_m(self, freq, u, v, adjoint=False): """ - In this case, we assume that electrical conductivity, \\\(\\\sigma\\\) is the physical property of interest (i.e. \\\(\\\sigma\\\) = model.transform). Then we want + Product of the derivative of our system matrix with respect to the model and a vector - .. math :: + In this case, we assume that electrical conductivity, :math:`\sigma` is the physical property of interest (i.e. :math:`\sigma` = model.transform). Then we want - \\frac{\mathbf{A(\sigma)} \mathbf{v}}{d \\mathbf{m}} &= \\mathbf{C} \\mathbf{M^e_{mu^{-1}}} \\mathbf{C^T} \\frac{d \\mathbf{M^f_{\\sigma^{-1}}}}{d \\mathbf{m}} - &= \\mathbf{C} \\mathbf{M^e_{mu}^{-1}} \\mathbf{C^T} \\frac{d \\mathbf{M^f_{\\sigma^{-1}}}}{d \\mathbf{\\sigma^{-1}}} \\frac{d \\mathbf{\\sigma^{-1}}}{d \\mathbf{\\sigma}} \\frac{d \\mathbf{\\sigma}}{d \\mathbf{m}} + .. math :: + + \\frac{\mathbf{A(\sigma)} \mathbf{v}}{d \mathbf{m}} = \mathbf{C} \mathbf{M^e_{mu^{-1}}} \mathbf{C^T} \\frac{d \mathbf{M^f_{\sigma^{-1}}}\mathbf{v} }{d \mathbf{m}} + + :param float freq: frequency + :param numpy.ndarray u: solution vector (nF,) + :param numpy.ndarray v: vector to take prodct with (nP,) or (nD,) for adjoint + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: derivative of the system matrix times a vector (nP,) or adjoint (nD,) """ MeMuI = self.MeMuI @@ -446,12 +538,14 @@ class Problem_j(BaseFDEMProblem): def getRHS(self, freq): """ - .. math :: + Right hand side for the system - \mathbf{RHS} = \mathbf{C} \mathbf{M_{\mu}^e}^{-1}\mathbf{s_m} -i\omega \mathbf{s_e} - :param float freq: Frequency - :rtype: numpy.ndarray (nE, nSrc) - :return: RHS + .. math :: + + \mathbf{RHS} = \mathbf{C} \mathbf{M_{\mu}^e}^{-1}\mathbf{s_m} -i\omega \mathbf{s_e} + :param float freq: Frequency + :rtype: numpy.ndarray (nE, nSrc) + :return: RHS """ S_m, S_e = self.getSourceTerm(freq) @@ -466,6 +560,17 @@ class Problem_j(BaseFDEMProblem): return RHS def getRHSDeriv_m(self, freq, src, v, adjoint=False): + """ + Derivative of the right hand side with respect to the model + + :param float freq: frequency + :param SimPEG.EM.FDEM.Src src: FDEM source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of rhs deriv with a vector + """ + C = self.mesh.edgeCurl MeMuI = self.MeMuI S_mDeriv, S_eDeriv = src.evalDeriv(self, adjoint) @@ -489,18 +594,19 @@ class Problem_j(BaseFDEMProblem): class Problem_h(BaseFDEMProblem): """ - We eliminate \\\(\\\mathbf{j}\\\) using + We eliminate \\\(\\\mathbf{j}\\\) using - .. math :: + .. math :: - \mathbf{j} = \mathbf{C} \mathbf{h} - \mathbf{s_e} + \mathbf{j} = \mathbf{C} \mathbf{h} - \mathbf{s_e} - and solve for \\\(\\\mathbf{h}\\\) using + and solve for \\\(\\\mathbf{h}\\\) using - .. math :: + .. math :: - \\left(\mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{C} + i \omega \mathbf{M_{\mu}^e}\\right) \mathbf{h} = \mathbf{M^e} \mathbf{s_m} + \mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{s_e} + \\left(\mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{C} + i \omega \mathbf{M_{\mu}^e}\\right) \mathbf{h} = \mathbf{M^e} \mathbf{s_m} + \mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{s_e} + :param SimPEG.Mesh mesh: mesh """ _fieldType = 'h' @@ -512,13 +618,15 @@ class Problem_h(BaseFDEMProblem): def getA(self, freq): """ - .. math :: + System matrix - \mathbf{A} = \mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{C} + i \omega \mathbf{M_{\mu}^e} + .. math :: - :param float freq: Frequency - :rtype: scipy.sparse.csr_matrix - :return: A + \mathbf{A} = \mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{C} + i \omega \mathbf{M_{\mu}^e} + + :param float freq: Frequency + :rtype: scipy.sparse.csr_matrix + :return: A """ MeMu = self.MeMu @@ -528,6 +636,19 @@ class Problem_h(BaseFDEMProblem): return C.T * (MfRho * C) + 1j*omega(freq)*MeMu def getADeriv_m(self, freq, u, v, adjoint=False): + """ + Product of the derivative of our system matrix with respect to the model and a vector + + .. math:: + \\frac{\mathbf{A}(\mathbf{m}) \mathbf{v}}{d \mathbf{m}} = \mathbf{C}^{\\top} \\frac{d \mathbf{M^f_{\\rho}}\mathbf{v} }{d\mathbf{m}} + + :param float freq: frequency + :param numpy.ndarray u: solution vector (nE,) + :param numpy.ndarray v: vector to take prodct with (nP,) or (nD,) for adjoint + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: derivative of the system matrix times a vector (nP,) or adjoint (nD,) + """ MeMu = self.MeMu C = self.mesh.edgeCurl @@ -539,13 +660,15 @@ class Problem_h(BaseFDEMProblem): def getRHS(self, freq): """ - .. math :: + Right hand side for the system - \mathbf{RHS} = \mathbf{M^e} \mathbf{s_m} + \mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{s_e} + .. math :: - :param float freq: Frequency - :rtype: numpy.ndarray (nE, nSrc) - :return: RHS + \mathbf{RHS} = \mathbf{M^e} \mathbf{s_m} + \mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{s_e} + + :param float freq: Frequency + :rtype: numpy.ndarray + :return: RHS (nE, nSrc) """ S_m, S_e = self.getSourceTerm(freq) @@ -557,6 +680,17 @@ class Problem_h(BaseFDEMProblem): return RHS def getRHSDeriv_m(self, freq, src, v, adjoint=False): + """ + Derivative of the right hand side with respect to the model + + :param float freq: frequency + :param SimPEG.EM.FDEM.Src src: FDEM source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of rhs deriv with a vector + """ + _, S_e = src.eval(self) C = self.mesh.edgeCurl MfRho = self.MfRho From e4fc1283839b1159cc2744ed95a9b8183b6e514d Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Sat, 6 Feb 2016 15:06:23 -0800 Subject: [PATCH 71/77] organizing toctree in EM docs --- docs/em/api_FDEM.rst | 8 +- docs/em/api_TDEM.rst | 299 ++++++++++++++++ docs/em/api_TDEM_derivation.rst | 341 ------------------- docs/em/api_Utils.rst | 19 +- docs/em/index.rst | 36 +- docs/images/simpegEM_noMath.png | Bin 0 -> 57223 bytes docs/images/simpegEM_sensitivity_J_JTvec.png | Bin 0 -> 115806 bytes docs/images/simpegEM_withMath.png | Bin 0 -> 77062 bytes 8 files changed, 322 insertions(+), 381 deletions(-) delete mode 100644 docs/em/api_TDEM_derivation.rst create mode 100644 docs/images/simpegEM_noMath.png create mode 100644 docs/images/simpegEM_sensitivity_J_JTvec.png create mode 100644 docs/images/simpegEM_withMath.png diff --git a/docs/em/api_FDEM.rst b/docs/em/api_FDEM.rst index bf5bdcb4..e60c1bbf 100644 --- a/docs/em/api_FDEM.rst +++ b/docs/em/api_FDEM.rst @@ -121,15 +121,15 @@ For the two formulations, the discretization of the physical properties, fields Note that resistivity is the inverse of conductivity, \\(\\rho = \\sigma^{-1}\\). -E-B Formulation: -**************** +E-B Formulation +--------------- .. math :: \mathbf{C} \mathbf{e} + i \omega \mathbf{b} = \mathbf{s_m} \\ \mathbf{C^T} \mathbf{M^f_{\mu^{-1}}} \mathbf{b} - \mathbf{M^e_\sigma} \mathbf{e} = \mathbf{M^e} \mathbf{s_e} -H-J Formulation: -**************** +H-J Formulation +--------------- .. math :: \mathbf{C^T} \mathbf{M^f_\rho} \mathbf{j} + i \omega \mathbf{M^e_\mu} \mathbf{h} = \mathbf{M^e} \mathbf{s_m} \\ diff --git a/docs/em/api_TDEM.rst b/docs/em/api_TDEM.rst index cbbc48b8..fe3dc613 100644 --- a/docs/em/api_TDEM.rst +++ b/docs/em/api_TDEM.rst @@ -48,6 +48,305 @@ \newcommand{\I}{\vec{I}} +Time Domain Electromagnetics +**************************** + +.. _api_TDEM_derivation: + +Time-Domain EM Derivation +========================= + +The following shows the derivation for the TDEM problem. We use the b-formulation below. +(More to come soon..!) + + +Sensitivity Calculation +----------------------- + +.. math:: + + \begin{align} + \dcurl \e^{(t+1)} + \frac{\b^{(t+1)} - \b^{(t)}}{\delta t} = 0 \\ + \dcurl^\top \MfMui \b^{(t+1)} - \MeSig \e^{(t+1)} = \Me \j_s^{(t+1)} + \end{align} + +Using Gauss-Newton to solve the inverse problem requires the ability to calculate the product of the +Jacobian and a vector, as well as the transpose of the Jacobian times a vector. +The above system can be rewritten as: + +.. math:: + + \begin{align} + \mathbf{A} \u^{(t+1)} + \mathbf{B} \u^{(t)}= \s^{(t+1)} + \end{align} + +where + +.. math:: + + \begin{align} + \mathbf{A} = + \left[ + \begin{array}{cc} + \frac{1}{\delta t} \MfMui & \MfMui\dcurl \\ + \dcurl^\top \MfMui & -\MeSig + \end{array} + \right] \\ + \mathbf{B} = + \left[ + \begin{array}{cc} + -\frac{1}{\delta t} \MfMui & 0 \\ + 0 & 0 + \end{array} + \right] \\ + \u^{(k)} = \left[ + \begin{array}{c} + \b^{(k)}\\ + \e^{(k)} + \end{array} + \right] \\ + \s^{(k)} = \left[ + \begin{array}{c} + 0\\ + \Me \j^{(k)}_s + \end{array} + \right] + \end{align} + +.. note:: + + Here we have multiplied through by \\(\\MfMui\\) to make A and B symmetric! + +The entire time dependent system can be written in a single matrix expression + +.. math:: + + \begin{align} + \hat{\mathbf{A}} \hat{u} = \hat{s} + \end{align} + +where + +.. math:: + + \begin{align} + \mathbf{\hat{A}} = \left[ + \begin{array}{cccc} + A & 0 & & \\ + B & A & & \\ + & \ddots & \ddots & \\ + & & B & A + \end{array} + \right] \\ + \hat{u} = \left[ + \begin{array}{c} + \u^{(1)} \\ + \u^{(2)} \\ + \vdots \\ + \u^{(N)} + \end{array} \right]\\ + \hat{s} = \left[ + \begin{array}{c} + \s^{(1)} - \mathbf{B} \u^{(0)} \\ + \s^{(2)} \\ + \vdots \\ + \s^{(N)} + \end{array} + \right] + \end{align} + +For the fields \\(\\u\\), the measured data is given by + +.. math:: + + \begin{align} + \vec{d} = \mathbf{Q} \u + \end{align} + +The sensitivity matrix **J** is then defined as + +.. math:: + + \begin{align} + \mathbf{J} = \mathbf{Q} \frac{\partial \u}{\partial \sigma} + \end{align} + + +Defining the function \\(\\c(m,\\u)\\) to be + +.. math:: + + \begin{align} + \vec{c}(m,\u) = \hat{\mathbf{A}} \vec{u} - \vec{q} = \vec{0} + \end{align} + +then + +.. math:: + + \begin{align} + \frac{\partial \vec{c}}{\partial m} \partial m + + \frac{\partial \vec{c}}{\partial \u} \partial \vec{u} = 0 + \end{align} + +or + +.. math:: + + \begin{align} + \frac{\partial \vec{u}}{\partial m} = -\left(\frac{\partial \vec{c}}{\partial \u} \right)^{-1} \frac{\partial \vec{c}}{\partial m} + \end{align} + + +Differentiating, we find that + +.. math:: + + \begin{align} + \frac{\partial \vec{c}}{\partial \hat{u}} = \hat{\mathbf{A}} + \end{align} + +and + +.. math:: + + \begin{align} + \frac{\partial \vec{c}}{\partial \sigma} = \mathbf{G}_\sigma = + \left[ + \begin{array}{c} + g_\sigma^{(1)}\\ + g_\sigma^{(2)}\\ + \vdots \\ + g_\sigma^{(N)} + \end{array} + \right] + \end{align} + +with + +.. math:: + + \begin{align} + g_\sigma^{(n)} = + \left[ + \begin{array}{c} + \mathbf{0} \\ + - \diag{\e^{(n)}} \Ace \diag{\vec{V}} + \end{array} + \right] + \end{align} + + +Implementing **J** times a vector +--------------------------------- + +Multiplying **J** onto a vector can be broken into three steps + + +* Compute \\(\\vec{p} = \\mathbf{G}m\\) +* Solve \\(\\hat{\\mathbf{A}} \\vec{y} = \\vec{p}\\) +* Compute \\(\\vec{w} = -\\mathbf{Q} \\vec{y}\\) + +.. math:: + + \begin{align} + \vec{p}^{(n)} = \left[ + \begin{array}{c} + \vec{p}_b^{(n)} \\ + \vec{p}_e^{(n)} + \end{array} + \right] \\ + \vec{p}_b^{(n)} = 0 \\ + \vec{p}_e^{(n)} = - \diag{\e^{(n)}} \Ace \diag{V} m + \end{align} + + +For all time steps: + +.. math:: + + \begin{align} + \frac{1}{\delta t} \MfMui\vec{y}_{b}^{(t+1)} + \MfMui\dcurl \vec{y}_{e}^{(t+1)} + - \frac{1}{\delta t} \MfMui \vec{y}_{b}^{(t)} + = \vec{p}_b^{(t+1)} \\ + \dcurl^\top \MfMui \vec{y}_b^{(t+1)} - \MeSig \vec{y}_e^{(t+1)} = \vec{p}_e^{(t+1)} + \end{align} + +and + +.. math:: + + \begin{align} + \left( \MfMui \dcurl \MeSig^{-1} \dcurl^\top \MfMui + \frac{1}{\delta t} \MfMui \right) \vec{y}_{b}^{(t+1)} = + \frac{1}{\delta t} \MfMui \vec{y}_b^{(t)} + + \MfMui \dcurl \MeSig^{-1} \vec{p}_e^{(t+1)} + \vec{p}_b^{(t+1)} \\ + \vec{y}_e^{(t+1)} = \MeSig^{-1} \dcurl^\top \MfMui \vec{y}_b^{(t+1)} - \MeSig^{-1} \vec{p}_e^{(t+1)} + \end{align} + +.. note:: + + For the first time step, \\\(t=0\\\), the term: \\\(\\frac{1}{\\delta t} \\MfMui \\vec{y}_b^{(0)}\\\) is zero. + + + + +Implementing **J** transpose times a vector +------------------------------------------- + +Multiplying \\(\\mathbf{J}^\\top\\) onto a vector can be broken into three steps + + +* Compute \\(\\vec{p} = \\mathbf{Q}^\\top \\vec{v}\\) +* Solve \\(\\hat{\\mathbf{A}}^\\top \\vec{y} = \\vec{p}\\) +* Compute \\(\\vec{w} = -\\mathbf{G}^\\top y\\) + + +.. math:: + + \mathbf{\hat{A}}^\top = \left[ + \begin{array}{cccc} + A & B & & \\ + & \ddots & \ddots & \\ + & & A & B \\ + & & 0 & A + \end{array} + \right] + +For the all time-steps (going backwards in time): + + +.. math:: + + A \vec{y}^{(t)} + B \vec{y}^{(t+1)} = \vec{p}^{(t)} + + +.. math:: + + \begin{align} + \frac{1}{\delta t} \MfMui\vec{y}_{b}^{(t)} + \MfMui\dcurl \vec{y}_{e}^{(t)} + - \frac{1}{\delta t} \MfMui \vec{y}_{b}^{(t+1)} + = \vec{p}_b^{(t)} \\ + \dcurl^\top \MfMui \vec{y}_b^{(t)} - \MeSig \vec{y}_e^{(t)} = \vec{p}_e^{(t)} + \end{align} + +and + +.. math:: + + \begin{align} + \left( \MfMui \dcurl \MeSig^{-1} \dcurl^\top \MfMui + \frac{1}{\delta t} \MfMui \right) \vec{y}_{b}^{(t)} = + \frac{1}{\delta t} \MfMui \vec{y}_b^{(t+1)} + + \MfMui \dcurl \MeSig^{-1} \vec{p}_e^{(t)} + \vec{p}_b^{(t)} \\ + \vec{y}_e^{(t)} = \MeSig^{-1} \dcurl^\top \MfMui \vec{y}_b^{(t)} - \MeSig^{-1} \vec{p}_e^{(t)} + \end{align} + + +.. note:: + + For the last time step, \\\(t=N\\\), the term: \\\(\\frac{1}{\\delta t} \\MfMui \\vec{y}_b^{(N+1)}\\\) is zero. + + + TDEM - B formulation ==================== diff --git a/docs/em/api_TDEM_derivation.rst b/docs/em/api_TDEM_derivation.rst deleted file mode 100644 index af3fc2fc..00000000 --- a/docs/em/api_TDEM_derivation.rst +++ /dev/null @@ -1,341 +0,0 @@ -.. _api_TDEM_derivation: - - -.. math:: - - \renewcommand{\div}{\nabla\cdot\,} - \newcommand{\grad}{\vec \nabla} - \newcommand{\curl}{{\vec \nabla}\times\,} - \newcommand {\J}{{\vec J}} - \renewcommand{\H}{{\vec H}} - \newcommand {\E}{{\vec E}} - \newcommand{\dcurl}{{\mathbf C}} - \newcommand{\dgrad}{{\mathbf G}} - \newcommand{\Acf}{{\mathbf A_c^f}} - \newcommand{\Ace}{{\mathbf A_c^e}} - \renewcommand{\S}{{\mathbf \Sigma}} - \newcommand{\St}{{\mathbf \Sigma_\tau}} - \newcommand{\T}{{\mathbf T}} - \newcommand{\Tt}{{\mathbf T_\tau}} - \newcommand{\diag}[1]{\,{\sf diag}\left( #1 \right)} - \newcommand{\M}{{\mathbf M}} - \newcommand{\MfMui}{{\M^f_{\mu^{-1}}}} - \newcommand{\MeSig}{{\M^e_\sigma}} - \newcommand{\MeSigInf}{{\M^e_{\sigma_\infty}}} - \newcommand{\MeSigO}{{\M^e_{\sigma_0}}} - \newcommand{\Me}{{\M^e}} - \newcommand{\Mes}[1]{{\M^e_{#1}}} - \newcommand{\Mee}{{\M^e_e}} - \newcommand{\Mej}{{\M^e_j}} - \newcommand{\BigO}[1]{\mathcal{O}\bigl(#1\bigr)} - \newcommand{\bE}{\mathbf{E}} - \newcommand{\bH}{\mathbf{H}} - \newcommand{\B}{\vec{B}} - \newcommand{\D}{\vec{D}} - \renewcommand{\H}{\vec{H}} - \newcommand{\s}{\vec{s}} - \newcommand{\bfJ}{\bf{J}} - \newcommand{\vecm}{\vec m} - \renewcommand{\Re}{\mathsf{Re}} - \renewcommand{\Im}{\mathsf{Im}} - \renewcommand {\j} { {\vec j} } - \newcommand {\h} { {\vec h} } - \renewcommand {\b} { {\vec b} } - \newcommand {\e} { {\vec e} } - \newcommand {\c} { {\vec c} } - \renewcommand {\d} { {\vec d} } - \renewcommand {\u} { {\vec u} } - \newcommand{\I}{\vec{I}} - - -Time-Domain EM Derivation -************************* - -The following shows the derivation for the TDEM problem. We use the b-formulation below. -(More to come soon..!) - - -Sensitivity Calculation -======================= - -.. math:: - - \begin{align} - \dcurl \e^{(t+1)} + \frac{\b^{(t+1)} - \b^{(t)}}{\delta t} = 0 \\ - \dcurl^\top \MfMui \b^{(t+1)} - \MeSig \e^{(t+1)} = \Me \j_s^{(t+1)} - \end{align} - -Using Gauss-Newton to solve the inverse problem requires the ability to calculate the product of the -Jacobian and a vector, as well as the transpose of the Jacobian times a vector. -The above system can be rewritten as: - -.. math:: - - \begin{align} - \mathbf{A} \u^{(t+1)} + \mathbf{B} \u^{(t)}= \s^{(t+1)} - \end{align} - -where - -.. math:: - - \begin{align} - \mathbf{A} = - \left[ - \begin{array}{cc} - \frac{1}{\delta t} \MfMui & \MfMui\dcurl \\ - \dcurl^\top \MfMui & -\MeSig - \end{array} - \right] \\ - \mathbf{B} = - \left[ - \begin{array}{cc} - -\frac{1}{\delta t} \MfMui & 0 \\ - 0 & 0 - \end{array} - \right] \\ - \u^{(k)} = \left[ - \begin{array}{c} - \b^{(k)}\\ - \e^{(k)} - \end{array} - \right] \\ - \s^{(k)} = \left[ - \begin{array}{c} - 0\\ - \Me \j^{(k)}_s - \end{array} - \right] - \end{align} - -.. note:: - - Here we have multiplied through by \\(\\MfMui\\) to make A and B symmetric! - -The entire time dependent system can be written in a single matrix expression - -.. math:: - - \begin{align} - \hat{\mathbf{A}} \hat{u} = \hat{s} - \end{align} - -where - -.. math:: - - \begin{align} - \mathbf{\hat{A}} = \left[ - \begin{array}{cccc} - A & 0 & & \\ - B & A & & \\ - & \ddots & \ddots & \\ - & & B & A - \end{array} - \right] \\ - \hat{u} = \left[ - \begin{array}{c} - \u^{(1)} \\ - \u^{(2)} \\ - \vdots \\ - \u^{(N)} - \end{array} \right]\\ - \hat{s} = \left[ - \begin{array}{c} - \s^{(1)} - \mathbf{B} \u^{(0)} \\ - \s^{(2)} \\ - \vdots \\ - \s^{(N)} - \end{array} - \right] - \end{align} - -For the fields \\(\\u\\), the measured data is given by - -.. math:: - - \begin{align} - \vec{d} = \mathbf{Q} \u - \end{align} - -The sensitivity matrix **J** is then defined as - -.. math:: - - \begin{align} - \mathbf{J} = \mathbf{Q} \frac{\partial \u}{\partial \sigma} - \end{align} - - -Defining the function \\(\\c(m,\\u)\\) to be - -.. math:: - - \begin{align} - \vec{c}(m,\u) = \hat{\mathbf{A}} \vec{u} - \vec{q} = \vec{0} - \end{align} - -then - -.. math:: - - \begin{align} - \frac{\partial \vec{c}}{\partial m} \partial m - + \frac{\partial \vec{c}}{\partial \u} \partial \vec{u} = 0 - \end{align} - -or - -.. math:: - - \begin{align} - \frac{\partial \vec{u}}{\partial m} = -\left(\frac{\partial \vec{c}}{\partial \u} \right)^{-1} \frac{\partial \vec{c}}{\partial m} - \end{align} - - -Differentiating, we find that - -.. math:: - - \begin{align} - \frac{\partial \vec{c}}{\partial \hat{u}} = \hat{\mathbf{A}} - \end{align} - -and - -.. math:: - - \begin{align} - \frac{\partial \vec{c}}{\partial \sigma} = \mathbf{G}_\sigma = - \left[ - \begin{array}{c} - g_\sigma^{(1)}\\ - g_\sigma^{(2)}\\ - \vdots \\ - g_\sigma^{(N)} - \end{array} - \right] - \end{align} - -with - -.. math:: - - \begin{align} - g_\sigma^{(n)} = - \left[ - \begin{array}{c} - \mathbf{0} \\ - - \diag{\e^{(n)}} \Ace \diag{\vec{V}} - \end{array} - \right] - \end{align} - - -Implementing **J** times a vector -================================= - -Multiplying **J** onto a vector can be broken into three steps - - -* Compute \\(\\vec{p} = \\mathbf{G}m\\) -* Solve \\(\\hat{\\mathbf{A}} \\vec{y} = \\vec{p}\\) -* Compute \\(\\vec{w} = -\\mathbf{Q} \\vec{y}\\) - -.. math:: - - \begin{align} - \vec{p}^{(n)} = \left[ - \begin{array}{c} - \vec{p}_b^{(n)} \\ - \vec{p}_e^{(n)} - \end{array} - \right] \\ - \vec{p}_b^{(n)} = 0 \\ - \vec{p}_e^{(n)} = - \diag{\e^{(n)}} \Ace \diag{V} m - \end{align} - - -For all time steps: - -.. math:: - - \begin{align} - \frac{1}{\delta t} \MfMui\vec{y}_{b}^{(t+1)} + \MfMui\dcurl \vec{y}_{e}^{(t+1)} - - \frac{1}{\delta t} \MfMui \vec{y}_{b}^{(t)} - = \vec{p}_b^{(t+1)} \\ - \dcurl^\top \MfMui \vec{y}_b^{(t+1)} - \MeSig \vec{y}_e^{(t+1)} = \vec{p}_e^{(t+1)} - \end{align} - -and - -.. math:: - - \begin{align} - \left( \MfMui \dcurl \MeSig^{-1} \dcurl^\top \MfMui + \frac{1}{\delta t} \MfMui \right) \vec{y}_{b}^{(t+1)} = - \frac{1}{\delta t} \MfMui \vec{y}_b^{(t)} - + \MfMui \dcurl \MeSig^{-1} \vec{p}_e^{(t+1)} + \vec{p}_b^{(t+1)} \\ - \vec{y}_e^{(t+1)} = \MeSig^{-1} \dcurl^\top \MfMui \vec{y}_b^{(t+1)} - \MeSig^{-1} \vec{p}_e^{(t+1)} - \end{align} - -.. note:: - - For the first time step, \\\(t=0\\\), the term: \\\(\\frac{1}{\\delta t} \\MfMui \\vec{y}_b^{(0)}\\\) is zero. - - - - -Implementing **J** transpose times a vector -=========================================== - -Multiplying \\(\\mathbf{J}^\\top\\) onto a vector can be broken into three steps - - -* Compute \\(\\vec{p} = \\mathbf{Q}^\\top \\vec{v}\\) -* Solve \\(\\hat{\\mathbf{A}}^\\top \\vec{y} = \\vec{p}\\) -* Compute \\(\\vec{w} = -\\mathbf{G}^\\top y\\) - - -.. math:: - - \mathbf{\hat{A}}^\top = \left[ - \begin{array}{cccc} - A & B & & \\ - & \ddots & \ddots & \\ - & & A & B \\ - & & 0 & A - \end{array} - \right] - -For the all time-steps (going backwards in time): - - -.. math:: - - A \vec{y}^{(t)} + B \vec{y}^{(t+1)} = \vec{p}^{(t)} - - -.. math:: - - \begin{align} - \frac{1}{\delta t} \MfMui\vec{y}_{b}^{(t)} + \MfMui\dcurl \vec{y}_{e}^{(t)} - - \frac{1}{\delta t} \MfMui \vec{y}_{b}^{(t+1)} - = \vec{p}_b^{(t)} \\ - \dcurl^\top \MfMui \vec{y}_b^{(t)} - \MeSig \vec{y}_e^{(t)} = \vec{p}_e^{(t)} - \end{align} - -and - -.. math:: - - \begin{align} - \left( \MfMui \dcurl \MeSig^{-1} \dcurl^\top \MfMui + \frac{1}{\delta t} \MfMui \right) \vec{y}_{b}^{(t)} = - \frac{1}{\delta t} \MfMui \vec{y}_b^{(t+1)} - + \MfMui \dcurl \MeSig^{-1} \vec{p}_e^{(t)} + \vec{p}_b^{(t)} \\ - \vec{y}_e^{(t)} = \MeSig^{-1} \dcurl^\top \MfMui \vec{y}_b^{(t)} - \MeSig^{-1} \vec{p}_e^{(t)} - \end{align} - - -.. note:: - - For the last time step, \\\(t=N\\\), the term: \\\(\\frac{1}{\\delta t} \\MfMui \\vec{y}_b^{(N+1)}\\\) is zero. diff --git a/docs/em/api_Utils.rst b/docs/em/api_Utils.rst index 8ae98855..ac8f9d34 100644 --- a/docs/em/api_Utils.rst +++ b/docs/em/api_Utils.rst @@ -4,6 +4,16 @@ simpegEM Utilities SimPEG for EM provides a few EM specific utility codes, sources, and analytic functions. +Utilities for Electromagnetics +============================== + +.. automodule:: SimPEG.EM.Utils + :show-inheritance: + :members: + :undoc-members: + :inherited-members: + + Analytic Functions - Time ========================= @@ -22,12 +32,3 @@ Analytic Functions - Frequency :members: :undoc-members: :inherited-members: - - -Sources -======= - -.. autoclass:: SimPEG.EM.FDEM.SrcFDEM.MagDipole - :show-inheritance: - :members: - :undoc-members: diff --git a/docs/em/index.rst b/docs/em/index.rst index fdf4dc19..a86ebb69 100644 --- a/docs/em/index.rst +++ b/docs/em/index.rst @@ -3,42 +3,24 @@ Electromagnetics ================ `SimPEG.EM` uses SimPEG as the framework for the forward and inverse -electromagnetics geophysical problems. +electromagnetics geophysical problems. -Time Domian Electromagnetics ----------------------------- - -.. toctree:: - :maxdepth: 2 - - api_TDEM_derivation +To solve for predicted data, we follow the framework shown below. The model is +what we invert for. This is mapped to a physical property on the simulation +mesh. A source which is used to excite the system is specified. Having a model +and a source, we can solve Maxwell's equations for fields. We sample these +fields with recievers to give us predicted data. -Code for Time Domian Electromagnetics -------------------------------------- +.. image:: ../images/simpegEM_noMath.png + :scale: 50% -.. toctree:: - :maxdepth: 2 - - api_TDEM - -Frequency Domian Electromagnetics ---------------------------------- .. toctree:: :maxdepth: 2 api_FDEM - - -Utility Codes -------------- - -.. toctree:: - :maxdepth: 2 - + api_TDEM api_Utils - - diff --git a/docs/images/simpegEM_noMath.png b/docs/images/simpegEM_noMath.png new file mode 100644 index 0000000000000000000000000000000000000000..958f7003aa7486d7b37b6f3eba22291445ef1be5 GIT binary patch literal 57223 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 literal 0 HcmV?d00001 diff --git a/docs/images/simpegEM_sensitivity_J_JTvec.png b/docs/images/simpegEM_sensitivity_J_JTvec.png new file mode 100644 index 0000000000000000000000000000000000000000..f2e2d0e40b535c1323573998ff819bdd72ca5852 GIT binary patch literal 115806 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 Su^g(l4er!NbEW{>tMi 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 R>k#lyPD<%P*1gAn{14tVu`mDt literal 0 HcmV?d00001 diff --git a/docs/images/simpegEM_withMath.png b/docs/images/simpegEM_withMath.png new file mode 100644 index 0000000000000000000000000000000000000000..4571c058ee18ff07718c6ecd2f8d3e57b56f2f05 GIT binary patch literal 77062 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 ?yXq*Ts_)h3xVAtBM({787geU#y)uSz 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�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 literal 0 HcmV?d00001 From e7b69dccfd13bd57ea922446dd0617a81ae307ec Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Sun, 7 Feb 2016 11:31:00 -0800 Subject: [PATCH 72/77] FDEM Fields object docs --- SimPEG/EM/FDEM/FieldsFDEM.py | 508 ++++++++++++++++++++++++++++++++++- 1 file changed, 507 insertions(+), 1 deletion(-) diff --git a/SimPEG/EM/FDEM/FieldsFDEM.py b/SimPEG/EM/FDEM/FieldsFDEM.py index 8f6fafe9..9f54855c 100644 --- a/SimPEG/EM/FDEM/FieldsFDEM.py +++ b/SimPEG/EM/FDEM/FieldsFDEM.py @@ -7,11 +7,39 @@ from SimPEG.Utils import Zero, Identity class Fields(SimPEG.Problem.Fields): - """Fancy Field Storage for a FDEM survey.""" + """ + + Fancy Field Storage for a FDEM survey. Only one field type is stored for + each problem, the rest are computed. The fields obejct acts like an array and is indexed by + + .. code-block:: python + + f = problem.fields(m) + e = f[srcList,'e'] + b = f[srcList,'b'] + + If accessing all sources for a given field, use the :code:`:` + + .. code-block:: python + + f = problem.fields(m) + e = f[:,'e'] + b = f[:,'b'] + + The array returned will be size (nE or nF, nSrcs :math:`\\times` nFrequencies) + """ + knownFields = {} dtype = complex class Fields_e(Fields): + """ + Fields object for Problem_e. + + :param Mesh mesh: mesh + :param Survey survey: survey + """ + knownFields = {'eSolution':'E'} aliasFields = { 'e' : ['eSolution','E','_e'], @@ -30,6 +58,15 @@ class Fields_e(Fields): self._edgeCurl = self.survey.prob.mesh.edgeCurl def _ePrimary(self, eSolution, srcList): + """ + Primary electric field from source + + :param numpy.ndarray eSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: primary electric field as defined by the sources + """ + ePrimary = np.zeros_like(eSolution) for i, src in enumerate(srcList): ep = src.ePrimary(self.prob) @@ -37,19 +74,67 @@ class Fields_e(Fields): return ePrimary def _eSecondary(self, eSolution, srcList): + """ + Secondary electric field is the thing we solved for + + :param numpy.ndarray eSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: secondary electric field + """ + return eSolution def _e(self, eSolution, srcList): + """ + Total electric field is sum of primary and secondary + + :param numpy.ndarray eSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: total electric field + """ + return self._ePrimary(eSolution,srcList) + self._eSecondary(eSolution,srcList) def _eDeriv_u(self, src, v, adjoint = False): + """ + Derivative of the total electric field with respect to the thing we + solved for + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the derivative of the electric field with respect to the field we solved for with a vector + """ + return Identity()*v def _eDeriv_m(self, src, v, adjoint = False): + """ + Derivative of the total electric field with respect to the inversion model. Here, we assume that the primary does not depend on the model. + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: SimPEG.Utils.Zero + :return: product of the electric field derivative with respect to the inversion model with a vector + """ + # assuming primary does not depend on the model return Zero() def _bPrimary(self, eSolution, srcList): + """ + Primary magnetic flux density from source + + :param numpy.ndarray eSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: primary magnetic flux density as defined by the sources + """ + bPrimary = np.zeros([self._edgeCurl.shape[0],eSolution.shape[1]],dtype = complex) for i, src in enumerate(srcList): bp = src.bPrimary(self.prob) @@ -57,6 +142,15 @@ class Fields_e(Fields): return bPrimary def _bSecondary(self, eSolution, srcList): + """ + Secondary magnetic flux density from eSolution + + :param numpy.ndarray eSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: secondary magnetic flux density + """ + C = self._edgeCurl b = (C * eSolution) for i, src in enumerate(srcList): @@ -66,29 +160,85 @@ class Fields_e(Fields): return b def _bSecondaryDeriv_u(self, src, v, adjoint = False): + """ + Derivative of the secondary magnetic flux density with respect to the thing we solved for + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the derivative of the secondary magnetic flux density with respect to the field we solved for with a vector + """ + C = self._edgeCurl if adjoint: return - 1./(1j*omega(src.freq)) * (C.T * v) return - 1./(1j*omega(src.freq)) * (C * v) def _bSecondaryDeriv_m(self, src, v, adjoint = False): + """ + Derivative of the secondary magnetic flux density with respect to the inversion model. + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the secondary magnetic flux density derivative with respect to the inversion model with a vector + """ + S_mDeriv, _ = src.evalDeriv(self.prob, adjoint) S_mDeriv = S_mDeriv(v) return 1./(1j * omega(src.freq)) * S_mDeriv def _b(self, eSolution, srcList): + """ + Total magnetic flux density is sum of primary and secondary + + :param numpy.ndarray eSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: total magnetic flux density + """ + return self._bPrimary(eSolution, srcList) + self._bSecondary(eSolution, srcList) def _bDeriv_u(self, src, v, adjoint=False): + """ + Derivative of the total magnetic flux density with respect to the thing we solved for + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the derivative of the magnetic flux density with respect to the field we solved for with a vector + """ + # Primary does not depend on u return self._bSecondaryDeriv_u(src, v, adjoint) def _bDeriv_m(self, src, v, adjoint=False): + """ + Derivative of the total magnetic flux density with respect to the inversion model. + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: SimPEG.Utils.Zero + :return: product of the magnetic flux density derivative with respect to the inversion model with a vector + """ + # Assuming the primary does not depend on the model return self._bSecondaryDeriv_m(src, v, adjoint) class Fields_b(Fields): + """ + Fields object for Problem_b. + + :param Mesh mesh: mesh + :param Survey survey: survey + """ + knownFields = {'bSolution':'F'} aliasFields = { 'b' : ['bSolution','F','_b'], @@ -111,6 +261,15 @@ class Fields_b(Fields): self._Me = self.survey.prob.Me def _bPrimary(self, bSolution, srcList): + """ + Primary magnetic flux density from source + + :param numpy.ndarray bSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: primary electric field as defined by the sources + """ + bPrimary = np.zeros_like(bSolution) for i, src in enumerate(srcList): bp = src.bPrimary(self.prob) @@ -118,19 +277,66 @@ class Fields_b(Fields): return bPrimary def _bSecondary(self, bSolution, srcList): + """ + Secondary magnetic flux density is the thing we solved for + + :param numpy.ndarray bSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: secondary magnetic flux density + """ + return bSolution def _b(self, bSolution, srcList): + """ + Total magnetic flux density is sum of primary and secondary + + :param numpy.ndarray bSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: total magnetic flux density + """ + return self._bPrimary(bSolution, srcList) + self._bSecondary(bSolution, srcList) def _bDeriv_u(self, src, v, adjoint=False): + """ + Derivative of the total magnetic flux density with respect to the thing we + solved for + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the derivative of the magnetic flux density with respect to the field we solved for with a vector + """ return Identity()*v def _bDeriv_m(self, src, v, adjoint=False): + """ + Derivative of the total magnetic flux density with respect to the inversion model. Here, we assume that the primary does not depend on the model. + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: SimPEG.Utils.Zero + :return: product of the magnetic flux density derivative with respect to the inversion model with a vector + """ + # assuming primary does not depend on the model return Zero() def _ePrimary(self, bSolution, srcList): + """ + Primary electric field from source + + :param numpy.ndarray bSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: primary electric field as defined by the sources + """ + ePrimary = np.zeros([self._edgeCurl.shape[1],bSolution.shape[1]],dtype = complex) for i,src in enumerate(srcList): ep = src.ePrimary(self.prob) @@ -138,6 +344,15 @@ class Fields_b(Fields): return ePrimary def _eSecondary(self, bSolution, srcList): + """ + Secondary electric field from bSolution + + :param numpy.ndarray bSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: secondary electric field + """ + e = self._MeSigmaI * ( self._edgeCurl.T * ( self._MfMui * bSolution)) for i,src in enumerate(srcList): _,S_e = src.eval(self.prob) @@ -145,12 +360,32 @@ class Fields_b(Fields): return e def _eSecondaryDeriv_u(self, src, v, adjoint=False): + """ + Derivative of the secondary electric field with respect to the thing we solved for + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the derivative of the secondary electric field with respect to the field we solved for with a vector + """ + if not adjoint: return self._MeSigmaI * ( self._edgeCurl.T * ( self._MfMui * v) ) else: return self._MfMui.T * (self._edgeCurl * (self._MeSigmaI.T * v)) def _eSecondaryDeriv_m(self, src, v, adjoint=False): + """ + Derivative of the secondary electric field with respect to the inversion model + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the derivative of the secondary electric field with respect to the model with a vector + """ + bSolution = self[[src],'bSolution'] _,S_e = src.eval(self.prob) Me = self._Me @@ -174,17 +409,53 @@ class Fields_b(Fields): return de_dm def _e(self, bSolution, srcList): + """ + Total electric field is sum of primary and secondary + + :param numpy.ndarray eSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: total electric field + """ + return self._ePrimary(bSolution, srcList) + self._eSecondary(bSolution, srcList) def _eDeriv_u(self, src, v, adjoint=False): + """ + Derivative of the total electric field with respect to the thing we solved for + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the derivative of the electric field with respect to the field we solved for with a vector + """ + return self._eSecondaryDeriv_u(src, v, adjoint) def _eDeriv_m(self, src, v, adjoint=False): + """ + Derivative of the total electric field density with respect to the inversion model. + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the electric field derivative with respect to the inversion model with a vector + """ + # assuming primary doesn't depend on model return self._eSecondaryDeriv_m(src, v, adjoint) class Fields_j(Fields): + """ + Fields object for Problem_j. + + :param Mesh mesh: mesh + :param Survey survey: survey + """ + knownFields = {'jSolution':'F'} aliasFields = { 'j' : ['jSolution','F','_j'], @@ -207,6 +478,15 @@ class Fields_j(Fields): self._Me = self.survey.prob.Me def _jPrimary(self, jSolution, srcList): + """ + Primary current density from source + + :param numpy.ndarray jSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: primary current density as defined by the sources + """ + jPrimary = np.zeros_like(jSolution,dtype = complex) for i, src in enumerate(srcList): jp = src.jPrimary(self.prob) @@ -214,19 +494,66 @@ class Fields_j(Fields): return jPrimary def _jSecondary(self, jSolution, srcList): + """ + Secondary current density is the thing we solved for + + :param numpy.ndarray jSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: secondary current density + """ + return jSolution def _j(self, jSolution, srcList): + """ + Total current density is sum of primary and secondary + + :param numpy.ndarray jSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: total current density + """ + return self._jPrimary(jSolution, srcList) + self._jSecondary(jSolution, srcList) def _jDeriv_u(self, src, v, adjoint=False): + """ + Derivative of the total current density with respect to the thing we + solved for + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the derivative of the current density with respect to the field we solved for with a vector + """ + return Identity()*v def _jDeriv_m(self, src, v, adjoint=False): + """ + Derivative of the total current density with respect to the inversion model. Here, we assume that the primary does not depend on the model. + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: SimPEG.Utils.Zero + :return: product of the current density derivative with respect to the inversion model with a vector + """ # assuming primary does not depend on the model return Zero() def _hPrimary(self, jSolution, srcList): + """ + Primary magnetic field from source + + :param numpy.ndarray hSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: primary magnetic field as defined by the sources + """ + hPrimary = np.zeros([self._edgeCurl.shape[1],jSolution.shape[1]],dtype = complex) for i, src in enumerate(srcList): hp = src.hPrimary(self.prob) @@ -234,6 +561,15 @@ class Fields_j(Fields): return hPrimary def _hSecondary(self, jSolution, srcList): + """ + Secondary magnetic field from bSolution + + :param numpy.ndarray jSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: secondary magnetic field + """ + h = self._MeMuI * (self._edgeCurl.T * (self._MfRho * jSolution) ) for i, src in enumerate(srcList): h[:,i] *= -1./(1j*omega(src.freq)) @@ -242,12 +578,32 @@ class Fields_j(Fields): return h def _hSecondaryDeriv_u(self, src, v, adjoint=False): + """ + Derivative of the secondary magnetic field with respect to the thing we solved for + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the derivative of the secondary magnetic field with respect to the field we solved for with a vector + """ + if not adjoint: return -1./(1j*omega(src.freq)) * self._MeMuI * (self._edgeCurl.T * (self._MfRho * v) ) elif adjoint: return -1./(1j*omega(src.freq)) * self._MfRho.T * (self._edgeCurl * ( self._MeMuI.T * v)) def _hSecondaryDeriv_m(self, src, v, adjoint=False): + """ + Derivative of the secondary magnetic field with respect to the inversion model + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the derivative of the secondary magnetic field with respect to the model with a vector + """ + jSolution = self[[src],'jSolution'] MeMuI = self._MeMuI C = self._edgeCurl @@ -272,17 +628,53 @@ class Fields_j(Fields): def _h(self, jSolution, srcList): + """ + Total magnetic field is sum of primary and secondary + + :param numpy.ndarray eSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: total magnetic field + """ + return self._hPrimary(jSolution, srcList) + self._hSecondary(jSolution, srcList) def _hDeriv_u(self, src, v, adjoint=False): + """ + Derivative of the total magnetic field with respect to the thing we solved for + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the derivative of the magnetic field with respect to the field we solved for with a vector + """ + return self._hSecondaryDeriv_u(src, v, adjoint) def _hDeriv_m(self, src, v, adjoint=False): + """ + Derivative of the total magnetic field density with respect to the inversion model. + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the magnetic field derivative with respect to the inversion model with a vector + """ + # assuming the primary doesn't depend on the model return self._hSecondaryDeriv_m(src, v, adjoint) class Fields_h(Fields): + """ + Fields object for Problem_h. + + :param Mesh mesh: mesh + :param Survey survey: survey + """ + knownFields = {'hSolution':'E'} aliasFields = { 'h' : ['hSolution','E','_h'], @@ -303,6 +695,15 @@ class Fields_h(Fields): self._MfRho = self.survey.prob.MfRho def _hPrimary(self, hSolution, srcList): + """ + Primary magnetic field from source + + :param numpy.ndarray eSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: primary magnetic field as defined by the sources + """ + hPrimary = np.zeros_like(hSolution,dtype = complex) for i, src in enumerate(srcList): hp = src.hPrimary(self.prob) @@ -310,19 +711,67 @@ class Fields_h(Fields): return hPrimary def _hSecondary(self, hSolution, srcList): + """ + Secondary magnetic field is the thing we solved for + + :param numpy.ndarray hSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: secondary magnetic field + """ + return hSolution def _h(self, hSolution, srcList): + """ + Total magnetic field is sum of primary and secondary + + :param numpy.ndarray hSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: total magnetic field + """ + return self._hPrimary(hSolution, srcList) + self._hSecondary(hSolution, srcList) def _hDeriv_u(self, src, v, adjoint=False): + """ + Derivative of the total magnetic field with respect to the thing we + solved for + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the derivative of the magnetic field with respect to the field we solved for with a vector + """ + return Identity()*v def _hDeriv_m(self, src, v, adjoint=False): + """ + Derivative of the total magnetic field with respect to the inversion model. Here, we assume that the primary does not depend on the model. + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: SimPEG.Utils.Zero + :return: product of the magnetic field derivative with respect to the inversion model with a vector + """ + # assuming primary does not depend on the model return Zero() def _jPrimary(self, hSolution, srcList): + """ + Primary current density from source + + :param numpy.ndarray hSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: primary current density as defined by the sources + """ + jPrimary = np.zeros([self._edgeCurl.shape[0], hSolution.shape[1]], dtype = complex) for i, src in enumerate(srcList): jp = src.jPrimary(self.prob) @@ -330,6 +779,15 @@ class Fields_h(Fields): return jPrimary def _jSecondary(self, hSolution, srcList): + """ + Secondary current density from eSolution + + :param numpy.ndarray hSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: secondary current density + """ + j = self._edgeCurl*hSolution for i, src in enumerate(srcList): _,S_e = src.eval(self.prob) @@ -337,22 +795,70 @@ class Fields_h(Fields): return j def _jSecondaryDeriv_u(self, src, v, adjoint=False): + """ + Derivative of the secondary current density with respect to the thing we solved for + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the derivative of the secondary current density with respect to the field we solved for with a vector + """ + if not adjoint: return self._edgeCurl*v elif adjoint: return self._edgeCurl.T*v def _jSecondaryDeriv_m(self, src, v, adjoint=False): + """ + Derivative of the secondary current density with respect to the inversion model. + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the secondary current density derivative with respect to the inversion model with a vector + """ + _,S_eDeriv = src.evalDeriv(self.prob, adjoint) S_eDeriv = S_eDeriv(v) return -S_eDeriv def _j(self, hSolution, srcList): + """ + Total current density is sum of primary and secondary + + :param numpy.ndarray eSolution: field we solved for + :param list srcList: list of sources + :rtype: numpy.ndarray + :return: total current density + """ + return self._jPrimary(hSolution, srcList) + self._jSecondary(hSolution, srcList) def _jDeriv_u(self, src, v, adjoint=False): + """ + Derivative of the total current density with respect to the thing we solved for + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of the derivative of the current density with respect to the field we solved for with a vector + """ return self._jSecondaryDeriv_u(src,v,adjoint) def _jDeriv_m(self, src, v, adjoint=False): + """ + Derivative of the total current density with respect to the inversion model. + + :param SimPEG.EM.FDEM.Src src: source + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: SimPEG.Utils.Zero + :return: product of the current density with respect to the inversion model with a vector + """ + # assuming the primary does not depend on the model return self._jSecondaryDeriv_m(src,v,adjoint) From cb9a70bacc41900107d2fa7ea7f80e14f260f6ce Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Sun, 7 Feb 2016 12:53:57 -0800 Subject: [PATCH 73/77] docs for source --- SimPEG/EM/FDEM/SrcFDEM.py | 293 +++++++++++++++++++++++++++++++++++--- 1 file changed, 274 insertions(+), 19 deletions(-) diff --git a/SimPEG/EM/FDEM/SrcFDEM.py b/SimPEG/EM/FDEM/SrcFDEM.py index b29768ac..ba57d39b 100644 --- a/SimPEG/EM/FDEM/SrcFDEM.py +++ b/SimPEG/EM/FDEM/SrcFDEM.py @@ -1,55 +1,141 @@ from SimPEG import Survey, Problem, Utils, np, sp from scipy.constants import mu_0 from SimPEG.EM.Utils import * -from SimPEG.Utils import Zero -# from SurveyFDEM import Rx - +from SimPEG.Utils import Zero class BaseSrc(Survey.BaseSrc): + """ + Base source class for FDEM Survey + """ + freq = None - # rxPair = Rx + # rxPair = RxFDEM integrate = True def eval(self, prob): + """ + Evaluate the source terms. + - :math:`S_m` : magnetic source term + - :math:`S_e` : electric source term + + :param Problem prob: FDEM Problem + :rtype: (numpy.ndarray, numpy.ndarray) + :return: tuple with magnetic source term and electric source term + """ S_m = self.S_m(prob) S_e = self.S_e(prob) return S_m, S_e - def evalDeriv(self, prob, v, adjoint=False): - return lambda v: self.S_mDeriv(prob,v,adjoint), lambda v: self.S_eDeriv(prob,v,adjoint) + def evalDeriv(self, prob, v=None, adjoint=False): + """ + Derivatives of the source terms with respect to the inversion model + - :code:`S_mDeriv` : derivative of the magnetic source term + - :code:`S_eDeriv` : derivative of the electric source term + + :param Problem prob: FDEM Problem + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: (numpy.ndarray, numpy.ndarray) + :return: tuple with magnetic source term and electric source term derivatives times a vector + """ + if v is not None: + return self.S_mDeriv(prob,v,adjoint), self.S_eDeriv(prob,v,adjoint) + else: + return lambda v: self.S_mDeriv(prob,v,adjoint), lambda v: self.S_eDeriv(prob,v,adjoint) def bPrimary(self, prob): + """ + Primary magnetic flux density + + :param Problem prob: FDEM Problem + :rtype: numpy.ndarray + :return: primary magnetic flux density + """ return Zero() def hPrimary(self, prob): + """ + Primary magnetic field + + :param Problem prob: FDEM Problem + :rtype: numpy.ndarray + :return: primary magnetic field + """ return Zero() def ePrimary(self, prob): + """ + Primary electric field + + :param Problem prob: FDEM Problem + :rtype: numpy.ndarray + :return: primary electric field + """ return Zero() def jPrimary(self, prob): + """ + Primary current density + + :param Problem prob: FDEM Problem + :rtype: numpy.ndarray + :return: primary current density + """ return Zero() def S_m(self, prob): + """ + Magnetic source term + + :param Problem prob: FDEM Problem + :rtype: numpy.ndarray + :return: magnetic source term on mesh + """ return Zero() def S_e(self, prob): + """ + Electric source term + + :param Problem prob: FDEM Problem + :rtype: numpy.ndarray + :return: electric source term on mesh + """ return Zero() def S_mDeriv(self, prob, v, adjoint = False): + """ + Derivative of magnetic source term with respect to the inversion model + + :param Problem prob: FDEM Problem + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of magnetic source term derivative with a vector + """ + return Zero() def S_eDeriv(self, prob, v, adjoint = False): + """ + Derivative of electric source term with respect to the inversion model + + :param Problem prob: FDEM Problem + :param numpy.ndarray v: vector to take product with + :param bool adjoint: adjoint? + :rtype: numpy.ndarray + :return: product of electric source term derivative with a vector + """ return Zero() class RawVec_e(BaseSrc): """ - RawVec electric source. It is defined by the user provided vector S_e + RawVec electric source. It is defined by the user provided vector S_e - :param numpy.array S_e: electric source term - :param float freq: frequency - :param rxList: receiver list + :param list rxList: receiver list + :param float freq: frequency + :param numpy.array S_e: electric source term """ def __init__(self, rxList, freq, S_e): #, ePrimary=None, bPrimary=None, hPrimary=None, jPrimary=None): @@ -58,16 +144,17 @@ class RawVec_e(BaseSrc): BaseSrc.__init__(self, rxList) def S_e(self, prob): + return self._S_e class RawVec_m(BaseSrc): """ - RawVec magnetic source. It is defined by the user provided vector S_m + RawVec magnetic source. It is defined by the user provided vector S_m - :param numpy.array S_m: magnetic source term - :param float freq: frequency - :param rxList: receiver list + :param float freq: frequency + :param rxList: receiver list + :param numpy.array S_m: magnetic source term """ def __init__(self, rxList, freq, S_m, integrate = True): #ePrimary=Zero(), bPrimary=Zero(), hPrimary=Zero(), jPrimary=Zero()): @@ -78,17 +165,24 @@ class RawVec_m(BaseSrc): BaseSrc.__init__(self, rxList) def S_m(self, prob): + """ + Magnetic source term + + :param Problem prob: FDEM Problem + :rtype: numpy.ndarray + :return: magnetic source term on mesh + """ return self._S_m class RawVec(BaseSrc): """ - RawVec source. It is defined by the user provided vectors S_m, S_e + RawVec source. It is defined by the user provided vectors S_m, S_e - :param numpy.array S_m: magnetic source term - :param numpy.array S_e: electric source term - :param float freq: frequency - :param rxList: receiver list + :param rxList: receiver list + :param float freq: frequency + :param numpy.array S_m: magnetic source term + :param numpy.array S_e: electric source term """ def __init__(self, rxList, freq, S_m, S_e, integrate = True): self._S_m = np.array(S_m,dtype=complex) @@ -109,6 +203,51 @@ class RawVec(BaseSrc): class MagDipole(BaseSrc): + """ + Point magnetic dipole source calculated by taking the curl of a magnetic + vector potential. By taking the discrete curl, we ensure that the magnetic + flux density is divergence free (no magnetic monopoles!). + + This approach uses a primary-secondary in frequency. Here we show the + derivation for E-B formulation noting that similar steps are followed for + the H-J formulation. + + .. math:: + \mathbf{C} \mathbf{e} + i \omega \mathbf{b} = \mathbf{s_m} \\\\ + {\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{b} - \mathbf{M_{\sigma}^e} \mathbf{e} = \mathbf{s_e}} + + We split up the fields and :math:`\mu^{-1}` into primary (:math:`\mathbf{P}`) and secondary (:math:`\mathbf{S}`) components + + - :math:`\mathbf{e} = \mathbf{e^P} + \mathbf{e^S}` + - :math:`\mathbf{b} = \mathbf{b^P} + \mathbf{b^S}` + - :math:`\\boldsymbol{\mu}^{\mathbf{-1}} = \\boldsymbol{\mu}^{\mathbf{-1}^\mathbf{P}} + \\boldsymbol{\mu}^{\mathbf{-1}^\mathbf{S}}` + + and define a zero-frequency primary problem, noting that the source is + generated by a divergence free electric current + + .. math:: + \mathbf{C} \mathbf{e^P} = \mathbf{s_m^P} = 0 \\\\ + {\mathbf{C}^T \mathbf{{M_{\mu^{-1}}^f}^P} \mathbf{b^P} - \mathbf{M_{\sigma}^e} \mathbf{e^P} = \mathbf{M^e} \mathbf{s_e^P}} + + Since :math:`\mathbf{e^P}` is curl-free, divergence-free, we assume that there is no constant field background, the :math:`\mathbf{e^P} = 0`, so our primary problem is + + .. math:: + \mathbf{e^P} = 0 \\\\ + {\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f^P} \mathbf{b^P} = \mathbf{s_e^P}} + + Our secondary problem is then + + .. math:: + \mathbf{C} \mathbf{e^S} + i \omega \mathbf{b^S} = - i \omega \mathbf{b^P} \\\\ + {\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{b^S} - \mathbf{M_{\sigma}^e} \mathbf{e^S} = -\mathbf{C}^T \mathbf{M_{\mu^{-1}^S}^f} \mathbf{b^P}} + + :param list rxList: receiver list + :param float freq: frequency + :param numpy.ndarray loc: source location (ie: :code:`np.r_[xloc,yloc,zloc]`) + :param string orientation: 'X', 'Y', 'Z' + :param float moment: magnetic dipole moment + :param float mu: background magnetic permeability + """ #TODO: right now, orientation doesn't actually do anything! The methods in SrcUtils should take care of that def __init__(self, rxList, freq, loc, orientation='Z', moment=1., mu = mu_0): @@ -121,6 +260,13 @@ class MagDipole(BaseSrc): BaseSrc.__init__(self, rxList) def bPrimary(self, prob): + """ + The primary magnetic flux density from a magnetic vector potential + + :param Problem prob: FDEM problem + :rtype: numpy.ndarray + :return: primary magnetic field + """ eqLocs = prob._eqLocs if eqLocs is 'FE': @@ -152,14 +298,37 @@ class MagDipole(BaseSrc): return C*a def hPrimary(self, prob): + """ + The primary magnetic field from a magnetic vector potential + + :param Problem prob: FDEM problem + :rtype: numpy.ndarray + :return: primary magnetic field + """ b = self.bPrimary(prob) return h_from_b(prob,b) def S_m(self, prob): + """ + The magnetic source term + + :param Problem prob: FDEM problem + :rtype: numpy.ndarray + :return: primary magnetic field + """ + b_p = self.bPrimary(prob) return -1j*omega(self.freq)*b_p def S_e(self, prob): + """ + The electric source term + + :param Problem prob: FDEM problem + :rtype: numpy.ndarray + :return: primary magnetic field + """ + if all(np.r_[self.mu] == np.r_[prob.curModel.mu]): return Zero() else: @@ -179,6 +348,21 @@ class MagDipole(BaseSrc): class MagDipole_Bfield(BaseSrc): + """ + Point magnetic dipole source calculated with the analytic solution for the + fields from a magnetic dipole. No discrete curl is taken, so the magnetic + flux density may not be strictly divergence free. + + This approach uses a primary-secondary in frequency in the same fashion as the MagDipole. + + :param list rxList: receiver list + :param float freq: frequency + :param numpy.ndarray loc: source location (ie: :code:`np.r_[xloc,yloc,zloc]`) + :param string orientation: 'X', 'Y', 'Z' + :param float moment: magnetic dipole moment + :param float mu: background magnetic permeability + """ + #TODO: right now, orientation doesn't actually do anything! The methods in SrcUtils should take care of that #TODO: neither does moment def __init__(self, rxList, freq, loc, orientation='Z', moment=1., mu = mu_0): @@ -190,6 +374,14 @@ class MagDipole_Bfield(BaseSrc): BaseSrc.__init__(self, rxList) def bPrimary(self, prob): + """ + The primary magnetic flux density from the analytic solution for magnetic fields from a dipole + + :param Problem prob: FDEM problem + :rtype: numpy.ndarray + :return: primary magnetic field + """ + eqLocs = prob._eqLocs if eqLocs is 'FE': @@ -221,14 +413,35 @@ class MagDipole_Bfield(BaseSrc): return b def hPrimary(self, prob): + """ + The primary magnetic field from a magnetic vector potential + + :param Problem prob: FDEM problem + :rtype: numpy.ndarray + :return: primary magnetic field + """ b = self.bPrimary(prob) return h_from_b(prob, b) def S_m(self, prob): + """ + The magnetic source term + + :param Problem prob: FDEM problem + :rtype: numpy.ndarray + :return: primary magnetic field + """ b = self.bPrimary(prob) return -1j*omega(self.freq)*b def S_e(self, prob): + """ + The electric source term + + :param Problem prob: FDEM problem + :rtype: numpy.ndarray + :return: primary magnetic field + """ if all(np.r_[self.mu] == np.r_[prob.curModel.mu]): return Zero() else: @@ -247,6 +460,20 @@ class MagDipole_Bfield(BaseSrc): class CircularLoop(BaseSrc): + """ + Circular loop magnetic source calculated by taking the curl of a magnetic + vector potential. By taking the discrete curl, we ensure that the magnetic + flux density is divergence free (no magnetic monopoles!). + + This approach uses a primary-secondary in frequency in the same fashion as the MagDipole. + + :param list rxList: receiver list + :param float freq: frequency + :param numpy.ndarray loc: source location (ie: :code:`np.r_[xloc,yloc,zloc]`) + :param string orientation: 'X', 'Y', 'Z' + :param float moment: magnetic dipole moment + :param float mu: background magnetic permeability + """ #TODO: right now, orientation doesn't actually do anything! The methods in SrcUtils should take care of that def __init__(self, rxList, freq, loc, orientation='Z', radius = 1., mu=mu_0): @@ -259,6 +486,13 @@ class CircularLoop(BaseSrc): BaseSrc.__init__(self, rxList) def bPrimary(self, prob): + """ + The primary magnetic flux density from a magnetic vector potential + + :param Problem prob: FDEM problem + :rtype: numpy.ndarray + :return: primary magnetic field + """ eqLocs = prob._eqLocs if eqLocs is 'FE': @@ -289,14 +523,35 @@ class CircularLoop(BaseSrc): return C*a def hPrimary(self, prob): + """ + The primary magnetic field from a magnetic vector potential + + :param Problem prob: FDEM problem + :rtype: numpy.ndarray + :return: primary magnetic field + """ b = self.bPrimary(prob) return 1./self.mu*b def S_m(self, prob): + """ + The magnetic source term + + :param Problem prob: FDEM problem + :rtype: numpy.ndarray + :return: primary magnetic field + """ b = self.bPrimary(prob) return -1j*omega(self.freq)*b def S_e(self, prob): + """ + The electric source term + + :param Problem prob: FDEM problem + :rtype: numpy.ndarray + :return: primary magnetic field + """ if all(np.r_[self.mu] == np.r_[prob.curModel.mu]): return Zero() else: From 81c13b12e3ea69bbe6bf6ae2e9acf96c9bf6e8ad Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Sun, 7 Feb 2016 13:04:13 -0800 Subject: [PATCH 74/77] cleanup of docs, docs for survey --- SimPEG/EM/FDEM/FDEM.py | 50 +++++++++++++++++++----------------- SimPEG/EM/FDEM/FieldsFDEM.py | 13 ++++------ SimPEG/EM/FDEM/SurveyFDEM.py | 44 +++++++++++++++++++++++++++++-- docs/em/api_FDEM.rst | 18 +++++++++++++ 4 files changed, 91 insertions(+), 34 deletions(-) diff --git a/SimPEG/EM/FDEM/FDEM.py b/SimPEG/EM/FDEM/FDEM.py index 843ab245..09bbd143 100644 --- a/SimPEG/EM/FDEM/FDEM.py +++ b/SimPEG/EM/FDEM/FDEM.py @@ -15,18 +15,20 @@ class BaseFDEMProblem(BaseEMProblem): .. math :: \mathbf{C} \mathbf{e} + i \omega \mathbf{b} = \mathbf{s_m} \\\\ - {\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{b} - \mathbf{M_{\sigma}^e} \mathbf{e} = \mathbf{M^e} \mathbf{s_e}} + {\mathbf{C}^{\\top} \mathbf{M_{\mu^{-1}}^f} \mathbf{b} - \mathbf{M_{\sigma}^e} \mathbf{e} = \mathbf{s_e}} if using the E-B formulation (:code:`Problem_e` - or :code:`Problem_b`) or the magnetic field + or :code:`Problem_b`). Note that in this case, :math:`\mathbf{s_e}` is an integrated quantity. + + If we write Maxwell's equations in terms of \\\(\\\mathbf{h}\\\) and current density \\\(\\\mathbf{j}\\\) .. math :: - \mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{j} + i \omega \mathbf{M_{\mu}^e} \mathbf{h} = \mathbf{M^e} \mathbf{s_m} \\\\ + \mathbf{C}^{\\top} \mathbf{M_{\\rho}^f} \mathbf{j} + i \omega \mathbf{M_{\mu}^e} \mathbf{h} = \mathbf{s_m} \\\\ \mathbf{C} \mathbf{h} - \mathbf{j} = \mathbf{s_e} - if using the H-J formulation (:code:`Problem_j` or :code:`Problem_h`). + if using the H-J formulation (:code:`Problem_j` or :code:`Problem_h`). Note that here, :math:`\mathbf{s_m}` is an integrated quantity. The problem performs the elimination so that we are solving the system for \\\(\\\mathbf{e},\\\mathbf{b},\\\mathbf{j} \\\) or \\\(\\\mathbf{h}\\\) """ @@ -204,7 +206,7 @@ class Problem_e(BaseFDEMProblem): .. math :: - \\left(\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{C}+ i \omega \mathbf{M^e_{\sigma}} \\right)\mathbf{e} = \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f}\mathbf{s_m} -i\omega\mathbf{M^e}\mathbf{s_e} + \\left(\mathbf{C}^{\\top} \mathbf{M_{\mu^{-1}}^f} \mathbf{C}+ i \omega \mathbf{M^e_{\sigma}} \\right)\mathbf{e} = \mathbf{C}^{\\top} \mathbf{M_{\mu^{-1}}^f}\mathbf{s_m} -i\omega\mathbf{M^e}\mathbf{s_e} which we solve for :math:`\mathbf{e}`. @@ -223,7 +225,7 @@ class Problem_e(BaseFDEMProblem): System matrix .. math :: - \mathbf{A} = \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{C} + i \omega \mathbf{M^e_{\sigma}} + \mathbf{A} = \mathbf{C}^{\\top} \mathbf{M_{\mu^{-1}}^f} \mathbf{C} + i \omega \mathbf{M^e_{\sigma}} :param float freq: Frequency :rtype: scipy.sparse.csr_matrix @@ -265,7 +267,7 @@ class Problem_e(BaseFDEMProblem): Right hand side for the system .. math :: - \mathbf{RHS} = \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f}\mathbf{s_m} -i\omega\mathbf{M_e}\mathbf{s_e} + \mathbf{RHS} = \mathbf{C}^{\\top} \mathbf{M_{\mu^{-1}}^f}\mathbf{s_m} -i\omega\mathbf{M_e}\mathbf{s_e} :param float freq: Frequency :rtype: numpy.ndarray @@ -294,7 +296,7 @@ class Problem_e(BaseFDEMProblem): C = self.mesh.edgeCurl MfMui = self.MfMui - S_mDeriv, S_eDeriv = src.evalDeriv(self, adjoint) + S_mDeriv, S_eDeriv = src.evalDeriv(self, adjoint=adjoint) if adjoint: dRHS = MfMui * (C * v) @@ -310,13 +312,13 @@ class Problem_b(BaseFDEMProblem): .. math :: - \mathbf{e} = \mathbf{M^e_{\sigma}}^{-1} \\left(\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{b} - \mathbf{s_e}\\right) + \mathbf{e} = \mathbf{M^e_{\sigma}}^{-1} \\left(\mathbf{C}^{\\top} \mathbf{M_{\mu^{-1}}^f} \mathbf{b} - \mathbf{s_e}\\right) and solve for :math:`\mathbf{b}` using: .. math :: - \\left(\mathbf{C} \mathbf{M^e_{\sigma}}^{-1} \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} + i \omega \\right)\mathbf{b} = \mathbf{s_m} + \mathbf{M^e_{\sigma}}^{-1}\mathbf{M^e}\mathbf{s_e} + \\left(\mathbf{C} \mathbf{M^e_{\sigma}}^{-1} \mathbf{C}^{\\top} \mathbf{M_{\mu^{-1}}^f} + i \omega \\right)\mathbf{b} = \mathbf{s_m} + \mathbf{M^e_{\sigma}}^{-1}\mathbf{M^e}\mathbf{s_e} .. note :: The inverse problem will not work with full anisotropy @@ -336,7 +338,7 @@ class Problem_b(BaseFDEMProblem): System matrix .. math :: - \mathbf{A} = \mathbf{C} \mathbf{M^e_{\sigma}}^{-1} \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} + i \omega + \mathbf{A} = \mathbf{C} \mathbf{M^e_{\sigma}}^{-1} \mathbf{C}^{\\top} \mathbf{M_{\mu^{-1}}^f} + i \omega :param float freq: Frequency :rtype: scipy.sparse.csr_matrix @@ -431,7 +433,7 @@ class Problem_b(BaseFDEMProblem): v = self.MfMui * v MeSigmaIDeriv = self.MeSigmaIDeriv(S_e) - S_mDeriv, S_eDeriv = src.evalDeriv(self, adjoint) + S_mDeriv, S_eDeriv = src.evalDeriv(self, adjoint=adjoint) if not adjoint: RHSderiv = C * (MeSigmaIDeriv * v) @@ -458,13 +460,13 @@ class Problem_j(BaseFDEMProblem): .. math :: - \mathbf{h} = \\frac{1}{i \omega} \mathbf{M_{\mu}^e}^{-1} \\left(-\mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{j} + \mathbf{M^e} \mathbf{s_m} \\right) + \mathbf{h} = \\frac{1}{i \omega} \mathbf{M_{\mu}^e}^{-1} \\left(-\mathbf{C}^{\\top} \mathbf{M_{\\rho}^f} \mathbf{j} + \mathbf{M^e} \mathbf{s_m} \\right) and solve for \\\(\\\mathbf{j}\\\) using .. math :: - \\left(\mathbf{C} \mathbf{M_{\mu}^e}^{-1} \mathbf{C}^T \mathbf{M_{\\rho}^f} + i \omega\\right)\mathbf{j} = \mathbf{C} \mathbf{M_{\mu}^e}^{-1} \mathbf{M^e} \mathbf{s_m} -i\omega\mathbf{s_e} + \\left(\mathbf{C} \mathbf{M_{\mu}^e}^{-1} \mathbf{C}^{\\top} \mathbf{M_{\\rho}^f} + i \omega\\right)\mathbf{j} = \mathbf{C} \mathbf{M_{\mu}^e}^{-1} \mathbf{M^e} \mathbf{s_m} -i\omega\mathbf{s_e} .. note:: This implementation does not yet work with full anisotropy!! @@ -484,7 +486,7 @@ class Problem_j(BaseFDEMProblem): System matrix .. math :: - \\mathbf{A} = \\mathbf{C} \\mathbf{M^e_{\\mu^{-1}}} \\mathbf{C}^T \\mathbf{M^f_{\\sigma^{-1}}} + i\\omega + \\mathbf{A} = \\mathbf{C} \\mathbf{M^e_{\\mu^{-1}}} \\mathbf{C}^{\\top} \\mathbf{M^f_{\\sigma^{-1}}} + i\\omega :param float freq: Frequency :rtype: scipy.sparse.csr_matrix @@ -511,7 +513,7 @@ class Problem_j(BaseFDEMProblem): .. math :: - \\frac{\mathbf{A(\sigma)} \mathbf{v}}{d \mathbf{m}} = \mathbf{C} \mathbf{M^e_{mu^{-1}}} \mathbf{C^T} \\frac{d \mathbf{M^f_{\sigma^{-1}}}\mathbf{v} }{d \mathbf{m}} + \\frac{\mathbf{A(\sigma)} \mathbf{v}}{d \mathbf{m}} = \mathbf{C} \mathbf{M^e_{mu^{-1}}} \mathbf{C^{\\top}} \\frac{d \mathbf{M^f_{\sigma^{-1}}}\mathbf{v} }{d \mathbf{m}} :param float freq: frequency :param numpy.ndarray u: solution vector (nF,) @@ -543,6 +545,7 @@ class Problem_j(BaseFDEMProblem): .. math :: \mathbf{RHS} = \mathbf{C} \mathbf{M_{\mu}^e}^{-1}\mathbf{s_m} -i\omega \mathbf{s_e} + :param float freq: Frequency :rtype: numpy.ndarray (nE, nSrc) :return: RHS @@ -573,7 +576,7 @@ class Problem_j(BaseFDEMProblem): C = self.mesh.edgeCurl MeMuI = self.MeMuI - S_mDeriv, S_eDeriv = src.evalDeriv(self, adjoint) + S_mDeriv, S_eDeriv = src.evalDeriv(self, adjoint=adjoint) if adjoint: if self._makeASymmetric: @@ -604,7 +607,7 @@ class Problem_h(BaseFDEMProblem): .. math :: - \\left(\mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{C} + i \omega \mathbf{M_{\mu}^e}\\right) \mathbf{h} = \mathbf{M^e} \mathbf{s_m} + \mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{s_e} + \\left(\mathbf{C}^{\\top} \mathbf{M_{\\rho}^f} \mathbf{C} + i \omega \mathbf{M_{\mu}^e}\\right) \mathbf{h} = \mathbf{M^e} \mathbf{s_m} + \mathbf{C}^{\\top} \mathbf{M_{\\rho}^f} \mathbf{s_e} :param SimPEG.Mesh mesh: mesh """ @@ -620,9 +623,8 @@ class Problem_h(BaseFDEMProblem): """ System matrix - .. math :: - - \mathbf{A} = \mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{C} + i \omega \mathbf{M_{\mu}^e} + .. math:: + \mathbf{A} = \mathbf{C}^{\\top} \mathbf{M_{\\rho}^f} \mathbf{C} + i \omega \mathbf{M_{\mu}^e} :param float freq: Frequency :rtype: scipy.sparse.csr_matrix @@ -640,7 +642,7 @@ class Problem_h(BaseFDEMProblem): Product of the derivative of our system matrix with respect to the model and a vector .. math:: - \\frac{\mathbf{A}(\mathbf{m}) \mathbf{v}}{d \mathbf{m}} = \mathbf{C}^{\\top} \\frac{d \mathbf{M^f_{\\rho}}\mathbf{v} }{d\mathbf{m}} + \\frac{\mathbf{A}(\mathbf{m}) \mathbf{v}}{d \mathbf{m}} = \mathbf{C}^{\\top}\\frac{d \mathbf{M^f_{\\rho}}\mathbf{v} }{d\mathbf{m}} :param float freq: frequency :param numpy.ndarray u: solution vector (nE,) @@ -664,7 +666,7 @@ class Problem_h(BaseFDEMProblem): .. math :: - \mathbf{RHS} = \mathbf{M^e} \mathbf{s_m} + \mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{s_e} + \mathbf{RHS} = \mathbf{M^e} \mathbf{s_m} + \mathbf{C}^{\\top} \mathbf{M_{\\rho}^f} \mathbf{s_e} :param float freq: Frequency :rtype: numpy.ndarray @@ -701,7 +703,7 @@ class Problem_h(BaseFDEMProblem): elif adjoint: RHSDeriv = MfRhoDeriv.T * (C * v) - S_mDeriv, S_eDeriv = src.evalDeriv(self, adjoint) + S_mDeriv, S_eDeriv = src.evalDeriv(self, adjoint=adjoint) return RHSDeriv + S_mDeriv(v) + C.T * (MfRho * S_eDeriv(v)) diff --git a/SimPEG/EM/FDEM/FieldsFDEM.py b/SimPEG/EM/FDEM/FieldsFDEM.py index 9f54855c..e171a5c5 100644 --- a/SimPEG/EM/FDEM/FieldsFDEM.py +++ b/SimPEG/EM/FDEM/FieldsFDEM.py @@ -186,8 +186,7 @@ class Fields_e(Fields): :return: product of the secondary magnetic flux density derivative with respect to the inversion model with a vector """ - S_mDeriv, _ = src.evalDeriv(self.prob, adjoint) - S_mDeriv = S_mDeriv(v) + S_mDeriv, _ = src.evalDeriv(self.prob, v, adjoint) return 1./(1j * omega(src.freq)) * S_mDeriv def _b(self, eSolution, srcList): @@ -401,10 +400,9 @@ class Fields_b(Fields): elif adjoint: de_dm = self._MeSigmaIDeriv(w).T * v - _, S_eDeriv = src.evalDeriv(self.prob, adjoint) - Se_Deriv = S_eDeriv(v) + _, S_eDeriv = src.evalDeriv(self.prob, v, adjoint) - de_dm = de_dm - self._MeSigmaI * Se_Deriv + de_dm = de_dm - self._MeSigmaI * S_eDeriv return de_dm @@ -616,7 +614,7 @@ class Fields_j(Fields): elif adjoint: hDeriv_m = -1./(1j*omega(src.freq)) * MfRhoDeriv(jSolution).T * ( C * (MeMuI.T * v ) ) - S_mDeriv,_ = src.evalDeriv(self.prob, adjoint) + S_mDeriv,_ = src.evalDeriv(self.prob, adjoint = adjoint) if not adjoint: S_mDeriv = S_mDeriv(v) @@ -821,8 +819,7 @@ class Fields_h(Fields): :return: product of the secondary current density derivative with respect to the inversion model with a vector """ - _,S_eDeriv = src.evalDeriv(self.prob, adjoint) - S_eDeriv = S_eDeriv(v) + _,S_eDeriv = src.evalDeriv(self.prob, v, adjoint) return -S_eDeriv def _j(self, hSolution, srcList): diff --git a/SimPEG/EM/FDEM/SurveyFDEM.py b/SimPEG/EM/FDEM/SurveyFDEM.py index f60cbfdf..dd49b3ed 100644 --- a/SimPEG/EM/FDEM/SurveyFDEM.py +++ b/SimPEG/EM/FDEM/SurveyFDEM.py @@ -10,6 +10,12 @@ import SrcFDEM as Src #################################################### class Rx(SimPEG.Survey.BaseRx): + """ + Frequency domain receivers + + :param numpy.ndarray locs: receiver locations (ie. :code:`np.r_[x,y,z]`) + :param string rxType: reciever type from knownRxTypes + """ knownRxTypes = { 'exr':['e', 'Ex', 'real'], @@ -61,6 +67,15 @@ class Rx(SimPEG.Survey.BaseRx): return self.knownRxTypes[self.rxType][2] def projectFields(self, src, mesh, u): + """ + Project fields to recievers to get data. + + :param Source src: FDEM source + :param Mesh mesh: mesh used + :param Fields u: fields object + :rtype: numpy.ndarray + :return: fields projected to recievers + """ P = self.getP(mesh) u_part_complex = u[src, self.projField] # get the real or imag component @@ -69,6 +84,16 @@ class Rx(SimPEG.Survey.BaseRx): return P*u_part def projectFieldsDeriv(self, src, mesh, u, v, adjoint=False): + """ + Derivative of projected fields with respect to the inversion model times a vector. + + :param Source src: FDEM source + :param Mesh mesh: mesh used + :param Fields u: fields object + :param numpy.ndarray v: vector to multiply + :rtype: numpy.ndarray + :return: fields projected to recievers + """ P = self.getP(mesh) if not adjoint: @@ -95,10 +120,13 @@ class Rx(SimPEG.Survey.BaseRx): class Survey(SimPEG.Survey.BaseSurvey): """ - docstring for SurveyFDEM + Frequency domain electromagnetic survey + + :param list srcList: list of FDEM sources used in the survey """ srcPair = Src.BaseSrc + rxPaair = Rx def __init__(self, srcList, **kwargs): # Sort these by frequency @@ -126,6 +154,7 @@ class Survey(SimPEG.Survey.BaseSurvey): @property def nSrcByFreq(self): + """Number of sources at each frequency""" if getattr(self, '_nSrcByFreq', None) is None: self._nSrcByFreq = {} for freq in self.freqs: @@ -133,11 +162,22 @@ class Survey(SimPEG.Survey.BaseSurvey): return self._nSrcByFreq def getSrcByFreq(self, freq): - """Returns the sources associated with a specific frequency.""" + """ + Returns the sources associated with a specific frequency. + :param float freq: frequency for which we look up sources + :rtype: dictionary + :returns: sources at the sepcified frequency + """ assert freq in self._freqDict, "The requested frequency is not in this survey." return self._freqDict[freq] def projectFields(self, u): + """ + Project fields to receiver locations + :param Fields u: fields object + :rtype: numpy.ndarray + :returns: data + """ data = SimPEG.Survey.Data(self) for src in self.srcList: for rx in src.rxList: diff --git a/docs/em/api_FDEM.rst b/docs/em/api_FDEM.rst index e60c1bbf..f9de0979 100644 --- a/docs/em/api_FDEM.rst +++ b/docs/em/api_FDEM.rst @@ -144,6 +144,10 @@ H-J Formulation API === + +FDEM Problem +------------ + .. automodule:: SimPEG.EM.FDEM.FDEM :show-inheritance: :members: @@ -157,3 +161,17 @@ FDEM Survey :show-inheritance: :members: :undoc-members: + +.. automodule:: SimPEG.EM.FDEM.SrcFDEM + :show-inheritance: + :members: + :undoc-members: + +FDEM Fields +----------- + +.. automodule:: SimPEG.EM.FDEM.FieldsFDEM + :show-inheritance: + :members: + :undoc-members: + From f9359b7f0809af1e44012236a970215063ffef10 Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Sun, 7 Feb 2016 13:07:40 -0800 Subject: [PATCH 75/77] a couple typo fixes --- SimPEG/EM/FDEM/SrcFDEM.py | 4 ++-- SimPEG/EM/FDEM/SurveyFDEM.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SimPEG/EM/FDEM/SrcFDEM.py b/SimPEG/EM/FDEM/SrcFDEM.py index ba57d39b..1213cef3 100644 --- a/SimPEG/EM/FDEM/SrcFDEM.py +++ b/SimPEG/EM/FDEM/SrcFDEM.py @@ -233,13 +233,13 @@ class MagDipole(BaseSrc): .. math:: \mathbf{e^P} = 0 \\\\ - {\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f^P} \mathbf{b^P} = \mathbf{s_e^P}} + {\mathbf{C}^T \mathbf{{M_{\mu^{-1}}^f}^P} \mathbf{b^P} = \mathbf{s_e^P}} Our secondary problem is then .. math:: \mathbf{C} \mathbf{e^S} + i \omega \mathbf{b^S} = - i \omega \mathbf{b^P} \\\\ - {\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{b^S} - \mathbf{M_{\sigma}^e} \mathbf{e^S} = -\mathbf{C}^T \mathbf{M_{\mu^{-1}^S}^f} \mathbf{b^P}} + {\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{b^S} - \mathbf{M_{\sigma}^e} \mathbf{e^S} = -\mathbf{C}^T \mathbf{{M_{\mu^{-1}}^f}^S} \mathbf{b^P}} :param list rxList: receiver list :param float freq: frequency diff --git a/SimPEG/EM/FDEM/SurveyFDEM.py b/SimPEG/EM/FDEM/SurveyFDEM.py index dd49b3ed..444df88d 100644 --- a/SimPEG/EM/FDEM/SurveyFDEM.py +++ b/SimPEG/EM/FDEM/SurveyFDEM.py @@ -166,7 +166,7 @@ class Survey(SimPEG.Survey.BaseSurvey): Returns the sources associated with a specific frequency. :param float freq: frequency for which we look up sources :rtype: dictionary - :returns: sources at the sepcified frequency + :return: sources at the sepcified frequency """ assert freq in self._freqDict, "The requested frequency is not in this survey." return self._freqDict[freq] @@ -176,7 +176,7 @@ class Survey(SimPEG.Survey.BaseSurvey): Project fields to receiver locations :param Fields u: fields object :rtype: numpy.ndarray - :returns: data + :return: data """ data = SimPEG.Survey.Data(self) for src in self.srcList: From 00774176f31c90c78526f197912dc7846ad5b4e3 Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Mon, 8 Feb 2016 08:55:02 -0800 Subject: [PATCH 76/77] cleanup of math --- docs/em/api_FDEM.rst | 78 +++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/docs/em/api_FDEM.rst b/docs/em/api_FDEM.rst index f9de0979..778e0b3a 100644 --- a/docs/em/api_FDEM.rst +++ b/docs/em/api_FDEM.rst @@ -19,14 +19,14 @@ Electromagnetic phenomena are governed by Maxwell's equations. They describe the Fourier Transform Convention ---------------------------- -In order to examine Maxwell's equations in the frequency domain, we must first define our choice of harmonic time-dependence by choosing a Fourier transform convention. We use the \\(e^{i \\omega t} \\) convention, so we define our Fourier Transform pair as +In order to examine Maxwell's equations in the frequency domain, we must first define our choice of harmonic time-dependence by choosing a Fourier transform convention. We use the :math:`e^{i \omega t}` convention, so we define our Fourier Transform pair as .. math :: - F(\omega) = \int_{-\infty}^{\infty} f(t) e^{- i \omega t} dt \\ + F(\omega) = \int_{-\infty}^{\infty} f(t) e^{- i \omega t} dt \\ - f(t) = \frac{1}{2\pi}\int_{-\infty}^{\infty} F(\omega) e^{i \omega t} d \omega + f(t) = \frac{1}{2\pi}\int_{-\infty}^{\infty} F(\omega) e^{i \omega t} d \omega -where \\(\\omega\\) is angular frequency, \\(t\\) is time, \\(F(\\omega)\\) is the function defined in the frequency domain and \\(f(t)\\) is the function defined in the time domain. +where :math:`\omega` is angular frequency, :math:`t` is time, :math:`F(\omega)` is the function defined in the frequency domain and :math:`f(t)` is the function defined in the time domain. Maxwell's Equations @@ -34,44 +34,46 @@ Maxwell's Equations In the frequency domain, Maxwell's equations are given by .. math :: - \curl \vec{E} = - i \omega \vec{B} \\ + \curl \vec{E} + i \omega \vec{B} = \vec{S_m}\\ - \curl \vec{H} = \vec{J} + i \omega \vec{D} + \vec{S} \\ + \curl \vec{H} - \vec{J} - i \omega \vec{D} = \vec{S_e} \\ - \div \vec{B} = 0 \\ + \div \vec{B} = 0 \\ - \div \vec{D} = \rho_f + \div \vec{D} = \rho_f where: - - \\(\\vec{E}\\) : electric field (\\(V/m\\)) - - \\(\\vec{H}\\) : magnetic field (\\(A/m\\)) - - \\(\\vec{B}\\) : magnetic flux density (\\(Wb/m^2\\)) - - \\(\\vec{D}\\) : electric displacement / electric flux density (\\(C/m^2\\)) - - \\(\\vec{J}\\) : electric current density (\\(A/m^2\\)) - - \\(\\rho_f\\) : free charge density + - :math:`\vec{E}` : electric field (:math:`V/m` ) + - :math:`\vec{H}` : magnetic field (:math:`A/m` ) + - :math:`\vec{B}` : magnetic flux density (:math:`Wb/m^2` ) + - :math:`\vec{D}` : electric displacement / electric flux density (:math:`C/m^2` ) + - :math:`\vec{J}` : electric current density (:math:`A/m^2` ) + - :math:`\vec{S_m}` : magnetic source term (:math:`V/m^2` ) + - :math:`\vec{S_e}` : electric source term (:math:`A/m^2` ) + - :math:`\rho_f` : free charge density (:math:`\Omega m` ) -The source term is \\(\\vec{S}\\) Constitutive Relations ---------------------- + The fields and fluxes are related through the constitutive relations. At each frequency, they are given by .. math :: - \vec{J} = \sigma \vec{E} \\ + \vec{J} = \sigma \vec{E} \\ - \vec{B} = \mu \vec{H} \\ + \vec{B} = \mu \vec{H} \\ - \vec{D} = \varepsilon \vec{E} + \vec{D} = \varepsilon \vec{E} where: -- \\(\\sigma\\) : electrical conductivity \\(S/m\\) -- \\(\\mu\\) : magnetic permeability \\(H/m\\) -- \\(\\varepsilon\\) : dielectric permittivity \\(F/m\\) +- :math:`\sigma` : electrical conductivity (:math:`S/m`) +- :math:`\mu` : magnetic permeability (:math:`H/m`) +- :math:`\varepsilon` : dielectric permittivity (:math:`F/m`) -\\(\\sigma\\), \\(\\mu\\), \\(\\varepsilon\\) are physical properties which depend on the material. \\(\\sigma\\) describes how easily electric current passes through a material, \\(\\mu\\) describes how easily a material is magnetized, and \\(\\varepsilon\\) describes how easily a material is electrically polarized. In most geophysical applications of EM, \\(\\sigma\\) is the the primary physical property of interest, and \\(\\mu\\), \\(\\varepsilon\\) are assumed to have their free-space values \\(\\mu_0 = 4\\pi \\times 10^{-7} H/m \\), \\(\\varepsilon_0 = 8.85 \\times 10^{-12} F/m\\) +:math:`\sigma`, :math:`\mu`, :math:`\varepsilon` are physical properties which depend on the material. :math:`\sigma` describes how easily electric current passes through a material, :math:`\mu` describes how easily a material is magnetized, and :math:`\varepsilon` describes how easily a material is electrically polarized. In most geophysical applications of EM, :math:`\sigma` is the the primary physical property of interest, and :math:`\mu`, :math:`\varepsilon` are assumed to have their free-space values :math:`\mu_0 = 4\pi \times 10^{-7} H/m` , :math:`\varepsilon_0 = 8.85 \times 10^{-12} F/m` Quasi-static Approximation @@ -80,8 +82,8 @@ Quasi-static Approximation For the frequency range typical of most geophysical surveys, the contribution of the electric displacement is negligible compared to the electric current density. In this case, we use the Quasi-static approximation and assume that this term can be neglected, giving .. math :: - \nabla \times \vec{E} = -i \omega \vec{B} \\ - \nabla \times \vec{H} = \vec{J} + \vec{S} + \nabla \times \vec{E} + i \omega \vec{B} = \vec{S_m} \\ + \nabla \times \vec{H} - \vec{J} = \vec{S_e} Implementation in SimPEG.EM @@ -90,14 +92,14 @@ Implementation in SimPEG.EM We consider two formulations in SimPEG.EM, both first-order and both in terms of one field and one flux. We allow for the definition of magnetic and electric sources (see for example: Ward and Hohmann, starting on page 144). The E-B formulation is in terms of the electric field and the magnetic flux: .. math :: - \nabla \times \vec{E} + i \omega \vec{B} = \vec{S}_m \\ - \nabla \times \mu^{-1} \vec{B} - \sigma \vec{E} = \vec{S}_e + \nabla \times \vec{E} + i \omega \vec{B} = \vec{S}_m \\ + \nabla \times \mu^{-1} \vec{B} - \sigma \vec{E} = \vec{S}_e The H-J formulation is in terms of the current density and the magnetic field: .. math :: - \nabla \times \sigma^{-1} \vec{J} + i \omega \mu \vec{H} = \vec{S}_m \\ - \nabla \times \vec{H} - \vec{J} = \vec{S}_e + \nabla \times \sigma^{-1} \vec{J} + i \omega \mu \vec{H} = \vec{S}_m \\ + \nabla \times \vec{H} - \vec{J} = \vec{S}_e Discretizing @@ -106,34 +108,34 @@ For both formulations, we use a finite volume discretization and discretize fields on cell edges, fluxes on cell faces and physical properties in cell centers. This is particularly important when using symmetry to reduce the dimensionality of a problem -(for instance on a 2D CylMesh, there are \\(r\\), \\(z\\) faces and \\(\\theta\\) edges) +(for instance on a 2D CylMesh, there are :math:`r`, :math:`z` faces and :math:`\theta` edges) .. figure:: ../images/finitevolrealestate.png - :align: center - :scale: 60 % + :align: center + :scale: 60 % For the two formulations, the discretization of the physical properties, fields and fluxes are summarized below. .. figure:: ../images/ebjhdiscretizations.png - :align: center - :scale: 60 % + :align: center + :scale: 60 % -Note that resistivity is the inverse of conductivity, \\(\\rho = \\sigma^{-1}\\). +Note that resistivity is the inverse of conductivity, :math:`\rho = \sigma^{-1}`. E-B Formulation --------------- .. math :: - \mathbf{C} \mathbf{e} + i \omega \mathbf{b} = \mathbf{s_m} \\ - \mathbf{C^T} \mathbf{M^f_{\mu^{-1}}} \mathbf{b} - \mathbf{M^e_\sigma} \mathbf{e} = \mathbf{M^e} \mathbf{s_e} + \mathbf{C} \mathbf{e} + i \omega \mathbf{b} = \mathbf{s_m} \\ + \mathbf{C^T} \mathbf{M^f_{\mu^{-1}}} \mathbf{b} - \mathbf{M^e_\sigma} \mathbf{e} = \mathbf{M^e} \mathbf{s_e} H-J Formulation --------------- .. math :: - \mathbf{C^T} \mathbf{M^f_\rho} \mathbf{j} + i \omega \mathbf{M^e_\mu} \mathbf{h} = \mathbf{M^e} \mathbf{s_m} \\ - \mathbf{C} \mathbf{h} - \mathbf{j} = \mathbf{s_e} + \mathbf{C^T} \mathbf{M^f_\rho} \mathbf{j} + i \omega \mathbf{M^e_\mu} \mathbf{h} = \mathbf{M^e} \mathbf{s_m} \\ + \mathbf{C} \mathbf{h} - \mathbf{j} = \mathbf{s_e} .. Forward Problem From 5da9235eb455e32eca4b6d3829d0b8349c366ff7 Mon Sep 17 00:00:00 2001 From: Lindsey Heagy Date: Mon, 8 Feb 2016 12:58:04 -0800 Subject: [PATCH 77/77] light code cleanup --- SimPEG/EM/FDEM/FDEM.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/SimPEG/EM/FDEM/FDEM.py b/SimPEG/EM/FDEM/FDEM.py index 4c8b0c89..d870cae2 100644 --- a/SimPEG/EM/FDEM/FDEM.py +++ b/SimPEG/EM/FDEM/FDEM.py @@ -237,9 +237,7 @@ class Problem_e(BaseFDEMProblem): C = self.mesh.edgeCurl MfMui = self.MfMui - RHS = C.T * (MfMui * S_m) -1j * omega(freq) * S_e - - return RHS + return C.T * (MfMui * S_m) -1j * omega(freq) * S_e def getRHSDeriv_m(self, freq, src, v, adjoint=False): C = self.mesh.edgeCurl @@ -552,9 +550,7 @@ class Problem_h(BaseFDEMProblem): C = self.mesh.edgeCurl MfRho = self.MfRho - RHS = S_m + C.T * ( MfRho * S_e ) - - return RHS + return S_m + C.T * ( MfRho * S_e ) def getRHSDeriv_m(self, freq, src, v, adjoint=False): _, S_e = src.eval(self)