Skip to content

Commit 6be5bdf

Browse files
authored
Merge pull request #94 from simpeg/TreeMesh_accel
Tree mesh acceleration
2 parents 0e64f1a + b45c8c7 commit 6be5bdf

29 files changed

+5397
-30682
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[bumpversion]
2-
current_version = 0.2.1
2+
current_version = 0.3.0
33
files = setup.py discretize/__init__.py docs/conf.py
44

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ docs/warnings.txt
4242
.DS_Store
4343
docs/auto_examples
4444
docs/modules
45+
tree_ext.cpp
46+
interputils_cython.c
4547

4648
# Jupyter
4749
*.ipynb

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ install:
3434
- conda install --quiet --yes pip python=$TRAVIS_PYTHON_VERSION numpy scipy matplotlib pillow cython ipython;
3535
- conda install --quiet --yes -c menpo vtk
3636
- pip install -r requirements_dev.txt
37+
- python --version
3738
- python setup.py install
3839
- make build
3940
- export MPLBACKEND="agg"

appveyor.yml

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,56 +4,53 @@ build: off
44

55
environment:
66
matrix:
7-
- PYTHON: 2.7
8-
MINICONDA: C:\Miniconda
9-
PYTHON_ARCH: 32
10-
- PYTHON: 3.5
11-
MINICONDA: C:\Miniconda3
12-
PYTHON_ARCH: 32
13-
- PYTHON: 3.6
14-
MINICONDA: C:\Miniconda3
15-
PYTHON_ARCH: 32
16-
- PYTHON: 2.7
17-
MINICONDA: C:\Miniconda
18-
PYTHON_ARCH: 64
19-
- PYTHON: 3.5
20-
MINICONDA: C:\Miniconda3
21-
PYTHON_ARCH: 64
22-
- PYTHON: 3.6
23-
MINICONDA: C:\Miniconda3
24-
PYTHON_ARCH: 64
7+
- PYTHON_VERSION: 2.7
8+
CONDA: C:\Miniconda
9+
10+
- PYTHON_VERSION: 3.5
11+
CONDA: C:\Miniconda35
12+
13+
- PYTHON_VERSION: 3.6
14+
CONDA: C:\Miniconda36
2515

2616
pypi_password:
2717
secure: S2mZCxm5LzIF4KFDss/f7g==
2818

19+
image: Visual Studio 2015
20+
21+
platform:
22+
- x86
23+
- x64
24+
2925
init:
30-
- "ECHO %PYTHON% %MINICONDA%"
26+
- ps: if ($Env:PLATFORM -eq "x64") { $Env:CONDA = "${Env:CONDA}-x64" }
27+
- ps: Write-Host $Env:PYTHON_VERSION
28+
- ps: Write-Host $Env:CONDA
29+
- ps: Write-Host $Env:PLATFORM
3130

3231
install:
33-
- "set PATH=%MINICONDA%;%MINICONDA%\\Scripts;%PATH%"
32+
# Load the conda root environment, configure and install some packages
33+
- '"%CONDA%\Scripts\activate.bat"'
3434
- conda config --set always_yes yes --set changeps1 no
3535
- conda update -q conda
3636
- conda info -a
37-
- "conda create -q -n test-environment python=%PYTHON% numpy scipy matplotlib cython ipython pillow wheel"
38-
- activate test-environment
39-
- conda install --quiet --yes -c menpo vtk
37+
- conda install numpy scipy matplotlib cython ipython pillow wheel
38+
- conda install -c menpo vtk
4039
- pip install -r requirements_dev.txt
4140
- python setup.py install
4241

43-
# Use the Agg backend in Matplotlib
44-
- "set MPLBACKEND=Agg"
45-
4642
test_script:
4743
- cd tests
48-
- nosetests base -v -s
44+
- nosetests -v -s base/test_interpolation.py base/test_tensor_io.py
45+
- nosetests -v -s tree
4946
- cd ..
5047

5148
after_test:
5249
# This step builds your wheels.
5350
# Again, you only need build.cmd if you're building C extensions for
5451
# 64-bit Python 3.3/3.4. And you need to use %PYTHON% to get the correct
5552
# interpreter
56-
- python setup.py sdist bdist_wheel
53+
- python setup.py bdist_wheel
5754

5855
artifacts:
5956
# bdist_wheel puts your built wheel in the dist directory
@@ -65,6 +62,6 @@ deploy_script:
6562
- echo [pypi] >> %USERPROFILE%\\.pypirc
6663
- echo username=lheagy >> %USERPROFILE%\\.pypirc
6764
- echo password=%pypi_password% >> %USERPROFILE%\\.pypirc
68-
- twine upload dist\* --skip-existing
65+
# - twine upload dist\* --skip-existing
6966
# deploy on dev or master
70-
# - ps: If ($env:APPVEYOR_REPO_TAG -eq "true" ) { Invoke-Expression "twine upload dist/* --skip-existing" 2>$null } Else { write-output "Not on a tag, won't deploy to pypi"}
67+
- ps: If ($env:APPVEYOR_REPO_TAG -eq "true" ) { Invoke-Expression "twine upload dist\*.whl --skip-existing" 2>$null } Else { write-output "Not on a tag, won't deploy to pypi"}

