import argparse
import os
import platform
import shutil
import subprocess
import time
from importlib.resources import files as imp_res_files
from pathlib import Path
import numpy
from mpas_tools.logging import check_call
[docs]
def jigsaw_driver(
cellWidth,
x,
y,
on_sphere=True,
earth_radius=6371.0e3,
geom_points=None,
geom_edges=None,
logger=None,
):
"""
A function for building a jigsaw mesh
Parameters
----------
cellWidth : ndarray
The size of each cell in the resulting mesh as a function of space
x, y : ndarray
The x and y coordinates of each point in the cellWidth array (lon and
lat for spherical mesh)
on_sphere : logical, optional
Whether this mesh is spherical or planar
earth_radius : float, optional
Earth radius in meters
geom_points : ndarray, optional
list of point coordinates for bounding polygon for planar mesh
geom_edges : ndarray, optional
list of edges between points in geom_points that define the bounding
polygon
logger : logging.Logger, optional
A logger for the output if not stdout
"""
try:
import jigsawpy
from jigsawpy.savejig import savejig
except ImportError as err:
raise ImportError(
'JIGSAW and/or jigsaw-python is not installed. '
'Please install them in the conda environment.'
) from err
# Authors
# -------
# Mark Petersen, Phillip Wolfram, Xylar Asay-Davis
# setup files for JIGSAW
opts = jigsawpy.jigsaw_jig_t()
opts.geom_file = 'mesh.msh'
opts.jcfg_file = 'mesh.jig'
opts.mesh_file = 'mesh-MESH.msh'
opts.hfun_file = 'mesh-HFUN.msh'
# save HFUN data to file
hmat = jigsawpy.jigsaw_msh_t()
if on_sphere:
hmat.mshID = 'ELLIPSOID-GRID'
hmat.xgrid = numpy.radians(x)
hmat.ygrid = numpy.radians(y)
else:
hmat.mshID = 'EUCLIDEAN-GRID'
hmat.xgrid = x
hmat.ygrid = y
hmat.value = cellWidth
jigsawpy.savemsh(opts.hfun_file, hmat)
# define JIGSAW geometry
geom = jigsawpy.jigsaw_msh_t()
if on_sphere:
geom.mshID = 'ELLIPSOID-MESH'
geom.radii = earth_radius * 1e-3 * numpy.ones(3, float)
else:
geom.mshID = 'EUCLIDEAN-MESH'
geom.vert2 = geom_points
geom.edge2 = geom_edges
jigsawpy.savemsh(opts.geom_file, geom)
# build mesh via JIGSAW!
opts.hfun_scal = 'absolute'
opts.hfun_hmax = float('inf')
opts.hfun_hmin = 0.0
opts.mesh_dims = +2 # 2-dim. simplexes
opts.optm_qlim = 0.9375
opts.verbosity = +1
savejig(opts.jcfg_file, opts)
check_call(['jigsaw', opts.jcfg_file], logger=logger)
[docs]
def build_jigsaw(logger=None, clone=False, subdir='jigsaw-python', hash=None):
"""
Build the JIGSAW and JIGSAW-Python tools using conda-forge compilers
Parameters
----------
logger : logging.Logger, optional
A logger for the output if not stdout
clone : bool, optional
If True, clone the jigsaw-python repository from github
and build it. If False, just build the existing repository.
subdir : str, optional
The subdirectory where the jigsaw-python repository is located or will
be cloned. Default is 'jigsaw-python'.
hash : str, optional
A git hash to checkout after cloning the repository.
Ignored if `clone` is False.
If None and `clone` is True, the latest version will be used.
"""
conda_env_path = os.getenv('CONDA_PREFIX')
if conda_env_path is None:
raise EnvironmentError(
'The CONDA_PREFIX environment variable is not defined. '
'Please activate a conda environment where you want to install '
'jigsaw and jigsawpy before running this function.'
)
conda_exe = os.getenv('CONDA_EXE')
if conda_exe is None:
raise EnvironmentError(
'The CONDA_EXE environment variable is not defined. '
'Please ensure conda is installed and accessible.'
)
conda_base = os.path.dirname(os.path.dirname(conda_exe))
conda_sh_path = os.path.join(conda_base, 'etc', 'profile.d', 'conda.sh')
activate_env = (
f'source {conda_sh_path} && conda activate {conda_env_path} && '
)
# remove conda jigsaw and jigsaw-python
t0 = time.time()
commands = (
f'{activate_env}'
f'pip uninstall -y jigsawpy; '
f'conda remove -y --force-remove jigsaw jigsawpy'
)
try:
check_call(commands, logger=logger, executable='/bin/bash', shell=True)
except subprocess.CalledProcessError:
# ignore errors if not installed
pass
if clone:
url = 'https://github.com/dengwirda/jigsaw-python.git'
commands = f'{activate_env}rm -rf {subdir} && git clone {url} {subdir}'
if hash is not None:
commands += (
f' && cd {subdir} && '
f'git -c advice.detachedHead=false checkout {hash}'
)
check_call(commands, logger=logger, executable='/bin/bash', shell=True)
# add build tools to deployment env, not polaris env
jigsaw_build_deps = 'cmake make libnetcdf setuptools numpy scipy'
if platform.system() == 'Linux':
jigsaw_build_deps = (
f'{jigsaw_build_deps} sysroot_linux-64=2.17 gxx=14 openmp'
)
elif platform.system() == 'Darwin':
jigsaw_build_deps = (
f'{jigsaw_build_deps} '
f'macosx_deployment_target_osx-64=10.13 '
f'clangxx=19 '
f'llvm-openmp=19'
)
print('Install dependencies\n')
# Install dependencies
commands = f'{activate_env}conda install -y {jigsaw_build_deps}'
check_call(commands, logger=logger, executable='/bin/bash', shell=True)
res = imp_res_files('mpas_tools.mesh.creation') / 'conda-toolchain.cmake'
if not os.path.exists(str(res)):
raise FileNotFoundError(
f'The conda toolchain file does not exist: {res}'
)
build_dir = Path(os.path.abspath(subdir)) / 'external' / 'jigsaw' / 'tmp'
if os.path.exists(build_dir):
shutil.rmtree(build_dir)
build_dir.mkdir(parents=True)
toolchain_path = build_dir / 'conda-toolchain.cmake'
with res.open('rb') as src, open(toolchain_path, 'wb') as dst:
shutil.copyfileobj(src, dst)
conda_toolchain = str(toolchain_path)
cmake_args = (
f'-DCMAKE_BUILD_TYPE=Release '
f'-DCMAKE_TOOLCHAIN_FILE="{conda_toolchain}"'
)
print('Building JIGSAW\n')
# Build JIGSAW
commands = (
f'{activate_env}'
f'cd {subdir}/external/jigsaw/tmp && '
f'cmake .. {cmake_args} && '
f'cmake --build . --config Release --target install --parallel 4'
)
check_call(commands, logger=logger, executable='/bin/bash', shell=True)
print('Installing JIGSAW into JIGSAW-Python\n')
# Set up JIGSAW-Python
commands = (
f'{activate_env}'
f'cd {subdir} && '
f'rm -rf jigsawpy/_bin jigsawpy/_lib && '
f'cp -r external/jigsaw/bin/ jigsawpy/_bin && '
f'cp -r external/jigsaw/lib/ jigsawpy/_lib'
)
check_call(commands, logger=logger, executable='/bin/bash', shell=True)
print('Installing JIGSAW-Python\n')
commands = (
f'{activate_env}'
f'cd {subdir} && '
f'python -m pip install --no-deps --no-build-isolation -e . && '
f'cp jigsawpy/_bin/* ${{CONDA_PREFIX}}/bin'
)
check_call(commands, logger=logger, executable='/bin/bash', shell=True)
t1 = time.time()
total = int(t1 - t0 + 0.5)
minutes, seconds = divmod(total, 60)
message = f'JIGSAW install took {minutes} min {seconds} s.'
if logger is None:
print(message)
else:
logger.info(message)
def main_build_jigsaw():
"""
Entry point for building JIGSAW and JIGSAW-Python tools.
"""
parser = argparse.ArgumentParser(
description='Build JIGSAW and JIGSAW-Python tools.'
)
parser.add_argument(
'--clone',
dest='clone',
action='store_true',
help=(
'Clone the jigsaw-python repository into the directory specified '
'with --subdir for you. If --clone is not specified, you must '
'already have cloned it.'
),
)
parser.add_argument(
'--subdir',
dest='subdir',
default='jigsaw-python',
help=(
'A subdirectory of the current work directory where the '
'jigsaw-python repository is located or will be cloned. Default '
'is "jigsaw-python".'
),
)
parser.add_argument(
'--hash',
dest='hash',
help=(
'A git hash to checkout after cloning the repository. '
'Ignored if --clone is not provided. If --clone is provided and '
'--hash is not, the latest version will be used.'
),
)
args = parser.parse_args()
build_jigsaw(clone=args.clone, subdir=args.subdir, hash=args.hash)