├── .gitignore ├── LICENSE.md ├── README.md ├── cache └── .keep ├── example.py ├── files ├── aust.msh ├── mask.vtk ├── topo.msh └── us48.msh ├── image └── voro.jpg ├── setup.cfg └── tests ├── __init__.py ├── case_1_.py ├── case_2_.py ├── case_3_.py ├── case_4_.py ├── case_5_.py ├── case_6_.py └── case_7_.py /.gitignore: -------------------------------------------------------------------------------- 1 | # jigsaw things 2 | cache/*.log 3 | cache/*.jig 4 | cache/*.msh 5 | 6 | # python things 7 | __pycache__/ 8 | *.py[cod] 9 | 10 | build/ 11 | develop-eggs/ 12 | dist/ 13 | eggs/ 14 | parts/ 15 | sdist/ 16 | var/ 17 | *.egg-info/ 18 | .installed.cfg 19 | *.egg 20 | 21 | # data files, etc 22 | *.nc 23 | *.grd 24 | *.vtk 25 | *.off 26 | *.obj 27 | *.stl 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | `JIGSAW` is licensed under the following terms: 2 | 3 | This program may be freely redistributed under the condition that the copyright notices (including this entire header) are not removed, and no compensation is received through use of the software. Private, research, and institutional use is free. You may distribute modified versions of this code `UNDER THE CONDITION THAT THIS CODE AND ANY MODIFICATIONS MADE TO IT IN THE SAME FILE REMAIN UNDER COPYRIGHT OF THE ORIGINAL AUTHOR, BOTH SOURCE AND OBJECT CODE ARE MADE FREELY AVAILABLE WITHOUT CHARGE, AND CLEAR NOTICE IS GIVEN OF THE MODIFICATIONS`. Distribution of this code as part of a commercial system is permissible `ONLY BY DIRECT ARRANGEMENT WITH THE AUTHOR`. (If you are not directly supplying this code to a customer, and you are instead telling them how they can obtain it for free, then you are not required to make any arrangement with me.) 4 | 5 | `DISCLAIMER`: Neither I nor: Columbia University, the Massachusetts Institute of Technology, the University of Sydney, nor the National Aeronautics and Space Administration warrant this code in any way whatsoever. This code is provided "as-is" to be used at your own risk. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## `JIGSAW(GEO): Mesh generation for geoscientific modelling` 2 | 3 |

4 | 5 |