discretize/MeshIO.py

Lines changed: 86 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -537,93 +537,19 @@ def writeUBC(mesh, fileName, models=None, directory='', comment_lines=''):
537537

538538
class TreeMeshIO(object):
539539

540-
def writeUBC(mesh, fileName, models=None):
541-
"""Write UBC ocTree mesh and model files from a
542-
octree mesh and model.
543-
544-
:param string fileName: File to write to
545-
:param dict models: Models in a dict, where each key is the filename
546-
"""
547-
548-
# Calculate information to write in the file.
549-
# Number of cells in the underlying mesh
550-
nCunderMesh = np.array([h.size for h in mesh.h], dtype=np.int64)
551-
# The top-south-west most corner of the mesh
552-
tswCorn = mesh.x0 + np.array([0, 0, np.sum(mesh.h[2])])
553-
# Smallest cell size
554-
smallCell = np.array([h.min() for h in mesh.h])
555-
# Number of cells
556-
nrCells = mesh.nC
557-
558-
# Extract information about the cells.
559-
cellPointers = np.array([c._pointer for c in mesh])
560-
cellW = np.array([mesh._levelWidth(i) for i in cellPointers[:, -1]])
561-
# Need to shift the pointers to work with UBC indexing
562-
# UBC Octree indexes always the top-left-close (top-south-west) corner
563-
# first and orders the cells in z(top-down), x, y vs x, y, z(bottom-up)
564-
# Shift index up by 1
565-
ubcCellPt = cellPointers[:, 0:-1].copy() + np.array([1., 1., 1.])
566-
# Need re-index the z index to be from the top-left-close corner and to
567-
# be from the global top.
568-
ubcCellPt[:, 2] = (nCunderMesh[-1] + 2) - (ubcCellPt[:, 2] + cellW)
569-
570-
# Reorder the ubcCellPt
571-
ubcReorder = np.argsort(
572-
ubcCellPt.view(', '.join(3*['float'])),
573-
axis=0,
574-
order=['f2', 'f1', 'f0']
575-
)[:, 0]
576-
# Make a array with the pointers and the withs,
577-
# that are order in the ubc ordering
578-
indArr = np.concatenate(
579-
(ubcCellPt[ubcReorder, :], cellW[ubcReorder].reshape((-1, 1))),
580-
axis=1
581-
)
582-
# Write the UBC octree mesh file
583-
head = (
584-
'{:.0f} {:.0f} {:.0f}\n'.format(
585-
nCunderMesh[0], nCunderMesh[1], nCunderMesh[2]
586-
) +
587-
'{:.4f} {:.4f} {:.4f}\n'.format(
588-
tswCorn[0], tswCorn[1], tswCorn[2]
589-
) +
590-
'{:.3f} {:.3f} {:.3f}\n'.format(
591-
smallCell[0], smallCell[1], smallCell[2]
592-
) +
593-
'{:.0f}'.format(nrCells)
594-
)
595-
np.savetxt(fileName, indArr, fmt='%i', header=head, comments='')
596-
597-
# Print the models
598-
# Assign the model('s) to the object
599-
if models is not None:
600-
for item in six.iteritems(models):
601-
# Save the data
602-
np.savetxt(item[0], item[1][ubcReorder], fmt='%3.5e')
603-
604540
@classmethod
605541
def readUBC(TreeMesh, meshFile):
606-
"""Read UBC 3D OcTree mesh and/or modelFiles
607-
542+
"""Read UBC 3D OcTree mesh file
608543
Input:
609544
:param str meshFile: path to the UBC GIF OcTree mesh file to read
610545
:rtype: discretize.TreeMesh
611546
:return: The octree mesh
612547
"""
613-
614-
# Read the file lines
615548
fileLines = np.genfromtxt(meshFile, dtype=str,
616-
delimiter='\n', comments='!')
617-
# Extract the data
618-
nCunderMesh = np.array(fileLines[0].
619-
split('!')[0].split(), dtype=int)
620-
# I think this is the case?
621-
# Format of file changed... First 3 values are the # of cells in the
622-
# underlying mesh and remaining 6 values are padding for the core region.
623-
nCunderMesh = nCunderMesh[0:3]
624-
625-
if np.unique(nCunderMesh).size >1:
626-
raise Exception('TreeMeshes have the same number of cell in all directions')
549+
delimiter='\n', comments='!')
550+
nCunderMesh = np.array(fileLines[0].split('!')[0].split(), dtype=int)
551+
nCunderMesh = nCunderMesh[0:3]
552+
627553
tswCorn = np.array(
628554
fileLines[1].split('!')[0].split(),
629555
dtype=float
@@ -632,44 +558,37 @@ def readUBC(TreeMesh, meshFile):
632558
fileLines[2].split('!')[0].split(),
633559
dtype=float
634560
)
635-
nrCells = np.array(
636-
fileLines[3].split('!')[0].split(),
637-
dtype=float
638-
)
639561
# Read the index array
640-
indArr = np.genfromtxt((line.encode('utf8') for line in fileLines[4::]), dtype=np.int)
562+
indArr = np.genfromtxt((line.encode('utf8') for line in fileLines[4::]),
563+
dtype=np.int)
641564

642-
# Calculate simpeg parameters
643565
h1, h2, h3 = [np.ones(nr)*sz for nr, sz in zip(nCunderMesh, smallCell)]
644566
x0 = tswCorn - np.array([0, 0, np.sum(h3)])
645-
# Convert the index array to a points list that complies with TreeMesh
646-
# Shift to start at 0
647-
simpegCellPt = indArr[:, 0:-1].copy()
648-
simpegCellPt[:, 2] = ( nCunderMesh[-1] + 2) - (simpegCellPt[:, 2] + indArr[:, 3])
649-
# Need reindex the z index to be from the bottom-left-close corner
650-
# and to be from the global bottom.
651-
simpegCellPt = simpegCellPt - np.array([1., 1., 1.])
652-
653-
# Calculate the cell level
654-
simpegLevel = np.log2(np.min(nCunderMesh)) - np.log2(indArr[:, 3])
655-
# Make a pointer matrix
656-
simpegPointers = np.concatenate((simpegCellPt, simpegLevel.reshape((-1, 1))), axis=1)
657-
658-
# Make the tree mesh
567+
568+
ls = np.log2(nCunderMesh).astype(int)
569+
if ls[0] == ls[1] and ls[1] == ls[2]:
570+
max_level = ls[0]
571+
else:
572+
max_level = min(ls)+1
573+
659574
mesh = TreeMesh([h1, h2, h3], x0=x0)
660-
mesh._cells = set([mesh._index(p) for p in simpegPointers.tolist()])
661575

662-
# Figure out the reordering
663-
mesh._simpegReorderUBC = np.argsort(
664-
np.array([mesh._index(i) for i in simpegPointers.tolist()])
665-
)
666-
# mesh._simpegReorderUBC = np.argsort((np.array([[1, 1, 1, -1]])*simpegPointers).view(', '.join(4*['float'])), axis=0, order=['f3', 'f2', 'f1', 'f0'])[:, 0]
576+
# Convert indArr to points in coordinates of underlying cpp tree
577+
# indArr is ix, iy, iz(top-down) need it in ix, iy, iz (bottom-up)
667578

579+
levels = indArr[:, -1]
580+
indArr = indArr[:, :-1]
581+
582+
indArr -= 1 # shift by 1....
583+
indArr = 2*indArr + levels[:, None] # get cell center index
584+
indArr[:, 2] = 2*nCunderMesh[2] - indArr[:, 2] # switch direction of iz
585+
levels = max_level-np.log2(levels) # calculate level
586+
587+
mesh.__setstate__((indArr, levels))
668588
return mesh
669589

670590
def readModelUBC(mesh, fileName):
671591
"""Read UBC OcTree model and get vector
672-
673592
:param string fileName: path to the UBC GIF model file to read
674593
:rtype: numpy.ndarray
675594
:return: OcTree model
@@ -681,49 +600,91 @@ def readModelUBC(mesh, fileName):
681600
out[f] = mesh.readModelUBC(f)
682601
return out
683602

