Skip to content

Commit

Permalink
* Added tests for sequential read/write.
Browse files Browse the repository at this point in the history
* Tests revealed that the sequential read/write wasn't
  really working. Now fixed!
* Removed mpi-h5py as a dependency. Now any h5py will do.
* Removed HDF5 as a config package.
* Removed H5PY self-build option.
* Added info on compiling mpi-h5py in `COMPILE.md`.
* Added barrier to end of read/writes for safety.
  • Loading branch information
jmansour committed Feb 13, 2019
1 parent 6e3b08c commit e01c14f
Show file tree
Hide file tree
Showing 13 changed files with 130 additions and 208 deletions.
17 changes: 16 additions & 1 deletion COMPILE.md
Expand Up @@ -15,7 +15,7 @@ Dependencies
* PETSc
* numpy
* swig
* h5py-mpi
* h5py


Getting the code
Expand Down Expand Up @@ -48,3 +48,18 @@ You will first need to make the project directory available to import within pyt
```
(note that if you are not using the bash shell, the required command will be different.)

h5py-mpi
--------
Underworld now supports non-mpi versions of `h5py`. Note however that for large parallel simulations,
saving data to disk may become a bottleneck, and collective IO via MPI-enabled `h5py` is recommended.
To install `h5py` with MPI enabled, you will first required a parallel enabled version of `libhdf5`.
The following command may be useful for installed MPI-enabled `h5py`:

```bash
$ CC=mpicc HDF5_MPI="ON" HDF5_DIR=/path/to/your/hdf5/install/ pip install --no-binary=h5py h5py
```

or alternatively you might use `CC=h5pcc` (if available). Please check the `h5py` site for more information.
Underworld will automatically perform `save()`/`load()` operations collectively if MPI-enabled `hdf5` is
available.

84 changes: 63 additions & 21 deletions docs/test/mesh_aux.py
@@ -1,31 +1,73 @@
'''
This script contains auxiliary mesh related tests.
This script contains auxiliary mesh related tests.
We test saving and loading of mesh and mesh variable objects.
We also tests creation/load/save of non-partitioned mesh and
variables. I'll leave this in for now, but I'm not sure if
load/save of non-partitioned mesh is useful or even a good
idea. Effectively, in parallel, we're saying that each process
has its own distinct mesh object, but then we're saving it to
a single global file. Sequentially this is fine (and each proc
*could* specify its own filename), but collectively this doesn't
make sense as we use COMM_WORLD but the mesh only is aware of
COMM_SELF. Note that the test passes, but I've disabled it in
any case as it doesn't make sense.
'''
import underworld as uw
import numpy as np

def meshtest(res, partitioned):
mesh = uw.mesh.FeMesh_Cartesian(elementRes=(res,res),partitioned=partitioned)

resp1 = res + 1
if not partitioned:
if (len(mesh.data) != (resp1*resp1)):
raise RuntimeError("A non-partitioned mesh should report identical vertex count "\
"independent of processor count.")
# test save/load of mesh
with mesh.deform_mesh():
mesh.data[:] *= 2.

# Simple test to check non-parallel decomposed meshing is working.
# Note that we are only really testing this parameter when we
# run this test in parallel.
mesh1 = uw.mesh.FeMesh_Cartesian(elementRes=(4,4),partitioned=False)

if len(mesh1.data) != 25:
raise RuntimeError("A non-partitioned mesh should report identical vertex count \
independent of processor count.")

mesh2 = uw.mesh.FeMesh_Cartesian(elementRes=(10,10),partitioned=True)
var = uw.mesh.MeshVariable( mesh2, nodeDofCount=2 )
var2 = uw.mesh.MeshVariable( mesh2, nodeDofCount=2 )

for ind, coord in enumerate(mesh2.data):
var.data[ind] = [coord[1]+5., coord[0]*-2.]
cpy = mesh.data.copy()
mesh.save('temp.h5')
mesh.reset()
if np.allclose(mesh.data, cpy):
raise RuntimeError("These arrays should be different.")
mesh.load('temp.h5')
if not np.allclose(mesh.data, cpy):
raise RuntimeError("These arrays should be identical.")

var.save('temp.h5')
var2.load('temp.h5')
# test save/load of meshvariable
var = uw.mesh.MeshVariable( mesh, nodeDofCount=2 )
for ind, coord in enumerate(mesh.data):
var.data[ind] = [coord[1]+5., coord[0]*-2.]

checkpoint_pass = np.allclose(var.data, var2.data)
var.syncronise()
cpy = var.data.copy()
var.save('temp2.h5')
var.data[:] = 0.
if np.allclose(var.data, cpy):
raise RuntimeError("These arrays should be different.")
var.load('temp2.h5')
if not np.allclose(var.data, cpy):
if uw.mpi.rank==0:
print("VAR")
print(var.data[:])
print("CPY")
print(cpy)
raise RuntimeError("These arrays should be identical.")

if( not checkpoint_pass ):
raise RuntimeError("Error in a checkpoint test")
if __name__ == '__main__':
import underworld as uw
uw.utils._io.PATTERN=1 # sequential
meshtest(16,True)
meshtest(8, True) # second run to make sure we're deleting datasets where different sizes
meshtest(16,False)
uw.utils._io.PATTERN=2 # collective
meshtest(16,True)
meshtest(8, True)
# meshtest(16,False) # this isn't a good idea, so we shouldn't do it.
if uw.mpi.rank==0:
import os
os.remove('temp.h5')
os.remove('temp2.h5')
6 changes: 5 additions & 1 deletion docs/test/swarm_aux.py
Expand Up @@ -47,7 +47,6 @@ def swarm_save_load(swarmtype):
clone_svar1.load("saved_swarm_variable1.h5")
if np.allclose(clone_swarm.particleCoordinates.data,clone_svar1.data) != True:
raise RuntimeError("Loaded swarm variable1 does not appear to contain the correct data.")

clone_svar2 = clone_swarm.add_variable("int",1)
clone_svar2.load("saved_swarm_variable2.h5")

Expand All @@ -59,5 +58,10 @@ def swarm_save_load(swarmtype):


if __name__ == '__main__':
import underworld as uw
uw.utils._io.PATTERN=1 # sequential
swarm_save_load('global')
swarm_save_load('passivetracer')
uw.utils._io.PATTERN=2 # collective
swarm_save_load('global')
swarm_save_load('passivetracer')
2 changes: 0 additions & 2 deletions libUnderworld/SConfigure
Expand Up @@ -62,7 +62,6 @@ env.AddMethod( UWExit )

env.UsePackage(packages.libm)
env.UsePackage(packages.libXML2)
env.UsePackage(packages.HDF5)
env.UsePackage(packages.Mpi4pyPk)
env.UsePackage(packages.H5py)
env.UsePackage(packages.Python)
Expand Down Expand Up @@ -173,7 +172,6 @@ if not (GetOption('help') or GetOption('clean')):
env.ConfigurePackage(packages.libm, required=False)
env.ConfigurePackage(packages.libXML2)
env.ConfigurePackage(packages.MPI)
env.ConfigurePackage(packages.HDF5, required=False)
env.ConfigurePackage(packages.Python, required=True)
env.ConfigurePackage(packages.PETSc)
env.ConfigurePackage(packages.NumpyPk, required=True)
Expand Down
110 changes: 6 additions & 104 deletions libUnderworld/config/packages/H5py.py
@@ -1,94 +1,23 @@
import os, sys
from config import Package
from .MPI import MPI
from .HDF5 import HDF5
import subprocess
from SCons.Script.Main import AddOption

class H5py(Package):

_buildh5py = False
_h5pysrc = os.path.abspath('./h5py_ext')
_h5pyp = os.path.abspath('../h5py')

def setup_dependencies(self):
self.MPI = self.add_dependency(MPI, required=True)
self.hdf5 = self.add_dependency(HDF5, required=False)

def _buildh5py(self, cc):
print("\nAttempting to build private h5py version at {} using cc={}. This may take a few minutes.".format(self._h5pyp,cc))
os.chdir(self._h5pysrc)
with open('h5py_build.out','w') as outfile:
with open('h5py_build.err','w') as errfile:
command = "python setup.py configure -rm --hdf5 " + self.hdf5.location[0]
self._logfile.write("\n\nh5py configuration command:\n")
self._logfile.write(self.launcher+command)
subp = subprocess.Popen((self.launcher+command).split(), stdout=outfile, stderr=errfile)
subp.wait()
if subp.wait() != 0:
self._logfile.write("\nFailed configuring h5py :(\nPlease check 'h5py_build.out' and 'h5py_build.err' in libUnderworld/h5py_ext\n")
raise RuntimeError
subp = subprocess.Popen(self.launcher+'python setup.py clean', shell=True, stdout=outfile, stderr=errfile)
subp.wait()
if subp.wait() != 0:
self._logfile.write("\nFailed cleaning h5py :(\nPlease check 'h5py_build.out' and 'h5py_build.err' in libUnderworld/h5py_ext\n")
raise RuntimeError

# The following doesn't work on raijin, one only requires 'setup.py build'
# but as we use a system h5py it isn't critical JG May2017
cmd = 'python setup.py build_ext --include-dirs='
# use all header paths the config has thus far added.
# we really just need the path to mpi.h
for header_path in self.env["CPPPATH"]:
cmd += header_path +':'
cmd2 = ' --build-lib ' + os.path.dirname(self._h5pyp)
cmd += cmd2

self._logfile.write("\nh5py build command:\n")
self._logfile.write('CC='+cc+' '+self.launcher+' '+cmd )
subp = subprocess.Popen( 'CC='+cc+' '+self.launcher+' '+cmd, shell=True, stdout=outfile, stderr=errfile)
subp.wait()
if subp.wait() != 0:
self._logfile.write("\nFailed building h5py :(\nPlease check 'h5py_build.out' and 'h5py_build.err' in libUnderworld/h5py_ext\n")
raise RuntimeError
# need to run this now to make it importable
self._logfile.write("\nh5py install command:\n")
self._logfile.write(self.launcher+'python setup.py build' + cmd2)
subp = subprocess.Popen(self.launcher+'python setup.py build' + cmd2, shell=True, stdout=outfile, stderr=errfile)
subp.wait()
if subp.wait() != 0:
self._logfile.write("\nFailed installing h5py :(\nPlease check 'h5py_build.out' and 'h5py_build.err' in libUnderworld/h5py_ext\n")
raise RuntimeError
os.chdir(self._h5pysrc+'/..')
self._logfile.write("\nh5py build completed successfully.\n")
pass

def _importtest(self):
'''
We run test on h5py here. Note that we first check if a custom h5py is
in the parent directory ie it is preferenced over system packages.
Also note that we do this via subprocess to keep our own python environment
clean and allow re-importing after (perhaps) we have built our own h5py.
'''
# next check for mpi compat
self._logfile.write("\nChecking if h5py is importable and built against mpi.\n")
self._logfile.write("\nChecking if h5py is available.")
self._logfile.flush()
proj_folder = os.path.realpath(os.path.dirname("../.."))

# try h5py in the parent directory
subp = subprocess.Popen(self.launcher+'python3 -c \'import sys\nsys.path.insert(0, \"{}\")\nimport h5py\nif not h5py.get_config().mpi: raise RuntimeError(\"h5py imported, but not compiled against mpi.\")\''.format(proj_folder), shell=True, stdout=self._logfile, stderr=self._logfile)
subp.wait()

subp = subprocess.Popen(self.launcher+'python3 -c \'import h5py\'', shell=True, stdout=self._logfile, stderr=self._logfile)
if subp.wait() != 0:
self._logfile.write("\nh5py is not importable from {}, or does not appear to be built against mpi.\n".format(proj_folder))
# try h5py in PYTHONPATH
subp = subprocess.Popen(self.launcher+'python3 -c \'import h5py\nif not h5py.get_config().mpi: raise RuntimeError(\"h5py imported, but not compiled against mpi.\")\'', shell=True, stdout=self._logfile, stderr=self._logfile)
if subp.wait() != 0:
self._logfile.write("\nh5py is not importable from system, or does not appear to be built against mpi.\n")
self._logfile.write("\nNote that you can install parallel h5py via pip using the following command:\n \
CC=mpicc HDF5_MPI=\"ON\" HDF5_DIR=/path/to/your/hdf5/install/ pip install --no-binary=h5py h5py\n")

self._logfile.flush()
return False
self._logfile.write("\nh5py does not appear to be available.\n")
self._logfile.flush()
return False

# if we made it this far, all is probably good
self._logfile.write("\nh5py configuration succeeded.\n")
Expand All @@ -103,35 +32,8 @@ def check(self, conf, env):

with open('config.log', 'a') as self._logfile:
self._logfile.write("\n\nCONFIGURE H5PY\n")
self._logfile.write("Checking if h5py in built in uw2 already, or provided by system (in that order of preference).\n")
if self._importtest():
return True

self._logfile.write("\n")
self._logfile.write("h5py not found. try build local version using uw2 CC.\n")
if not self.hdf5.result:
self._logfile.write(" libhdf5 not found by config. Please explicitly specify your libhdf5 install via the --hdf5-dir flags.\n")
return False

# let's get h5py
self.pull_from_git( "https://github.com/h5py/h5py", "2.7.0", self._h5pysrc, logfile=self._logfile)

try:
self._buildh5py(self.env['CC'])
if self._importtest():
return True
except:
pass


self._logfile.write("\n")
self._logfile.write("h5py not found, try build local version using h5pcc.\n")
try:
self._buildh5py('h5pcc')
if self._importtest():
return True
except:
pass

return False

Expand Down
51 changes: 0 additions & 51 deletions libUnderworld/config/packages/HDF5.py

This file was deleted.

1 change: 0 additions & 1 deletion libUnderworld/config/packages/__init__.py
@@ -1,7 +1,6 @@
from .libXML2 import libXML2
from .MPI import MPI
from .PETSc import PETSc
from .HDF5 import HDF5
from .spatialdata import spatialdata
from .pcu import pcu
from .Underworld import Underworld
Expand Down
2 changes: 1 addition & 1 deletion underworld/__init__.py
Expand Up @@ -158,7 +158,7 @@ def matplotlib_inline():
if isinstance(underworld.mpi.size, int) and underworld.mpi.size > 1:
_origexcepthook = _sys.excepthook
def _uw_uncaught_exception_handler(exctype, value, tb):
print('An uncaught exception was encountered on processor {}.'.format(uw.mpi.rank))
print('An uncaught exception was encountered on processor {}.'.format(underworld.mpi.rank))
# pass through to original handler
_origexcepthook(exctype, value, tb)
import sys
Expand Down

0 comments on commit e01c14f

Please sign in to comment.