6 | 7 | `JIGSAW(GEO)` is a set of algorithms designed to generate unstructured grids for geoscientific modelling. Applications include: large-scale atmospheric simulation and numerical weather prediction, global and coastal ocean-modelling, and ice-sheet dynamics. 8 | 9 | `JIGSAW(GEO)` can be used to produce high-quality 'generalised' Delaunay / Voronoi tessellations for unstructured finite-volume / element type models. Grids can be generated in local two-dimensional domains, and over general spheroidal surfaces. Mesh resolution can be adapted to follow complex user-defined metrics, including: topographic contours, discrete solution profiles or coastal features. This enables the construction of complex, multi-resolution climate process models, with simulation fidelity enhanced in regions of interest. 10 | 11 | `JIGSAW(GEO)` is typically able to produce the very high-quality staggered unstructured grids required by contemporary unstructued general circulation models (i.e. `MPAS`, `COMPAS`, `FESOM`, etc), generating highly optimised, multi-resolution meshes that are `locally-orthogonal`, `mutually-centroidal` and `self-centred`. 12 | 13 | `JIGSAW(GEO)` depends on the `JIGSAW-PYTHON` package; a `Python` interface to the underlying `JIGSAW` meshing library. 14 | 15 | ### `Quickstart` 16 | 17 | `JIGSAW(GEO)` requires the `JIGSAW` meshing package be installed. `JIGSAW`'s `Python` interface is available here. Once installed, the test problems can be run via: 18 | 19 | clone/download + unpack this repository. 20 | python3 example.py --IDnumber=1 21 | 22 | Note: installation of `JIGSAW` requires a `c++` compiler and the `cmake` utility. `JIGSAW` may also be installed as a `conda` package. See here for details. 23 | 24 | ### `Example Problems` 25 | 26 | The following set of example problems are available in `example.py`: 27 | 28 | example: 1; # generate a uniform resolution global grid 29 | example: 2; # generate a regionally-refined global grid 30 | example: 3; # build smooth mesh-spacing functions from noisy input data 31 | example: 4; # generate a complex, variable resolution global grid 32 | example: 5; # generate structured icosahedral and cubedsphere meshes 33 | example: 6; # generate a coastal mesh for the Australasian region 34 | example: 7; # generate a multi-part mesh for the (contiguous) USA 35 | 36 | Run `python3 example.py --IDnumber=N` to call the `N-th` example. `*.vtk` output is saved to `../cache` and can be visualised with, for example, Paraview. 37 | 38 | Additional material and discussion can be found in the worked examples here. 39 | 40 | ### `License` 41 | 42 | This program may be freely redistributed under the condition that the copyright notices (including this entire header) are not removed, and no compensation is received through use of the software. Private, research, and institutional use is free. You may distribute modified versions of this code `UNDER THE CONDITION THAT THIS CODE AND ANY MODIFICATIONS MADE TO IT IN THE SAME FILE REMAIN UNDER COPYRIGHT OF THE ORIGINAL AUTHOR, BOTH SOURCE AND OBJECT CODE ARE MADE FREELY AVAILABLE WITHOUT CHARGE, AND CLEAR NOTICE IS GIVEN OF THE MODIFICATIONS`. Distribution of this code as part of a commercial system is permissible `ONLY BY DIRECT ARRANGEMENT WITH THE AUTHOR`. (If you are not directly supplying this code to a customer, and you are instead telling them how they can obtain it for free, then you are not required to make any arrangement with me.) 43 | 44 | `DISCLAIMER`: Neither I nor: Columbia University, the Massachusetts Institute of Technology, the University of Sydney, nor the National Aeronautics and Space Administration warrant this code in any way whatsoever. This code is provided "as-is" to be used at your own risk. 45 | 46 | ### `References` 47 | 48 | There are a number of publications that describe the algorithms used in `JIGSAW(GEO)` in detail. Additional information and references regarding the formulation of the underlying `JIGSAW` mesh-generator can also be found here. If you make use of `JIGSAW` in your work, please consider including a reference to the following: 49 | 50 | `[1]` - Darren Engwirda: Generalised primal-dual grids for unstructured co-volume schemes, J. Comp. Phys., 375, pp. 155-176, https://doi.org/10.1016/j.jcp.2018.07.025, 2018. 51 | 52 | `[2]` - Darren Engwirda: JIGSAW-GEO (1.0): locally orthogonal staggered unstructured grid generation for general circulation modelling on the sphere, Geosci. Model Dev., 10, pp. 2117-2140, https://doi.org/10.5194/gmd-10-2117-2017, 2017. 53 | 54 | `[3]` - Darren Engwirda, David Ivers, Off-centre Steiner points for Delaunay-refinement on curved surfaces, Computer-Aided Design, 72, pp. 157-171, http://dx.doi.org/10.1016/j.cad.2015.10.007, 2016. 55 | 56 | `[4]` - Darren Engwirda: Multi-resolution unstructured grid-generation for geophysical applications on the sphere, Research note, Proceedings of the 24th International Meshing Roundtable, https://arxiv.org/abs/1512.00307, 2015. 57 | 58 | `[5]` - Darren Engwirda, Locally-optimal Delaunay-refinement and optimisation-based mesh generation, Ph.D. Thesis, School of Mathematics and Statistics, The University of Sydney, http://hdl.handle.net/2123/13148, 2014. 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /cache/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dengwirda/jigsaw-geo-python/24fc1aaf72d7693ae7155e292c38daff03043a1e/cache/.keep -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import argparse 5 | 6 | from tests.case_1_ import case_1_ 7 | from tests.case_2_ import case_2_ 8 | from tests.case_3_ import case_3_ 9 | from tests.case_4_ import case_4_ 10 | from tests.case_5_ import case_5_ 11 | from tests.case_6_ import case_6_ 12 | from tests.case_7_ import case_7_ 13 | 14 | 15 | def example(IDnumber=0): 16 | 17 | #--------------- delegate to the individual example cases... 18 | 19 | src_path = os.path.join( 20 | os.path.abspath( 21 | os.path.dirname(__file__)), "files") 22 | 23 | dst_path = os.path.join( 24 | os.path.abspath( 25 | os.path.dirname(__file__)), "cache") 26 | 27 | if (IDnumber == +1): 28 | case_1_(src_path, dst_path) 29 | 30 | elif (IDnumber == +2): 31 | case_2_(src_path, dst_path) 32 | 33 | elif (IDnumber == +3): 34 | case_3_(src_path, dst_path) 35 | 36 | elif (IDnumber == +4): 37 | case_4_(src_path, dst_path) 38 | 39 | elif (IDnumber == +5): 40 | case_5_(src_path, dst_path) 41 | 42 | elif (IDnumber == +6): 43 | case_6_(src_path, dst_path) 44 | 45 | elif (IDnumber == +7): 46 | case_7_(src_path, dst_path) 47 | 48 | return 49 | 50 | 51 | if __name__ == "__main__": 52 | parser = argparse.ArgumentParser( 53 | description=__doc__, formatter_class=argparse.RawTextHelpFormatter) 54 | 55 | parser.add_argument("--IDnumber", dest="IDnumber", type=int, 56 | required=True, help="Run example with ID = (1-7).") 57 | 58 | args = parser.parse_args() 59 | 60 | example(IDnumber=args.IDnumber) 61 | -------------------------------------------------------------------------------- /image/voro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dengwirda/jigsaw-geo-python/24fc1aaf72d7693ae7155e292c38daff03043a1e/image/voro.jpg -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = W503,W504,E115,E265,E271,E701,E702,E704 3 | exclude = jigsawpy/msh_l.py, 4 | jigsawpy/jig_l.py 5 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dengwirda/jigsaw-geo-python/24fc1aaf72d7693ae7155e292c38daff03043a1e/tests/__init__.py -------------------------------------------------------------------------------- /tests/case_1_.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import math 4 | import time 5 | import numpy as np 6 | from scipy import interpolate 7 | 8 | import jigsawpy 9 | 10 | 11 | def case_1_(src_path, dst_path): 12 | 13 | # DEMO-1: generate a uniform resolution (150KM) global grid. 14 | 15 | opts = jigsawpy.jigsaw_jig_t() 16 | 17 | topo = jigsawpy.jigsaw_msh_t() 18 | 19 | geom = jigsawpy.jigsaw_msh_t() 20 | mesh = jigsawpy.jigsaw_msh_t() 21 | 22 | #------------------------------------ setup files for JIGSAW 23 | 24 | opts.geom_file = \ 25 | os.path.join(dst_path, "geom.msh") 26 | 27 | opts.jcfg_file = \ 28 | os.path.join(dst_path, "opts.jig") 29 | 30 | opts.mesh_file = \ 31 | os.path.join(dst_path, "mesh.msh") 32 | 33 | #------------------------------------ define JIGSAW geometry 34 | 35 | geom.mshID = "ellipsoid-mesh" 36 | geom.radii = np.full( 37 | 3, 6.371E+003, dtype=geom.REALS_t) 38 | 39 | jigsawpy.savemsh(opts.geom_file, geom) 40 | 41 | #------------------------------------ make mesh using JIGSAW 42 | 43 | opts.hfun_scal = "absolute" 44 | opts.hfun_hmax = +150. # uniform at 150km 45 | 46 | opts.mesh_dims = +2 # 2-dim. simplexes 47 | 48 | opts.optm_qlim = +9.5E-01 # tighter opt. tol 49 | opts.optm_iter = +32 50 | opts.optm_qtol = +1.0E-05 51 | 52 | # opts.optm_kern = "cvt+dqdx" 53 | 54 | rbar = np.mean(geom.radii) # bisect heuristic 55 | 56 | nlev = round(math.log2( 57 | rbar / math.sin(.4 * math.pi) / opts.hfun_hmax) 58 | ) 59 | 60 | ttic = time.time() 61 | 62 | jigsawpy.cmd.tetris(opts, nlev - 1, mesh) 63 | 64 | ttoc = time.time() 65 | 66 | print("CPUSEC =", (ttoc - ttic)) 67 | 68 | print("BISECT =", +nlev) 69 | 70 | cost = jigsawpy.triscr2( # quality metrics! 71 | mesh.point["coord"], 72 | mesh.tria3["index"]) 73 | 74 | print("TRISCR =", np.min(cost), np.mean(cost)) 75 | 76 | cost = jigsawpy.pwrscr2( 77 | mesh.point["coord"], 78 | mesh.power, 79 | mesh.tria3["index"]) 80 | 81 | print("PWRSCR =", np.min(cost), np.mean(cost)) 82 | 83 | tbad = jigsawpy.centre2( 84 | mesh.point["coord"], 85 | mesh.power, 86 | mesh.tria3["index"]) 87 | 88 | print("OBTUSE =", 89 | +np.count_nonzero(np.logical_not(tbad))) 90 | 91 | ndeg = jigsawpy.trideg2( 92 | mesh.point["coord"], 93 | mesh.tria3["index"]) 94 | 95 | print("TOPOL. =", 96 | +np.count_nonzero(ndeg==+6) / ndeg.size) 97 | 98 | #------------------------------------ save mesh for Paraview 99 | 100 | jigsawpy.loadmsh(os.path.join( 101 | src_path, "topo.msh"), topo) 102 | 103 | #------------------------------------ a very rough land mask 104 | 105 | apos = jigsawpy.R3toS2( 106 | geom.radii, mesh.point["coord"][:]) 107 | 108 | apos = apos * 180. / np.pi 109 | 110 | zfun = interpolate.RectBivariateSpline( 111 | topo.ygrid, topo.xgrid, topo.value) 112 | 113 | mesh.value = zfun( 114 | apos[:, 1], apos[:, 0], grid=False) 115 | 116 | cell = mesh.tria3["index"] 117 | 118 | zmsk = \ 119 | mesh.value[cell[:, 0]] + \ 120 | mesh.value[cell[:, 1]] + \ 121 | mesh.value[cell[:, 2]] 122 | zmsk = zmsk / +3.0 123 | 124 | mesh.tria3 = mesh.tria3[zmsk < +0.] 125 | 126 | print("Saving to ../cache/case_1a.vtk") 127 | 128 | jigsawpy.savevtk(os.path.join( 129 | dst_path, "case_1a.vtk"), mesh) 130 | 131 | return 132 | -------------------------------------------------------------------------------- /tests/case_2_.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import time 4 | import math 5 | import numpy as np 6 | from scipy import interpolate 7 | 8 | import jigsawpy 9 | 10 | 11 | def case_2_(src_path, dst_path): 12 | 13 | # DEMO-2: generate a regionally-refined global grid with a 14 | # high-resolution 37.5km patch embedded in a uniform 150km 15 | # background grid. 16 | 17 | opts = jigsawpy.jigsaw_jig_t() 18 | 19 | topo = jigsawpy.jigsaw_msh_t() 20 | 21 | geom = jigsawpy.jigsaw_msh_t() 22 | hfun = jigsawpy.jigsaw_msh_t() 23 | mesh = jigsawpy.jigsaw_msh_t() 24 | 25 | #------------------------------------ setup files for JIGSAW 26 | 27 | opts.geom_file = \ 28 | os.path.join(dst_path, "geom.msh") 29 | 30 | opts.jcfg_file = \ 31 | os.path.join(dst_path, "opts.jig") 32 | 33 | opts.mesh_file = \ 34 | os.path.join(dst_path, "mesh.msh") 35 | 36 | opts.hfun_file = \ 37 | os.path.join(dst_path, "spac.msh") 38 | 39 | #------------------------------------ define JIGSAW geometry 40 | 41 | geom.mshID = "ellipsoid-mesh" 42 | geom.radii = np.full( 43 | 3, 6.371E+003, dtype=geom.REALS_t) 44 | 45 | jigsawpy.savemsh(opts.geom_file, geom) 46 | 47 | #------------------------------------ define spacing pattern 48 | 49 | hfun.mshID = "ellipsoid-grid" 50 | hfun.radii = geom.radii 51 | 52 | hfun.xgrid = np.linspace( 53 | -1. * np.pi, +1. * np.pi, 360) 54 | 55 | hfun.ygrid = np.linspace( 56 | -.5 * np.pi, +.5 * np.pi, 180) 57 | 58 | xmat, ymat = \ 59 | np.meshgrid(hfun.xgrid, hfun.ygrid) 60 | 61 | hfun.value = +150. - 112.5 * np.exp(-( 62 | +1.5 * (xmat + 1.0) ** 2 + 63 | +1.5 * (ymat - 0.5) ** 2) ** 4) 64 | 65 | jigsawpy.savemsh(opts.hfun_file, hfun) 66 | 67 | #------------------------------------ make mesh using JIGSAW 68 | 69 | opts.hfun_scal = "absolute" 70 | opts.hfun_hmax = float("inf") # null HFUN limits 71 | opts.hfun_hmin = float(+0.00) 72 | 73 | opts.mesh_dims = +2 # 2-dim. simplexes 74 | 75 | opts.optm_qlim = +9.5E-01 # tighter opt. tol 76 | opts.optm_iter = +32 77 | opts.optm_qtol = +1.0E-05 78 | 79 | # opts.optm_kern = "cvt+dqdx" 80 | 81 | rbar = np.mean(geom.radii) # bisect heuristic 82 | hbar = np.mean(hfun.value) 83 | nlev = round(math.log2( 84 | rbar / math.sin(.4 * math.pi) / hbar) 85 | ) 86 | 87 | ttic = time.time() 88 | 89 | jigsawpy.cmd.tetris(opts, nlev - 1, mesh) 90 | 91 | ttoc = time.time() 92 | 93 | print("CPUSEC =", (ttoc - ttic)) 94 | 95 | print("BISECT =", +nlev) 96 | 97 | cost = jigsawpy.triscr2( # quality metrics! 98 | mesh.point["coord"], 99 | mesh.tria3["index"]) 100 | 101 | print("TRISCR =", np.min(cost), np.mean(cost)) 102 | 103 | cost = jigsawpy.pwrscr2( 104 | mesh.point["coord"], 105 | mesh.power, 106 | mesh.tria3["index"]) 107 | 108 | print("PWRSCR =", np.min(cost), np.mean(cost)) 109 | 110 | tbad = jigsawpy.centre2( 111 | mesh.point["coord"], 112 | mesh.power, 113 | mesh.tria3["index"]) 114 | 115 | print("OBTUSE =", 116 | +np.count_nonzero(np.logical_not(tbad))) 117 | 118 | ndeg = jigsawpy.trideg2( 119 | mesh.point["coord"], 120 | mesh.tria3["index"]) 121 | 122 | print("TOPOL. =", 123 | +np.count_nonzero(ndeg==+6) / ndeg.size) 124 | 125 | #------------------------------------ save mesh for Paraview 126 | 127 | jigsawpy.loadmsh(os.path.join( 128 | src_path, "topo.msh"), topo) 129 | 130 | #------------------------------------ a very rough land mask 131 | 132 | apos = jigsawpy.R3toS2( 133 | geom.radii, mesh.point["coord"][:]) 134 | 135 | apos = apos * 180. / np.pi 136 | 137 | zfun = interpolate.RectBivariateSpline( 138 | topo.ygrid, topo.xgrid, topo.value) 139 | 140 | mesh.value = zfun( 141 | apos[:, 1], apos[:, 0], grid=False) 142 | 143 | cell = mesh.tria3["index"] 144 | 145 | zmsk = \ 146 | mesh.value[cell[:, 0]] + \ 147 | mesh.value[cell[:, 1]] + \ 148 | mesh.value[cell[:, 2]] 149 | zmsk = zmsk / +3.0 150 | 151 | mesh.tria3 = mesh.tria3[zmsk < +0.] 152 | 153 | print("Saving to ../cache/case_2a.vtk") 154 | 155 | jigsawpy.savevtk(os.path.join( 156 | dst_path, "case_2a.vtk"), mesh) 157 | 158 | print("Saving to ../cache/case_2b.vtk") 159 | 160 | jigsawpy.savevtk(os.path.join( 161 | dst_path, "case_2b.vtk"), hfun) 162 | 163 | return 164 | -------------------------------------------------------------------------------- /tests/case_3_.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import copy 4 | import numpy as np 5 | 6 | import jigsawpy 7 | 8 | 9 | def case_3_(src_path, dst_path): 10 | 11 | # DEMO-3: generate multi-resolution spacing, via local refi- 12 | # nement along coastlines and shallow ridges. Global grid 13 | # resolution is 150KM, background resolution is 67KM and the 14 | # min. adaptive resolution is 33KM. 15 | 16 | opts = jigsawpy.jigsaw_jig_t() 17 | 18 | topo = jigsawpy.jigsaw_msh_t() 19 | 20 | geom = jigsawpy.jigsaw_msh_t() 21 | 22 | hraw = jigsawpy.jigsaw_msh_t() 23 | hlim = jigsawpy.jigsaw_msh_t() 24 | 25 | #------------------------------------ setup files for JIGSAW 26 | 27 | opts.geom_file = \ 28 | os.path.join(dst_path, "geom.msh") 29 | 30 | opts.jcfg_file = \ 31 | os.path.join(dst_path, "opts.jig") 32 | 33 | opts.hfun_file = \ 34 | os.path.join(dst_path, "spac.msh") 35 | 36 | #------------------------------------ define JIGSAW geometry 37 | 38 | geom.mshID = "ellipsoid-mesh" 39 | geom.radii = np.full( 40 | 3, 6.371E+003, dtype=geom.REALS_t) 41 | 42 | jigsawpy.savemsh(opts.geom_file, geom) 43 | 44 | #------------------------------------ define spacing pattern 45 | 46 | jigsawpy.loadmsh(os.path.join( 47 | src_path, "topo.msh"), topo) 48 | 49 | hraw.mshID = "ellipsoid-grid" 50 | hraw.radii = geom.radii 51 | 52 | hraw.xgrid = topo.xgrid * np.pi / 180. 53 | hraw.ygrid = topo.ygrid * np.pi / 180. 54 | 55 | hfn0 = +150. # global spacing 56 | hfn2 = +33. # adapt. spacing 57 | hfn3 = +67. # arctic spacing 58 | 59 | hraw.value = np.sqrt( 60 | np.maximum(-topo.value, 0.0)) 61 | 62 | hraw.value = \ 63 | np.maximum(hraw.value, hfn2) 64 | hraw.value = \ 65 | np.minimum(hraw.value, hfn3) 66 | 67 | mask = hraw.ygrid < 40. * np.pi / 180. 68 | 69 | hraw.value[mask] = hfn0 70 | 71 | #------------------------------------ set HFUN grad.-limiter 72 | 73 | hlim = copy.copy(hraw) 74 | 75 | hlim.slope = np.full( # |dH/dx| limits 76 | topo.value.shape, 77 | +0.050, dtype=hlim.REALS_t) 78 | 79 | jigsawpy.savemsh(opts.hfun_file, hlim) 80 | 81 | jigsawpy.cmd.marche(opts, hlim) 82 | 83 | #------------------------------------ save mesh for Paraview 84 | 85 | print("Saving to ../cache/case_3a.vtk") 86 | 87 | jigsawpy.savevtk(os.path.join( 88 | dst_path, "case_3a.vtk"), hraw) 89 | 90 | print("Saving to ../cache/case_3b.vtk") 91 | 92 | jigsawpy.savevtk(os.path.join( 93 | dst_path, "case_3b.vtk"), hlim) 94 | 95 | return 96 | -------------------------------------------------------------------------------- /tests/case_4_.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import time 4 | import math 5 | import numpy as np 6 | from scipy import interpolate 7 | 8 | import jigsawpy 9 | 10 | 11 | def case_4_(src_path, dst_path): 12 | 13 | # DEMO-4: generate a multi-resolution mesh, via local refin- 14 | # ement along coastlines and shallow ridges. Global grid 15 | # resolution is 150KM, background resolution is 67KM and the 16 | # min. adaptive resolution is 33KM. 17 | 18 | opts = jigsawpy.jigsaw_jig_t() 19 | 20 | topo = jigsawpy.jigsaw_msh_t() 21 | 22 | geom = jigsawpy.jigsaw_msh_t() 23 | mesh = jigsawpy.jigsaw_msh_t() 24 | hmat = jigsawpy.jigsaw_msh_t() 25 | 26 | #------------------------------------ setup files for JIGSAW 27 | 28 | opts.geom_file = \ 29 | os.path.join(dst_path, "geom.msh") 30 | 31 | opts.jcfg_file = \ 32 | os.path.join(dst_path, "opts.jig") 33 | 34 | opts.mesh_file = \ 35 | os.path.join(dst_path, "mesh.msh") 36 | 37 | opts.hfun_file = \ 38 | os.path.join(dst_path, "spac.msh") 39 | 40 | #------------------------------------ define JIGSAW geometry 41 | 42 | geom.mshID = "ellipsoid-mesh" 43 | geom.radii = np.full( 44 | 3, 6.371E+003, dtype=geom.REALS_t) 45 | 46 | jigsawpy.savemsh(opts.geom_file, geom) 47 | 48 | #------------------------------------ define spacing pattern 49 | 50 | jigsawpy.loadmsh(os.path.join( 51 | src_path, "topo.msh"), topo) 52 | 53 | hmat.mshID = "ellipsoid-grid" 54 | hmat.radii = geom.radii 55 | 56 | hmat.xgrid = topo.xgrid * np.pi / 180. 57 | hmat.ygrid = topo.ygrid * np.pi / 180. 58 | 59 | hfn0 = +150. # global spacing 60 | hfn2 = +33. # adapt. spacing 61 | hfn3 = +67. # arctic spacing 62 | 63 | hmat.value = np.sqrt( 64 | np.maximum(-topo.value, 0.0)) 65 | 66 | hmat.value = \ 67 | np.maximum(hmat.value, hfn2) 68 | hmat.value = \ 69 | np.minimum(hmat.value, hfn3) 70 | 71 | mask = hmat.ygrid < 40. * np.pi / 180. 72 | 73 | hmat.value[mask] = hfn0 74 | 75 | #------------------------------------ set HFUN grad.-limiter 76 | 77 | hmat.slope = np.full( # |dH/dx| limits 78 | topo.value.shape, 79 | +0.050, dtype=hmat.REALS_t) 80 | 81 | jigsawpy.savemsh(opts.hfun_file, hmat) 82 | 83 | jigsawpy.cmd.marche(opts, hmat) 84 | 85 | #------------------------------------ make mesh using JIGSAW 86 | 87 | opts.hfun_scal = "absolute" 88 | opts.hfun_hmax = float("inf") # null HFUN limits 89 | opts.hfun_hmin = float(+0.00) 90 | 91 | opts.mesh_dims = +2 # 2-dim. simplexes 92 | 93 | opts.optm_qlim = +9.5E-01 # tighter opt. tol 94 | opts.optm_iter = +32 95 | opts.optm_qtol = +1.0E-05 96 | 97 | # opts.optm_kern = "cvt+dqdx" 98 | 99 | rbar = np.mean(geom.radii) # bisect heuristic 100 | hbar = np.mean(hmat.value) 101 | nlev = round(math.log2( 102 | rbar / math.sin(.4 * math.pi) / hbar) 103 | ) 104 | 105 | ttic = time.time() 106 | 107 | jigsawpy.cmd.tetris(opts, nlev - 1, mesh) 108 | 109 | ttoc = time.time() 110 | 111 | print("CPUSEC =", (ttoc - ttic)) 112 | 113 | print("BISECT =", +nlev) 114 | 115 | cost = jigsawpy.triscr2( # quality metrics! 116 | mesh.point["coord"], 117 | mesh.tria3["index"]) 118 | 119 | print("TRISCR =", np.min(cost), np.mean(cost)) 120 | 121 | cost = jigsawpy.pwrscr2( 122 | mesh.point["coord"], 123 | mesh.power, 124 | mesh.tria3["index"]) 125 | 126 | print("PWRSCR =", np.min(cost), np.mean(cost)) 127 | 128 | tbad = jigsawpy.centre2( 129 | mesh.point["coord"], 130 | mesh.power, 131 | mesh.tria3["index"]) 132 | 133 | print("OBTUSE =", 134 | +np.count_nonzero(np.logical_not(tbad))) 135 | 136 | ndeg = jigsawpy.trideg2( 137 | mesh.point["coord"], 138 | mesh.tria3["index"]) 139 | 140 | print("TOPOL. =", 141 | +np.count_nonzero(ndeg==+6) / ndeg.size) 142 | 143 | #------------------------------------ save mesh for Paraview 144 | 145 | apos = jigsawpy.R3toS2( 146 | geom.radii, mesh.point["coord"][:]) 147 | 148 | apos = apos * 180. / np.pi 149 | 150 | zfun = interpolate.RectBivariateSpline( 151 | topo.ygrid, topo.xgrid, topo.value) 152 | 153 | mesh.value = zfun( 154 | apos[:, 1], apos[:, 0], grid=False) 155 | 156 | cell = mesh.tria3["index"] 157 | 158 | zmsk = \ 159 | mesh.value[cell[:, 0]] + \ 160 | mesh.value[cell[:, 1]] + \ 161 | mesh.value[cell[:, 2]] 162 | zmsk = zmsk / +3.0 163 | 164 | mesh.tria3 = mesh.tria3[zmsk < +0.] 165 | 166 | print("Saving to ../cache/case_4a.vtk") 167 | 168 | jigsawpy.savevtk(os.path.join( 169 | dst_path, "case_4a.vtk"), mesh) 170 | 171 | print("Saving to ../cache/case_4b.vtk") 172 | 173 | jigsawpy.savevtk(os.path.join( 174 | dst_path, "case_4b.vtk"), hmat) 175 | 176 | return 177 | -------------------------------------------------------------------------------- /tests/case_5_.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import time 4 | import numpy as np 5 | from scipy import interpolate 6 | 7 | import jigsawpy 8 | 9 | 10 | def case_5_(src_path, dst_path): 11 | 12 | # DEMO-5: generate struct. icosahedral and cubedsphere grids 13 | 14 | opts = jigsawpy.jigsaw_jig_t() 15 | 16 | topo = jigsawpy.jigsaw_msh_t() 17 | 18 | geom = jigsawpy.jigsaw_msh_t() 19 | icos = jigsawpy.jigsaw_msh_t() 20 | cube = jigsawpy.jigsaw_msh_t() 21 | 22 | #------------------------------------ setup files for JIGSAW 23 | 24 | opts.geom_file = \ 25 | os.path.join(dst_path, "geom.msh") 26 | 27 | opts.jcfg_file = \ 28 | os.path.join(dst_path, "opts.jig") 29 | 30 | opts.mesh_file = \ 31 | os.path.join(dst_path, "mesh.msh") 32 | 33 | #------------------------------------ define JIGSAW geometry 34 | 35 | geom.mshID = "ellipsoid-mesh" 36 | geom.radii = np.full( 37 | 3, 1.000E+000, dtype=geom.REALS_t) 38 | 39 | jigsawpy.savemsh(opts.geom_file, geom) 40 | 41 | #------------------------------------ make mesh using JIGSAW 42 | 43 | opts.hfun_hmax = +1. 44 | opts.mesh_dims = +2 # 2-dim. simplexes 45 | 46 | opts.optm_iter = +512 47 | opts.optm_qtol = +1.0E-06 48 | 49 | # opts.optm_kern = "cvt+dqdx" 50 | 51 | ttic = time.time() 52 | 53 | jigsawpy.cmd.icosahedron(opts, +6, icos) 54 | 55 | ttoc = time.time() 56 | 57 | print("CPUSEC =", (ttoc - ttic)) 58 | 59 | ttic = time.time() 60 | 61 | jigsawpy.cmd.cubedsphere(opts, +6, cube) 62 | 63 | ttoc = time.time() 64 | 65 | print("CPUSEC =", (ttoc - ttic)) 66 | 67 | #------------------------------------ save mesh for Paraview 68 | 69 | jigsawpy.loadmsh(os.path.join( 70 | src_path, "topo.msh"), topo) 71 | 72 | #------------------------------------ a very rough land mask 73 | 74 | zfun = interpolate.RectBivariateSpline( 75 | topo.ygrid, topo.xgrid, topo.value) 76 | 77 | apos = jigsawpy.R3toS2( 78 | geom.radii, icos.point["coord"][:]) 79 | 80 | apos = apos * 180. / np.pi 81 | 82 | icos.value = zfun( 83 | apos[:, 1], apos[:, 0], grid=False) 84 | 85 | cell = icos.tria3["index"] 86 | 87 | zmsk = \ 88 | icos.value[cell[:, 0]] + \ 89 | icos.value[cell[:, 1]] + \ 90 | icos.value[cell[:, 2]] 91 | zmsk = zmsk / +3.0 92 | 93 | icos.tria3 = icos.tria3[zmsk < +0.] 94 | 95 | print("Saving to ../cache/case_5a.vtk") 96 | 97 | jigsawpy.savevtk(os.path.join( 98 | dst_path, "case_5a.vtk"), icos) 99 | 100 | #------------------------------------ a very rough land mask 101 | 102 | apos = jigsawpy.R3toS2( 103 | geom.radii, cube.point["coord"][:]) 104 | 105 | apos = apos * 180. / np.pi 106 | 107 | cube.value = zfun( 108 | apos[:, 1], apos[:, 0], grid=False) 109 | 110 | cell = cube.quad4["index"] 111 | 112 | zmsk = \ 113 | cube.value[cell[:, 0]] + \ 114 | cube.value[cell[:, 1]] + \ 115 | cube.value[cell[:, 2]] + \ 116 | cube.value[cell[:, 3]] 117 | zmsk = zmsk / +4.0 118 | 119 | cube.quad4 = cube.quad4[zmsk < +0.] 120 | 121 | print("Saving to ../cache/case_5b.vtk") 122 | 123 | jigsawpy.savevtk(os.path.join( 124 | dst_path, "case_5b.vtk"), cube) 125 | 126 | return 127 | -------------------------------------------------------------------------------- /tests/case_6_.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import time 4 | import numpy as np 5 | 6 | import jigsawpy 7 | 8 | 9 | def case_6_(src_path, dst_path): 10 | 11 | # DEMO-6: generate a 2-dim. grid for the Australian coastal 12 | # region, using scaled ocean-depth as a mesh-resolution 13 | # heuristic. A local stereographic projection is employed. 14 | 15 | opts = jigsawpy.jigsaw_jig_t() 16 | 17 | topo = jigsawpy.jigsaw_msh_t() 18 | 19 | geom = jigsawpy.jigsaw_msh_t() 20 | mesh = jigsawpy.jigsaw_msh_t() 21 | hmat = jigsawpy.jigsaw_msh_t() 22 | 23 | proj = jigsawpy.jigsaw_prj_t() 24 | 25 | #------------------------------------ setup files for JIGSAW 26 | 27 | opts.geom_file = \ 28 | os.path.join(dst_path, "geom.msh") 29 | 30 | opts.jcfg_file = \ 31 | os.path.join(dst_path, "aust.jig") 32 | 33 | opts.mesh_file = \ 34 | os.path.join(dst_path, "mesh.msh") 35 | 36 | opts.hfun_file = \ 37 | os.path.join(dst_path, "spac.msh") 38 | 39 | #------------------------------------ define JIGSAW geometry 40 | 41 | jigsawpy.loadmsh(os.path.join( 42 | src_path, "aust.msh"), geom) 43 | 44 | jigsawpy.loadmsh(os.path.join( 45 | src_path, "topo.msh"), topo) 46 | 47 | xmin = np.min( 48 | geom.point["coord"][:, 0]) 49 | ymin = np.min( 50 | geom.point["coord"][:, 1]) 51 | xmax = np.max( 52 | geom.point["coord"][:, 0]) 53 | ymax = np.max( 54 | geom.point["coord"][:, 1]) 55 | 56 | zlev = topo.value 57 | 58 | xmsk = np.logical_and(topo.xgrid > xmin, 59 | topo.xgrid < xmax) 60 | ymsk = np.logical_and(topo.ygrid > ymin, 61 | topo.ygrid < ymax) 62 | 63 | zlev = zlev[:, xmsk] 64 | zlev = zlev[ymsk, :] 65 | 66 | #------------------------------------ define spacing pattern 67 | 68 | hmat.mshID = "ellipsoid-grid" 69 | hmat.radii = np.full( 70 | +3, +6371.0, 71 | dtype=jigsawpy.jigsaw_msh_t.REALS_t) 72 | 73 | hmat.xgrid = \ 74 | topo.xgrid[xmsk] * np.pi / 180. 75 | hmat.ygrid = \ 76 | topo.ygrid[ymsk] * np.pi / 180. 77 | 78 | hmin = +1.0E+01; hmax = +1.0E+02 79 | 80 | hmat.value = \ 81 | np.sqrt(np.maximum(-zlev, 0.)) / 0.5 82 | 83 | hmat.value = \ 84 | np.maximum(hmat.value, hmin) 85 | hmat.value = \ 86 | np.minimum(hmat.value, hmax) 87 | 88 | hmat.slope = np.full( 89 | hmat.value.shape, +0.1500, 90 | dtype=jigsawpy.jigsaw_msh_t.REALS_t) 91 | 92 | #------------------------------------ do stereographic proj. 93 | 94 | geom.point["coord"][:, :] *= np.pi / 180. 95 | 96 | proj.prjID = 'stereographic' 97 | proj.radii = +6.371E+003 98 | proj.xbase = \ 99 | +0.500 * (xmin + xmax) * np.pi / 180. 100 | proj.ybase = \ 101 | +0.500 * (ymin + ymax) * np.pi / 180. 102 | 103 | jigsawpy.project(geom, proj, "fwd") 104 | jigsawpy.project(hmat, proj, "fwd") 105 | 106 | jigsawpy.savemsh(opts.geom_file, geom) 107 | jigsawpy.savemsh(opts.hfun_file, hmat) 108 | 109 | #------------------------------------ set HFUN grad.-limiter 110 | 111 | jigsawpy.cmd.marche(opts, hmat) 112 | 113 | #------------------------------------ make mesh using JIGSAW 114 | 115 | opts.hfun_scal = "absolute" 116 | opts.hfun_hmax = float("inf") # null HFUN limits 117 | opts.hfun_hmin = float(+0.00) 118 | 119 | opts.mesh_dims = +2 # 2-dim. simplexes 120 | opts.mesh_eps1 = +1. 121 | 122 | ttic = time.time() 123 | 124 | jigsawpy.cmd.jigsaw(opts, mesh) 125 | 126 | ttoc = time.time() 127 | 128 | print("CPUSEC =", (ttoc - ttic)) 129 | 130 | cost = jigsawpy.triscr2( # quality metrics! 131 | mesh.point["coord"], 132 | mesh.tria3["index"]) 133 | 134 | print("TRISCR =", np.min(cost), np.mean(cost)) 135 | 136 | cost = jigsawpy.pwrscr2( 137 | mesh.point["coord"], 138 | mesh.power, 139 | mesh.tria3["index"]) 140 | 141 | print("PWRSCR =", np.min(cost), np.mean(cost)) 142 | 143 | tbad = jigsawpy.centre2( 144 | mesh.point["coord"], 145 | mesh.power, 146 | mesh.tria3["index"]) 147 | 148 | print("OBTUSE =", 149 | +np.count_nonzero(np.logical_not(tbad))) 150 | 151 | #------------------------------------ save mesh for Paraview 152 | 153 | print("Saving to ../cache/case_6a.vtk") 154 | 155 | jigsawpy.savevtk(os.path.join( 156 | dst_path, "case_6a.vtk"), mesh) 157 | 158 | print("Saving to ../cache/case_6b.vtk") 159 | 160 | jigsawpy.savevtk(os.path.join( 161 | dst_path, "case_6b.vtk"), hmat) 162 | 163 | return 164 | -------------------------------------------------------------------------------- /tests/case_7_.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import time 4 | import numpy as np 5 | 6 | import jigsawpy 7 | 8 | 9 | def case_7_(src_path, dst_path): 10 | 11 | # DEMO-7: generate a multi-part mesh of the (contiguous) USA 12 | # using state boundaries to partition the mesh. A local 13 | # stereographic projection is employed. The domain is meshed 14 | # using uniform resolution. 15 | 16 | opts = jigsawpy.jigsaw_jig_t() 17 | 18 | geom = jigsawpy.jigsaw_msh_t() 19 | mesh = jigsawpy.jigsaw_msh_t() 20 | 21 | proj = jigsawpy.jigsaw_prj_t() 22 | 23 | #------------------------------------ setup files for JIGSAW 24 | 25 | opts.geom_file = \ 26 | os.path.join(dst_path, "geom.msh") 27 | 28 | opts.jcfg_file = \ 29 | os.path.join(dst_path, "us48.jig") 30 | 31 | opts.mesh_file = \ 32 | os.path.join(dst_path, "mesh.msh") 33 | 34 | #------------------------------------ define JIGSAW geometry 35 | 36 | jigsawpy.loadmsh(os.path.join( 37 | src_path, "us48.msh"), geom) 38 | 39 | xmin = np.min( 40 | geom.point["coord"][:, 0]) 41 | ymin = np.min( 42 | geom.point["coord"][:, 1]) 43 | xmax = np.max( 44 | geom.point["coord"][:, 0]) 45 | ymax = np.max( 46 | geom.point["coord"][:, 1]) 47 | 48 | geom.point["coord"][:, :] *= np.pi / 180. 49 | 50 | proj.prjID = 'stereographic' 51 | proj.radii = +6.371E+003 52 | proj.xbase = \ 53 | +0.500 * (xmin + xmax) * np.pi / 180. 54 | proj.ybase = \ 55 | +0.500 * (ymin + ymax) * np.pi / 180. 56 | 57 | jigsawpy.project(geom, proj, "fwd") 58 | 59 | jigsawpy.savemsh(opts.geom_file, geom) 60 | 61 | #------------------------------------ make mesh using JIGSAW 62 | 63 | opts.hfun_hmax = .005 64 | 65 | opts.mesh_dims = +2 # 2-dim. simplexes 66 | opts.mesh_eps1 = +1 / 6. 67 | 68 | ttic = time.time() 69 | 70 | jigsawpy.cmd.jigsaw(opts, mesh) 71 | 72 | ttoc = time.time() 73 | 74 | print("CPUSEC =", (ttoc - ttic)) 75 | 76 | cost = jigsawpy.triscr2( # quality metrics! 77 | mesh.point["coord"], 78 | mesh.tria3["index"]) 79 | 80 | print("TRISCR =", np.min(cost), np.mean(cost)) 81 | 82 | cost = jigsawpy.pwrscr2( 83 | mesh.point["coord"], 84 | mesh.power, 85 | mesh.tria3["index"]) 86 | 87 | print("PWRSCR =", np.min(cost), np.mean(cost)) 88 | 89 | tbad = jigsawpy.centre2( 90 | mesh.point["coord"], 91 | mesh.power, 92 | mesh.tria3["index"]) 93 | 94 | print("OBTUSE =", 95 | +np.count_nonzero(np.logical_not(tbad))) 96 | 97 | #------------------------------------ save mesh for Paraview 98 | 99 | print("Saving to ../cache/case_7a.vtk") 100 | 101 | jigsawpy.savevtk(os.path.join( 102 | dst_path, "case_7a.vtk"), mesh) 103 | 104 | return 105 | --------------------------------------------------------------------------------