684-
assert hasattr(mesh, '_simpegReorderUBC'), 'The file must have been loaded from a UBC format.'
685603
assert mesh.dim == 3
686604

687605
modList = []
688606
modArr = np.loadtxt(fileName)
689-
if len(modArr.shape) == 1:
690-
modList.append(modArr[mesh._simpegReorderUBC])
691-
else:
692-
modList.append(modArr[mesh._simpegReorderUBC, :])
693-
return modList
694607

695-
def writeVTK(mesh, fileName, models=None):
608+
ubc_order = mesh._ubc_order
609+
# order_ubc will re-order from treemesh ordering to UBC ordering
610+
# need the opposite operation
611+
un_order = np.empty_like(ubc_order)
612+
un_order[ubc_order] = np.arange(len(ubc_order))
613+
614+
model = modArr[un_order].copy() # ensure a contiguous array
615+
return model
616+
617+
def writeUBC(mesh, fileName, models=None):
618+
"""Write UBC ocTree mesh and model files from a
619+
octree mesh and model.
620+
:param string fileName: File to write to
621+
:param dict models: Models in a dict, where each key is the filename
622+
"""
623+
uniform_hs = np.array([np.allclose(h, h[0]) for h in mesh.h])
624+
if np.any(~uniform_hs):
625+
raise Exception('UBC form does not support variable cell widths')
626+
nCunderMesh = np.array([h.size for h in mesh.h], dtype=np.int64)
627+
tswCorn = mesh.x0 + np.array([0, 0, np.sum(mesh.h[2])])
628+
smallCell = np.array([h[0] for h in mesh.h])
629+
nrCells = mesh.nC
630+
631+
indArr, levels = mesh._ubc_indArr
632+
ubc_order = mesh._ubc_order
633+
634+
indArr = indArr[ubc_order]
635+
levels = levels[ubc_order]
636+
637+
# Write the UBC octree mesh file
638+
head = (
639+
'{:.0f} {:.0f} {:.0f}\n'.format(
640+
nCunderMesh[0], nCunderMesh[1], nCunderMesh[2]
641+
) +
642+
'{:.4f} {:.4f} {:.4f}\n'.format(
643+
tswCorn[0], tswCorn[1], tswCorn[2]
644+
) +
645+
'{:.3f} {:.3f} {:.3f}\n'.format(
646+
smallCell[0], smallCell[1], smallCell[2]
647+
) +
648+
'{:.0f}'.format(nrCells)
649+
)
650+
np.savetxt(fileName, np.c_[indArr, levels], fmt='%i', header=head, comments='')
651+
652+
# Print the models
653+
# Assign the model('s) to the object
654+
if models is not None:
655+
for item in six.iteritems(models):
656+
# Save the data
657+
np.savetxt(item[0], item[1][ubc_order], fmt='%3.5e')
658+
659+
660+
def writeVTK(self, fileName, models=None):
696661
"""Function to write a VTU file from a TreeMesh and model."""
697662
import vtk
698663
from vtk import vtkXMLUnstructuredGridWriter as Writer, VTK_VERSION
699-
from vtk.util.numpy_support import numpy_to_vtk, numpy_to_vtkIdTypeArray
700-
701-
if str(type(mesh)).split()[-1][1:-2] not in 'discretize.TreeMesh.TreeMesh':
702-
raise IOError('mesh is not a TreeMesh.')
664+
from vtk.util.numpy_support import numpy_to_vtk
703665

704666
# Make the data parts for the vtu object
705667
# Points
706-
mesh.number()
707-
ptsMat = mesh._gridN + mesh.x0
668+
ptsMat = np.vstack((self.gridN, self.gridhN))
708669

709670
vtkPts = vtk.vtkPoints()
710671
vtkPts.SetData(numpy_to_vtk(ptsMat, deep=True))
711672
# Cells
712-
cellConn = np.array([c.nodes for c in mesh], dtype=np.int64)
673+
cellArray = [c for c in self]
674+
cellConn = np.array([cell.nodes for cell in cellArray])
713675

714-
cellsMat = np.concatenate((np.ones((cellConn.shape[0], 1), dtype=np.int64)*cellConn.shape[1], cellConn), axis=1).ravel()
676+
cellsMat = np.concatenate((np.ones((cellConn.shape[0], 1))*cellConn.shape[1], cellConn), axis=1).ravel()
715677
cellsArr = vtk.vtkCellArray()
716678
cellsArr.SetNumberOfCells(cellConn.shape[0])
717-
cellsArr.SetCells(cellConn.shape[0], numpy_to_vtkIdTypeArray(cellsMat, deep =True))
679+
cellsArr.SetCells(cellConn.shape[0], numpy_to_vtk(cellsMat, deep=True, array_type=vtk.VTK_ID_TYPE))
718680

719681
# Make the object
720682
vtuObj = vtk.vtkUnstructuredGrid()
721683
vtuObj.SetPoints(vtkPts)
722684
vtuObj.SetCells(vtk.VTK_VOXEL, cellsArr)
723685
# Add the level of refinement as a cell array
724-
cellSides = np.array([np.array(vtuObj.GetCell(i).GetBounds()).reshape((3, 2)).dot(np.array([-1, 1])) for i in np.arange(vtuObj.GetNumberOfCells())])
725-
uniqueLevel, indLevel = np.unique(np.prod(cellSides, axis=1), return_inverse=True)
726-
refineLevelArr = numpy_to_vtk(indLevel.max() - indLevel, deep=1)
686+
cell_levels = np.array([cell._level for cell in cellArray])
687+
refineLevelArr = numpy_to_vtk(cell_levels, deep=1)
727688
refineLevelArr.SetName('octreeLevel')
728689
vtuObj.GetCellData().AddArray(refineLevelArr)
729690
# Assign the model('s) to the object

0 commit comments

Comments
 (0)