From e3a13ff38f3361c56226cec85c5e04264b3a1df0 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Wed, 17 May 2023 21:13:21 -0700 Subject: [PATCH 01/59] add `convert_to_mpfr` in helper --- test/test_helpers.py | 29 +++++++++++++++++++++++++++++ uxarray/grid.py | 2 ++ uxarray/helpers.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/test/test_helpers.py b/test/test_helpers.py index fd6a748cd..8bd19ca6d 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -178,3 +178,32 @@ def test_replace_fill_values_invalid(self): original_fill=-1, new_fill=INT_FILL_VALUE, new_dtype=np.int16) + +class TestMultiPrecision(TestCase): + def test_convert_to_mpfr(self): + """Tests if the convert_to_mpfr() helper function converts a numpy + array to a numpy array of the correct dtype.""" + # test different datatypes for face_nodes + f0_deg = np.array([np.array([120, -20]), np.array([130, -10]), np.array([120, 0]), + np.array([105, 0]), np.array([95, -10]), np.array([105, -20])]) + f1_deg = np.array([np.array([120, 0]), np.array([120, 10]), np.array([115, 0]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) + f2_deg = np.array([np.array([115, 0]), np.array([120, 10]), np.array([100, 10]), + np.array([105, 0]), np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) + f3_deg = np.array([np.array([95, -10]), np.array([105, 0]), np.array([95, 30]), + np.array([80, 30]), np.array([70, 0]), np.array([75, -10])]) + f4_deg = np.array([np.array([65, -20]), np.array([75, -10]), np.array([70, 0]), + np.array([55, 0]), np.array([45, -10]), np.array([55, -20])]) + f5_deg = np.array([np.array([70, 0]), np.array([80, 30]), np.array([70, 30]), + np.array([60, 0]), np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) + f6_deg = np.array([np.array([60, 0]), np.array([70, 30]), np.array([40, 30]), + np.array([45, 0]), np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) + + verts = np.array([f0_deg, f1_deg, f2_deg, f3_deg, f4_deg, f5_deg, f6_deg]) + + verts_mpfr = ux.helpers.convert_to_mpfr(verts, str_mode=False,precision=64) \ No newline at end of file diff --git a/uxarray/grid.py b/uxarray/grid.py index f23b5179b..817c02b5b 100644 --- a/uxarray/grid.py +++ b/uxarray/grid.py @@ -2,6 +2,7 @@ import os import xarray as xr import numpy as np +from gmpy2 import mpfr # reader and writer imports from ._exodus import _read_exodus, _encode_exodus @@ -199,6 +200,7 @@ def __from_vert__(self, dataset): z_coord = x_coord * 0.0 # Identify unique vertices and their indices + # TODO: Utilize GMPY2 for precise unique vertex identification unique_verts, indices = np.unique(dataset.reshape( -1, dataset.shape[-1]), axis=0, diff --git a/uxarray/helpers.py b/uxarray/helpers.py index 2453c80ee..029c53cab 100644 --- a/uxarray/helpers.py +++ b/uxarray/helpers.py @@ -1,8 +1,10 @@ import numpy as np import xarray as xr +import gmpy2 from pathlib import PurePath from .get_quadratureDG import get_gauss_quadratureDG, get_tri_quadratureDG from numba import njit, config +from gmpy2 import mpfr import math from uxarray.constants import INT_DTYPE, INT_FILL_VALUE @@ -673,3 +675,44 @@ def close_face_nodes(Mesh2_face_nodes, nMesh2_face, nMaxMesh2_face_nodes): np.put(closed.ravel(), first_fv_idx_1d, first_node_value) return closed + +def convert_to_mpfr(input_array, str_mode = True, precision = 53): + """ + Convert a numpy array to a list of mpfr numbers. + The default precision of an mpfr is 53 bits - the same precision as Python’s `float` type. + https://gmpy2.readthedocs.io/en/latest/mpfr.html + + Parameters + ---------- + input_array : numpy array, float/string + The input array to be converted to mpfr + str_mode : bool, optional + If True, the input array should be string when passing into the function. + If False, the input array should be float when passing into the function. + str_mode is True by default and is recommended. Because to take advantage of the higher precision provided by + the mpfr type, always pass constants as strings. + precision : int, optional + The precision of the mpfr numbers. The default precision of an mpfr is 53 bits - the same precision as Python’s `float` type. + + Returns + ---------- + mpfr_array : numpy array, mpfr type + The output array with mpfr type, which supports correct + rounding, selectable rounding modes, and many trigonometric, exponential, and special functions. A context + manager is used to control precision, rounding modes, and the behavior of exceptions. + """ + gmpy2.set_context(gmpy2.context()) + gmpy2.get_context().precision = precision + + # To take advantage of the higher precision provided by the mpfr type, always pass constants as strings. + # https://gmpy2.readthedocs.io/en/latest/mpfr.html + if not str_mode: + # Cast the input 2D array to string array + input_array = input_array.astype(str) + else: + if input_array.dtype != 'str': + raise ValueError('The input array should be string when str_mode is True.') + + # Then convert the input array to mpfr array + mpfr_array = np.array([gmpy2.mpfr(x) for x in input_array.ravel()]).reshape(input_array.shape) + return mpfr_array From fb485a61b60bf8f28dfd8500a22b9df46170202b Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Thu, 18 May 2023 16:42:15 -0700 Subject: [PATCH 02/59] Unique Coordinates precision detection up to the specified precision --- test/test_helpers.py | 28 ----- test/test_multi_precision_helpers.py | 157 +++++++++++++++++++++++++++ uxarray/constants.py | 1 + uxarray/helpers.py | 42 +------ uxarray/multi_precision_helpers.py | 99 +++++++++++++++++ 5 files changed, 260 insertions(+), 67 deletions(-) create mode 100644 test/test_multi_precision_helpers.py create mode 100644 uxarray/multi_precision_helpers.py diff --git a/test/test_helpers.py b/test/test_helpers.py index 8bd19ca6d..b34d57f28 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -179,31 +179,3 @@ def test_replace_fill_values_invalid(self): new_fill=INT_FILL_VALUE, new_dtype=np.int16) -class TestMultiPrecision(TestCase): - def test_convert_to_mpfr(self): - """Tests if the convert_to_mpfr() helper function converts a numpy - array to a numpy array of the correct dtype.""" - # test different datatypes for face_nodes - f0_deg = np.array([np.array([120, -20]), np.array([130, -10]), np.array([120, 0]), - np.array([105, 0]), np.array([95, -10]), np.array([105, -20])]) - f1_deg = np.array([np.array([120, 0]), np.array([120, 10]), np.array([115, 0]), - np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), - np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), - np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) - f2_deg = np.array([np.array([115, 0]), np.array([120, 10]), np.array([100, 10]), - np.array([105, 0]), np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), - np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) - f3_deg = np.array([np.array([95, -10]), np.array([105, 0]), np.array([95, 30]), - np.array([80, 30]), np.array([70, 0]), np.array([75, -10])]) - f4_deg = np.array([np.array([65, -20]), np.array([75, -10]), np.array([70, 0]), - np.array([55, 0]), np.array([45, -10]), np.array([55, -20])]) - f5_deg = np.array([np.array([70, 0]), np.array([80, 30]), np.array([70, 30]), - np.array([60, 0]), np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), - np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) - f6_deg = np.array([np.array([60, 0]), np.array([70, 30]), np.array([40, 30]), - np.array([45, 0]), np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), - np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) - - verts = np.array([f0_deg, f1_deg, f2_deg, f3_deg, f4_deg, f5_deg, f6_deg]) - - verts_mpfr = ux.helpers.convert_to_mpfr(verts, str_mode=False,precision=64) \ No newline at end of file diff --git a/test/test_multi_precision_helpers.py b/test/test_multi_precision_helpers.py new file mode 100644 index 000000000..03cd6b6e3 --- /dev/null +++ b/test/test_multi_precision_helpers.py @@ -0,0 +1,157 @@ + + +import os +import numpy as np +import numpy.testing as nt +import random +import xarray as xr +import gmpy2 +from gmpy2 import mpfr + +from unittest import TestCase +from pathlib import Path + +import uxarray as ux + +from uxarray.helpers import _replace_fill_values +from uxarray.constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS +from uxarray.multi_precision_helpers import convert_to_mpfr, unique_coordinates_mpfr + +try: + import constants +except ImportError: + from . import constants + +# Data files +current_path = Path(os.path.dirname(os.path.realpath(__file__))) + +exodus = current_path / "meshfiles" / "exodus" / "outCSne8" / "outCSne8.g" +ne8 = current_path / 'meshfiles' / "scrip" / "outCSne8" / 'outCSne8.nc' +err_tolerance = 1.0e-12 + + +class TestMultiPrecision(TestCase): + def test_convert_to_mpfr(self): + """Tests if the convert_to_mpfr() helper function converts a numpy + array to a numpy array of the correct dtype.""" + # test different datatypes for face_nodes + test_precision = 64 + f0_deg = np.array([np.array([120, -20]), np.array([130, -10]), np.array([120, 0]), + np.array([105, 0]), np.array([95, -10]), np.array([105, -20])]) + f1_deg = np.array([np.array([120, 0]), np.array([120, 10]), np.array([115, 0]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) + f2_deg = np.array([np.array([115, 0]), np.array([120, 10]), np.array([100, 10]), + np.array([105, 0]), np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) + f3_deg = np.array([np.array([95, -10]), np.array([105, 0]), np.array([95, 30]), + np.array([80, 30]), np.array([70, 0]), np.array([75, -10])]) + f4_deg = np.array([np.array([65, -20]), np.array([75, -10]), np.array([70, 0]), + np.array([55, 0]), np.array([45, -10]), np.array([55, -20])]) + f5_deg = np.array([np.array([70, 0]), np.array([80, 30]), np.array([70, 30]), + np.array([60, 0]), np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) + f6_deg = np.array([np.array([60, 0]), np.array([70, 30]), np.array([40, 30]), + np.array([45, 0]), np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) + + verts = np.array([f0_deg, f1_deg, f2_deg, f3_deg, f4_deg, f5_deg, f6_deg]) + + verts_mpfr = convert_to_mpfr(verts, str_mode=False,precision=test_precision) + + # Test if every object in the verts_mpfr array is of type mpfr + for i in range(verts_mpfr.shape[0]): + for j in range(verts_mpfr.shape[1]): + self.assertEqual(verts_mpfr[i, j][0].precision, test_precision) + self.assertEqual(verts_mpfr[i, j][1].precision, test_precision) + + # Then compare the values between verts and verts_mpfr up to the 53 bits of precision + self.assertAlmostEqual(verts[i, j][0], verts_mpfr[i, j][0], places=FLOAT_PRECISION_BITS) + self.assertAlmostEqual(verts[i, j][1], verts_mpfr[i, j][1], places=FLOAT_PRECISION_BITS) + + def test_mpfr_unique_normal_case(self): + """ + The input cartesian coordinates represents 8 vertices on a cube + 7---------6 + /| /| + / | / | + 3---------2 | + | | | | + | 4------|--5 + | / | / + |/ |/ + 0---------1 + """ + + test_precision = 64 + cart_x = [ + 0.577340924821405, 0.577340924821405, 0.577340924821405, + 0.577340924821405, -0.577345166204668, -0.577345166204668, + -0.577345166204668, -0.577345166204668 + ] + cart_y = [ + 0.577343045516932, 0.577343045516932, -0.577343045516932, + -0.577343045516932, 0.577338804118089, 0.577338804118089, + -0.577338804118089, -0.577338804118089 + ] + cart_z = [ + 0.577366836872017, -0.577366836872017, 0.577366836872017, + -0.577366836872017, 0.577366836872017, -0.577366836872017, + 0.577366836872017, -0.577366836872017 + ] + + # The order of the vertexes is irrelevant, the following indexing is just for forming a face matrix + face_vertices = [ + [0, 1, 2, 3], # front face + [1, 5, 6, 2], # right face + [5, 4, 7, 6], # back face + [4, 0, 3, 7], # left face + [3, 2, 6, 7], # top face + [4, 5, 1, 0] # bottom face + ] + + # Pack the cart_x/y/z into the face matrix using the index from face_vertices + faces_coords = [] + for face in face_vertices: + face_coords = [] + for vertex_index in face: + x, y, z = cart_x[vertex_index], cart_y[vertex_index], cart_z[ + vertex_index] + face_coords.append([x, y, z]) + faces_coords.append(face_coords) + + # Now consturct the grid using the faces_coords + verts_cart = np.array(faces_coords) + verts_cart_mpfr = convert_to_mpfr(verts_cart, str_mode=False, precision=test_precision) + verts_cart_mpfr_unique, unique_inverse = unique_coordinates_mpfr(verts_cart_mpfr.reshape( + -1, verts_cart_mpfr.shape[-1]), precision=test_precision) + recovered_verts_cart_mpfr = verts_cart_mpfr_unique[unique_inverse] + + # Compare the recovered verts_cart_mpfr with the original verts_cart_mpfr.reshape(-1, verts_cart_mpfr.shape[-1]) + expected = verts_cart_mpfr.reshape(-1, verts_cart_mpfr.shape[-1]) + for i in range(recovered_verts_cart_mpfr.shape[0]): + for j in range(recovered_verts_cart_mpfr.shape[1]): + self.assertEqual(recovered_verts_cart_mpfr[i, j].precision, test_precision) + # Then compare the values between verts and verts_mpfr up to the 53 bits of precision + self.assertAlmostEqual(expected[i, j], recovered_verts_cart_mpfr[i, j], places=FLOAT_PRECISION_BITS) + + def test_mpfr_unique_extreme_case(self): + coord1 = [gmpy2.mpfr('1.23456789'), gmpy2.mpfr('2.34567890'), gmpy2.mpfr('3.45678901')] + coord2 = [gmpy2.mpfr('1.23456789'), gmpy2.mpfr('2.34567890'), gmpy2.mpfr('3.45678901')] + # Convert the coordinates to string format + coord1_str = ["{0:.17f}".format(coord) for coord in coord1] + coord2_str = ["{0:.17f}".format(coord) for coord in coord2] + + # Create the final coordinates with the differing 64th bit + coord1_final = [coord + '0' for coord in coord1_str] + coord2_final = [coord + '1' for coord in coord2_str] + verts = np.array([coord1_final, coord2_final]) + verts_60 = convert_to_mpfr(verts, str_mode=True, precision=60) + verts_57 = convert_to_mpfr(verts, str_mode=True, precision=57) + + verts_cart_mpfr_unique_64, unique_inverse_64 = unique_coordinates_mpfr(verts_60, precision=60) + verts_cart_mpfr_unique_57, unique_inverse_57 = unique_coordinates_mpfr(verts_57, precision=57) + self.assertTrue(len(verts_cart_mpfr_unique_64) == 2) + self.assertTrue(len(verts_cart_mpfr_unique_57) == 1) + diff --git a/uxarray/constants.py b/uxarray/constants.py index 7a8f322f8..a04a6450a 100644 --- a/uxarray/constants.py +++ b/uxarray/constants.py @@ -3,3 +3,4 @@ # numpy indexing code is written for np.intp INT_DTYPE = np.intp INT_FILL_VALUE = np.iinfo(INT_DTYPE).min +FLOAT_PRECISION_BITS = 53 diff --git a/uxarray/helpers.py b/uxarray/helpers.py index 029c53cab..1c94b082f 100644 --- a/uxarray/helpers.py +++ b/uxarray/helpers.py @@ -1,10 +1,9 @@ import numpy as np import xarray as xr -import gmpy2 + from pathlib import PurePath from .get_quadratureDG import get_gauss_quadratureDG, get_tri_quadratureDG from numba import njit, config -from gmpy2 import mpfr import math from uxarray.constants import INT_DTYPE, INT_FILL_VALUE @@ -676,43 +675,8 @@ def close_face_nodes(Mesh2_face_nodes, nMesh2_face, nMaxMesh2_face_nodes): return closed -def convert_to_mpfr(input_array, str_mode = True, precision = 53): - """ - Convert a numpy array to a list of mpfr numbers. - The default precision of an mpfr is 53 bits - the same precision as Python’s `float` type. - https://gmpy2.readthedocs.io/en/latest/mpfr.html - Parameters - ---------- - input_array : numpy array, float/string - The input array to be converted to mpfr - str_mode : bool, optional - If True, the input array should be string when passing into the function. - If False, the input array should be float when passing into the function. - str_mode is True by default and is recommended. Because to take advantage of the higher precision provided by - the mpfr type, always pass constants as strings. - precision : int, optional - The precision of the mpfr numbers. The default precision of an mpfr is 53 bits - the same precision as Python’s `float` type. - Returns - ---------- - mpfr_array : numpy array, mpfr type - The output array with mpfr type, which supports correct - rounding, selectable rounding modes, and many trigonometric, exponential, and special functions. A context - manager is used to control precision, rounding modes, and the behavior of exceptions. - """ - gmpy2.set_context(gmpy2.context()) - gmpy2.get_context().precision = precision - - # To take advantage of the higher precision provided by the mpfr type, always pass constants as strings. - # https://gmpy2.readthedocs.io/en/latest/mpfr.html - if not str_mode: - # Cast the input 2D array to string array - input_array = input_array.astype(str) - else: - if input_array.dtype != 'str': - raise ValueError('The input array should be string when str_mode is True.') - # Then convert the input array to mpfr array - mpfr_array = np.array([gmpy2.mpfr(x) for x in input_array.ravel()]).reshape(input_array.shape) - return mpfr_array + + diff --git a/uxarray/multi_precision_helpers.py b/uxarray/multi_precision_helpers.py new file mode 100644 index 000000000..cc639d1a7 --- /dev/null +++ b/uxarray/multi_precision_helpers.py @@ -0,0 +1,99 @@ +import gmpy2 +from gmpy2 import mpfr +import numpy as np +from .constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS + + +def convert_to_mpfr(input_array, str_mode=True, precision=FLOAT_PRECISION_BITS): + """ + Convert a numpy array to a list of mpfr numbers. + The default precision of an mpfr is 53 bits - the same precision as Python’s `float` type. + https://gmpy2.readthedocs.io/en/latest/mpfr.html + + Parameters + ---------- + input_array : numpy array, float/string, shape is arbitrary + The input array to be converted to mpfr. The input array should be float or string. If the input array is float, + str_mode should be False. If the input array is string, str_mode should be True. + + str_mode : bool, optional + If True, the input array should be string when passing into the function. + If False, the input array should be float when passing into the function. + str_mode is True by default and is recommended. Because to take advantage of the higher precision provided by + the mpfr type, always pass constants as strings. + precision : int, optional + The precision of the mpfr numbers. The default precision of an mpfr is 53 bits - the same precision as Python’s `float` type. + + Returns + ---------- + mpfr_array : numpy array, mpfr type, shape will be same as the input_array + The output array with mpfr type, which supports correct + rounding, selectable rounding modes, and many trigonometric, exponential, and special functions. A context + manager is used to control precision, rounding modes, and the behavior of exceptions. + """ + gmpy2.set_context(gmpy2.context()) + gmpy2.get_context().precision = precision + + # To take advantage of the higher precision provided by the mpfr type, always pass constants as strings. + # https://gmpy2.readthedocs.io/en/latest/mpfr.html + if not str_mode: + # Cast the input 2D array to string array + input_array = input_array.astype(str) + else: + flattened_array = np.ravel(input_array) + if ~np.all([np.issubdtype(type(element), np.str_) for element in flattened_array]): + raise ValueError('The input array should be string when str_mode is True.') + + # Then convert the input array to mpfr array + mpfr_array = np.array([gmpy2.mpfr(x,precision) for x in input_array.ravel()]).reshape(input_array.shape) + return mpfr_array + + +def unique_coordinates_mpfr(input_array_mpfr, precision=FLOAT_PRECISION_BITS): + """ + Find the unique coordinates in the input array with mpfr numbers. + The default precision of an mpfr is 53 bits - the same precision as Python’s `float` type. + + Parameters: + ---------- + + + """ + + # Reset the mpfr precision + gmpy2.get_context().precision = precision + + # Check if the input_array is in th mpfr type + try: + # Flatten the input_array_mpfr to a 1D array so that we can check the type of each element + input_array_mpfr_copy = np.ravel(input_array_mpfr) + for i in range(len(input_array_mpfr_copy)): + if type(input_array_mpfr_copy[i]) != gmpy2.mpfr: + raise ValueError('The input array should be in the mpfr type. You can use convert_to_mpfr() to ' + 'convert the input array to mpfr.') + except Exception as e: + raise e + + unique_arr = [] + inverse_indices = [] + m, n = input_array_mpfr.shape + + unique_dict = {} + current_index = 0 + + for i in range(m): + format_string = "{0:+."+str(precision+1)+"Uf}" + hashable_row = tuple(format_string.format(gmpy2.mpfr(x, precision)) for x in input_array_mpfr[i]) + + if hashable_row not in unique_dict: + unique_dict[hashable_row] = current_index + unique_arr.append(input_array_mpfr[i]) + inverse_indices.append(current_index) + current_index += 1 + else: + inverse_indices.append(unique_dict[hashable_row]) + + unique_arr = np.array(unique_arr) + inverse_indices = np.array(inverse_indices) + + return unique_arr, inverse_indices From 049768c0972a7ea288a16e1690e1ea0984be2cd9 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Thu, 18 May 2023 19:40:55 -0700 Subject: [PATCH 03/59] Unique Coordinates precision detection up to the specified precision --- test/test_multi_precision_helpers.py | 43 +++++++++++++++++++----- uxarray/multi_precision_helpers.py | 50 ++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/test/test_multi_precision_helpers.py b/test/test_multi_precision_helpers.py index 03cd6b6e3..0dd918133 100644 --- a/test/test_multi_precision_helpers.py +++ b/test/test_multi_precision_helpers.py @@ -2,9 +2,6 @@ import os import numpy as np -import numpy.testing as nt -import random -import xarray as xr import gmpy2 from gmpy2 import mpfr @@ -13,9 +10,9 @@ import uxarray as ux -from uxarray.helpers import _replace_fill_values from uxarray.constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS -from uxarray.multi_precision_helpers import convert_to_mpfr, unique_coordinates_mpfr +from uxarray.multi_precision_helpers import convert_to_mpfr, unique_coordinates_mpfr, precision_bits_to_decimal_digits, decimal_digits_to_precision_bits +import math try: import constants @@ -147,11 +144,39 @@ def test_mpfr_unique_extreme_case(self): coord1_final = [coord + '0' for coord in coord1_str] coord2_final = [coord + '1' for coord in coord2_str] verts = np.array([coord1_final, coord2_final]) - verts_60 = convert_to_mpfr(verts, str_mode=True, precision=60) - verts_57 = convert_to_mpfr(verts, str_mode=True, precision=57) + bit_precision = decimal_digits_to_precision_bits(18) + verts_60 = convert_to_mpfr(verts, str_mode=True, precision=bit_precision) + verts_57 = convert_to_mpfr(verts, str_mode=True, precision=bit_precision - 1) - verts_cart_mpfr_unique_64, unique_inverse_64 = unique_coordinates_mpfr(verts_60, precision=60) - verts_cart_mpfr_unique_57, unique_inverse_57 = unique_coordinates_mpfr(verts_57, precision=57) + verts_cart_mpfr_unique_64, unique_inverse_64 = unique_coordinates_mpfr(verts_60, precision=bit_precision) + verts_cart_mpfr_unique_57, unique_inverse_57 = unique_coordinates_mpfr(verts_57, precision=bit_precision - 1) self.assertTrue(len(verts_cart_mpfr_unique_64) == 2) self.assertTrue(len(verts_cart_mpfr_unique_57) == 1) + coord1 = [gmpy2.mpfr('1.23456789'), gmpy2.mpfr('2.34567890'), gmpy2.mpfr('3.45678901')] + coord2 = [gmpy2.mpfr('1.23456789'), gmpy2.mpfr('2.34567890'), gmpy2.mpfr('3.45678901')] + # Convert the coordinates to string format + precision_bits = 200 + decimal_digits = precision_bits_to_decimal_digits(precision_bits - 1) + check = decimal_digits_to_precision_bits(decimal_digits) + + format_str = "{0:." + str(decimal_digits) + "f}" + coord1_str = [format_str.format(coord) for coord in coord1] + coord2_str = [format_str.format(coord) for coord in coord2] + + # Create the final coordinates with the differing 64th bit + coord1_final = [coord + '0' for coord in coord1_str] + coord2_final = [coord + '1' for coord in coord2_str] + verts = np.array([coord1_final, coord2_final]) + + verts_200 = convert_to_mpfr(verts, str_mode=True, precision=precision_bits) + verts_199 = convert_to_mpfr(verts, str_mode=True, precision=check - 1) + + verts_cart_mpfr_unique_200, unique_inverse_200 = unique_coordinates_mpfr(verts_200, precision=precision_bits) + verts_cart_mpfr_unique_199, unique_inverse_199 = unique_coordinates_mpfr(verts_199, precision=check - 1) + self.assertTrue(len(verts_cart_mpfr_unique_200) == 2) + self.assertTrue(len(verts_cart_mpfr_unique_199) == 1) + + + + diff --git a/uxarray/multi_precision_helpers.py b/uxarray/multi_precision_helpers.py index cc639d1a7..e2bf11755 100644 --- a/uxarray/multi_precision_helpers.py +++ b/uxarray/multi_precision_helpers.py @@ -1,6 +1,7 @@ import gmpy2 -from gmpy2 import mpfr +from gmpy2 import mpfr, mpz import numpy as np +import math from .constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS @@ -45,7 +46,7 @@ def convert_to_mpfr(input_array, str_mode=True, precision=FLOAT_PRECISION_BITS): raise ValueError('The input array should be string when str_mode is True.') # Then convert the input array to mpfr array - mpfr_array = np.array([gmpy2.mpfr(x,precision) for x in input_array.ravel()]).reshape(input_array.shape) + mpfr_array = np.array([gmpy2.mpfr(x, precision) for x in input_array.ravel()]).reshape(input_array.shape) return mpfr_array @@ -82,7 +83,7 @@ def unique_coordinates_mpfr(input_array_mpfr, precision=FLOAT_PRECISION_BITS): current_index = 0 for i in range(m): - format_string = "{0:+."+str(precision+1)+"Uf}" + format_string = "{0:+." + str(precision + 1) + "Uf}" hashable_row = tuple(format_string.format(gmpy2.mpfr(x, precision)) for x in input_array_mpfr[i]) if hashable_row not in unique_dict: @@ -97,3 +98,46 @@ def unique_coordinates_mpfr(input_array_mpfr, precision=FLOAT_PRECISION_BITS): inverse_indices = np.array(inverse_indices) return unique_arr, inverse_indices + + +def decimal_digits_to_precision_bits(decimal_digits): + """ + Convert the number of decimal digits to the number of bits of precision + + Parameters + ---------- + decimal_digits : int + The number of decimal digits of precision + + Returns + ------- + bits : int + The number of bits of precision + """ + bits = math.ceil(decimal_digits * math.log2(10)) + return bits + + +def precision_bits_to_decimal_digits(precision): + """ + Convert the number of bits of precision to the number of decimal digits + + Parameters + ---------- + precision : int + The number of bits of precision + + Returns + ------- + decimal_digits : int + The number of decimal digits of precision + """ + # Compute the decimal digit count using gmpy2.log10() + log10_2 = gmpy2.log10(gmpy2.mpfr(2)) # Logarithm base 10 of 2 + log10_precision = gmpy2.div(1, log10_2) # Logarithm base 10 of the precision + decimal_digits = gmpy2.div(precision, log10_precision) + + # Convert the result to an integer + decimal_digits = int(math.floor(decimal_digits)) + + return decimal_digits \ No newline at end of file From b21f7e902f7dc6fa6171f89984700cb8437a9284 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Thu, 18 May 2023 23:17:14 -0700 Subject: [PATCH 04/59] Implemented multi precision `__from_vert__()` --- test/test_grid.py | 68 ++++++++++++++++++ uxarray/constants.py | 2 + uxarray/grid.py | 107 +++++++++++++++++++++-------- uxarray/multi_precision_helpers.py | 2 +- 4 files changed, 150 insertions(+), 29 deletions(-) diff --git a/test/test_grid.py b/test/test_grid.py index 92d327852..b9f44778f 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -1,12 +1,15 @@ import os import numpy as np import xarray as xr +import gmpy2 +from gmpy2 import mpfr from unittest import TestCase from pathlib import Path import xarray as xr import uxarray as ux +import uxarray.multi_precision_helpers as mph import numpy.testing as nt try: @@ -143,6 +146,71 @@ def test_init_verts(self): assert (vgrid.nMesh2_node == 6) vgrid.encode_as("ugrid") + def test_init_verts_multi_precision_triangle(self): + nodes = [] + # Generate the nodes on the unit sphere, and the nodes are extremely close to each other: the edge length + # between any two nodes is less than 1e-17 (the precision of the nodes is 60 bits but we will use 65 bits for + # testing) + for i in range(4): + theta = gmpy2.div(i * 2 * gmpy2.const_pi(precision=65), 4) # Angle in radians + x = gmpy2.cos(theta) + y = gmpy2.sin(theta) + z = mpfr(0) # All nodes will have z-coordinate as 0 on the unit sphere + nodes.append([x, y, z]) + + # Generate the face nodes connectivity + face_nodes_connectivity = np.array([ + np.array([nodes[0], nodes[1], nodes[2]], dtype=object), + np.array([nodes[0], nodes[2], nodes[3]], dtype=object), + np.array([nodes[0], nodes[3], nodes[1]], dtype=object), + np.array([nodes[1], nodes[2], nodes[3]], dtype=object) + ]) + + # Create the grid + vgrid = ux.Grid(face_nodes_connectivity, multi_precision=True, precision= 65, + vertices=True, + islatlon=False, + concave=False) + assert (vgrid.source_grid == "From vertices") + assert (vgrid.nMesh2_face == 4) + assert (vgrid.nMesh2_node == 4) + + def test_init_verts_multi_precision_fill_values(self): + # Set the desired precision + gmpy2.get_context().precision = 100 + + # Generate the nodes on the unit sphere, and the nodes are extremely close to each other: the edge length + # between any two nodes is less than 1e-17 (the precision of the nodes is 60 bits but we will use 65 bits for + # testing) + + nodes = [] + for i in range(4): + theta = gmpy2.div(i * 2 * gmpy2.const_pi(precision=65), 4) # Angle in radians + x = gmpy2.cos(theta) + y = gmpy2.sin(theta) + z = mpfr('0') # All nodes will have z-coordinate as 0 on the unit sphere + nodes.append([x, y, z]) + + # Generate the face nodes connectivity + dumb_nodes = [ux.INT_FILL_VALUE_MPZ, ux.INT_FILL_VALUE_MPZ, ux.INT_FILL_VALUE_MPZ] + face_nodes_connectivity = np.array([ + np.array([nodes[0], nodes[1], nodes[2], dumb_nodes], dtype=object), + np.array([nodes[0], nodes[2], nodes[3], dumb_nodes], dtype=object), + np.array([nodes[0], nodes[3], nodes[1], dumb_nodes], dtype=object), + np.array([nodes[1], nodes[2], nodes[3], nodes[0]], dtype=object) + ], dtype=object) + # Create the grid + vgrid = ux.Grid(face_nodes_connectivity, multi_precision=True, precision= 65, + vertices=True, + islatlon=False, + concave=False) + assert (vgrid.source_grid == "From vertices") + assert (vgrid.nMesh2_face == 4) + assert (vgrid.nMesh2_node == 4) + + # Test the all numbers in the vgird.ds["Mesh2_face_nodes"] are less than 4 + assert (np.all(vgrid.ds["Mesh2_face_nodes"] < 4)) + def test_init_verts_different_input_datatype(self): """Create a uxarray grid from multiple face vertices with different datatypes(ndarray, list, tuple) and saves a ugrid file. diff --git a/uxarray/constants.py b/uxarray/constants.py index a04a6450a..1c5cdadd2 100644 --- a/uxarray/constants.py +++ b/uxarray/constants.py @@ -1,6 +1,8 @@ import numpy as np +from gmpy2 import mpz # numpy indexing code is written for np.intp INT_DTYPE = np.intp INT_FILL_VALUE = np.iinfo(INT_DTYPE).min +INT_FILL_VALUE_MPZ = mpz(str(INT_FILL_VALUE)) FLOAT_PRECISION_BITS = 53 diff --git a/uxarray/grid.py b/uxarray/grid.py index 817c02b5b..2a968061d 100644 --- a/uxarray/grid.py +++ b/uxarray/grid.py @@ -2,7 +2,8 @@ import os import xarray as xr import numpy as np -from gmpy2 import mpfr +import gmpy2 +from gmpy2 import mpfr, mpz # reader and writer imports from ._exodus import _read_exodus, _encode_exodus @@ -11,7 +12,8 @@ from ._scrip import _read_scrip, _encode_scrip from ._mpas import _read_mpas from .helpers import get_all_face_area_from_coords, parse_grid_type, node_xyz_to_lonlat_rad, node_lonlat_rad_to_xyz, close_face_nodes -from .constants import INT_DTYPE, INT_FILL_VALUE +from .constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ +from .multi_precision_helpers import convert_to_mpfr, unique_coordinates_mpfr, precision_bits_to_decimal_digits, decimal_digits_to_precision_bits class Grid: @@ -29,7 +31,7 @@ class Grid: >>> mesh.encode_as("ugrid") """ - def __init__(self, dataset, **kwargs): + def __init__(self, dataset, multi_precision=False, precision = FLOAT_PRECISION_BITS, **kwargs): """Initialize grid variables, decide if loading happens via file, verts or gridspec. @@ -77,12 +79,12 @@ def __init__(self, dataset, **kwargs): dataset = np.asarray(dataset) # grid with multiple faces if dataset.ndim == 3: - self.__from_vert__(dataset) + self.__from_vert__(dataset, multi_precision=multi_precision, precision=precision) self.source_grid = "From vertices" # grid with a single face elif dataset.ndim == 2: dataset = np.array([dataset]) - self.__from_vert__(dataset) + self.__from_vert__(dataset, multi_precision=multi_precision, precision=precision) self.source_grid = "From vertices" else: raise RuntimeError( @@ -160,7 +162,7 @@ def __init_grid_var_attrs__(self) -> None: if value in self.ds.dims: setattr(self, key, len(self.ds[value])) - def __from_vert__(self, dataset): + def __from_vert__(self, dataset, multi_precision=False, precision= FLOAT_PRECISION_BITS): """Create a grid with faces constructed from vertices specified by the given argument. @@ -200,20 +202,51 @@ def __from_vert__(self, dataset): z_coord = x_coord * 0.0 # Identify unique vertices and their indices - # TODO: Utilize GMPY2 for precise unique vertex identification - unique_verts, indices = np.unique(dataset.reshape( - -1, dataset.shape[-1]), - axis=0, - return_inverse=True) + if multi_precision: + # Check if the input_array is in th mpfr type + try: + # Flatten the input_array_mpfr to a 1D array so that we can check the type of each element + input_array_mpfr_copy = np.ravel(dataset) + for i in range(len(input_array_mpfr_copy)): + if type(input_array_mpfr_copy[i]) != gmpy2.mpfr and type(input_array_mpfr_copy[i]) != gmpy2.mpz: + dataset= convert_to_mpfr(dataset, precision=precision) + except Exception as e: + raise e + + unique_verts, indices = unique_coordinates_mpfr(dataset.reshape( + -1, dataset.shape[-1]), precision=precision) + + else: + unique_verts, indices = np.unique(dataset.reshape( + -1, dataset.shape[-1]), + axis=0, + return_inverse=True) # Nodes index that contain a fill value - fill_value_mask = np.logical_or(unique_verts[:, 0] == INT_FILL_VALUE, - unique_verts[:, 1] == INT_FILL_VALUE) - if dataset[0][0].size > 2: + if multi_precision: + # Perform element-wise comparison using gmpy.cmp() fill_value_mask = np.logical_or( - unique_verts[:, 0] == INT_FILL_VALUE, - unique_verts[:, 1] == INT_FILL_VALUE, - unique_verts[:, 2] == INT_FILL_VALUE) + np.array([gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 for x in unique_verts[:, 0]], dtype=bool), + np.array([gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 for x in unique_verts[:, 1]], dtype=bool) + ) + + if dataset[0][0].size > 2: + fill_value_mask = np.logical_or( + np.array([gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 for x in + unique_verts[:, 0]], dtype=bool), + np.array([gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 for x in + unique_verts[:, 1]], dtype=bool), + np.array([gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 for x in + unique_verts[:, 2]], dtype=bool)) + + else: + fill_value_mask = np.logical_or(unique_verts[:, 0] == INT_FILL_VALUE, + unique_verts[:, 1] == INT_FILL_VALUE) + if dataset[0][0].size > 2: + fill_value_mask = np.logical_or( + unique_verts[:, 0] == INT_FILL_VALUE, + unique_verts[:, 1] == INT_FILL_VALUE, + unique_verts[:, 2] == INT_FILL_VALUE) # Get the indices of all the False values in fill_value_mask false_indices = np.where(fill_value_mask == True)[0] @@ -226,9 +259,17 @@ def __from_vert__(self, dataset): unique_verts = np.delete(unique_verts, false_indices, axis=0) # Update indices accordingly - for i, idx in enumerate(false_indices): - indices[indices == idx] = INT_FILL_VALUE - indices[(indices > idx) & (indices != INT_FILL_VALUE)] -= 1 + if multi_precision: + for i, idx in enumerate(false_indices): + indices[indices == idx] = INT_FILL_VALUE_MPZ + for j in range(indices.size): + if gmpy2.cmp(mpz(indices[j]), mpz(idx)) > 0 and gmpy2.cmp(mpz(indices[j]), INT_FILL_VALUE_MPZ) != 0: + indices[j] -= 1 + + else: + for i, idx in enumerate(false_indices): + indices[indices == idx] = INT_FILL_VALUE + indices[(indices > idx) & (indices != INT_FILL_VALUE)] -= 1 # Create coordinate DataArrays self.ds["Mesh2_node_x"] = xr.DataArray(data=unique_verts[:, 0], @@ -249,14 +290,24 @@ def __from_vert__(self, dataset): # Create connectivity array using indices of unique vertices connectivity = indices.reshape(dataset.shape[:-1]) - self.ds["Mesh2_face_nodes"] = xr.DataArray( - data=xr.DataArray(connectivity).astype(INT_DTYPE), - dims=["nMesh2_face", "nMaxMesh2_face_nodes"], - attrs={ - "cf_role": "face_node_connectivity", - "_FillValue": INT_FILL_VALUE, - "start_index": 0 - }) + if multi_precision: + self.ds["Mesh2_face_nodes"] = xr.DataArray( + data=xr.DataArray(connectivity).astype(INT_DTYPE), + dims=["nMesh2_face", "nMaxMesh2_face_nodes"], + attrs={ + "cf_role": "face_node_connectivity", + "_FillValue": INT_FILL_VALUE_MPZ, + "start_index": 0 + }) + else: + self.ds["Mesh2_face_nodes"] = xr.DataArray( + data=xr.DataArray(connectivity).astype(INT_DTYPE), + dims=["nMesh2_face", "nMaxMesh2_face_nodes"], + attrs={ + "cf_role": "face_node_connectivity", + "_FillValue": INT_FILL_VALUE, + "start_index": 0 + }) # load mesh from a file def __from_ds__(self, dataset): diff --git a/uxarray/multi_precision_helpers.py b/uxarray/multi_precision_helpers.py index e2bf11755..201a3038b 100644 --- a/uxarray/multi_precision_helpers.py +++ b/uxarray/multi_precision_helpers.py @@ -69,7 +69,7 @@ def unique_coordinates_mpfr(input_array_mpfr, precision=FLOAT_PRECISION_BITS): # Flatten the input_array_mpfr to a 1D array so that we can check the type of each element input_array_mpfr_copy = np.ravel(input_array_mpfr) for i in range(len(input_array_mpfr_copy)): - if type(input_array_mpfr_copy[i]) != gmpy2.mpfr: + if type(input_array_mpfr_copy[i]) != gmpy2.mpfr and type(input_array_mpfr_copy[i]) != gmpy2.mpz: raise ValueError('The input array should be in the mpfr type. You can use convert_to_mpfr() to ' 'convert the input array to mpfr.') except Exception as e: From 27a95a1e4d441a9c913b6d0092b12be00f2ad0bb Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Fri, 19 May 2023 10:33:55 -0700 Subject: [PATCH 05/59] Update multi_precision_helpers.py --- test/test_multi_precision_helpers.py | 18 ++++++++++-------- uxarray/multi_precision_helpers.py | 26 +++++++++++++++++++++----- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/test/test_multi_precision_helpers.py b/test/test_multi_precision_helpers.py index 0dd918133..3369e175a 100644 --- a/test/test_multi_precision_helpers.py +++ b/test/test_multi_precision_helpers.py @@ -3,14 +3,14 @@ import os import numpy as np import gmpy2 -from gmpy2 import mpfr +from gmpy2 import mpfr, mpz from unittest import TestCase from pathlib import Path import uxarray as ux -from uxarray.constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS +from uxarray.constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ from uxarray.multi_precision_helpers import convert_to_mpfr, unique_coordinates_mpfr, precision_bits_to_decimal_digits, decimal_digits_to_precision_bits import math @@ -60,12 +60,14 @@ def test_convert_to_mpfr(self): # Test if every object in the verts_mpfr array is of type mpfr for i in range(verts_mpfr.shape[0]): for j in range(verts_mpfr.shape[1]): - self.assertEqual(verts_mpfr[i, j][0].precision, test_precision) - self.assertEqual(verts_mpfr[i, j][1].precision, test_precision) - - # Then compare the values between verts and verts_mpfr up to the 53 bits of precision - self.assertAlmostEqual(verts[i, j][0], verts_mpfr[i, j][0], places=FLOAT_PRECISION_BITS) - self.assertAlmostEqual(verts[i, j][1], verts_mpfr[i, j][1], places=FLOAT_PRECISION_BITS) + for idx, val in enumerate(verts_mpfr[i, j]): + if type(val) != mpz: + self.assertEqual(val.precision, test_precision) + # Then compare the values between verts and verts_mpfr up to the 53 bits of precision + self.assertAlmostEqual(verts[i, j][idx], val, places=FLOAT_PRECISION_BITS) + + else: + self.assertTrue(gmpy2.cmp(val, INT_FILL_VALUE_MPZ) == 0) def test_mpfr_unique_normal_case(self): """ diff --git a/uxarray/multi_precision_helpers.py b/uxarray/multi_precision_helpers.py index 201a3038b..1d5fd9e31 100644 --- a/uxarray/multi_precision_helpers.py +++ b/uxarray/multi_precision_helpers.py @@ -2,7 +2,7 @@ from gmpy2 import mpfr, mpz import numpy as np import math -from .constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS +from .constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ def convert_to_mpfr(input_array, str_mode=True, precision=FLOAT_PRECISION_BITS): @@ -37,16 +37,32 @@ def convert_to_mpfr(input_array, str_mode=True, precision=FLOAT_PRECISION_BITS): # To take advantage of the higher precision provided by the mpfr type, always pass constants as strings. # https://gmpy2.readthedocs.io/en/latest/mpfr.html + flattened_array = np.ravel(input_array) + mpfr_array = np.array(flattened_array, dtype=object) if not str_mode: # Cast the input 2D array to string array - input_array = input_array.astype(str) + for idx,val in enumerate(flattened_array): + if gmpy2.cmp(mpz(val), INT_FILL_VALUE_MPZ) == 0: + mpfr_array[idx] = INT_FILL_VALUE_MPZ + else: + decimal_digit = precision_bits_to_decimal_digits(precision) + format_str = "{0:+." + str(decimal_digit) + "f}" + val_str = format_str.format(val) + mpfr_array[idx] = mpfr(val_str, precision) + else: - flattened_array = np.ravel(input_array) + if ~np.all([np.issubdtype(type(element), np.str_) for element in flattened_array]): raise ValueError('The input array should be string when str_mode is True.') + # Then convert the input array to mpfr array + for idx, val in enumerate(flattened_array): + if val == "INT_FILL_VALUE": + mpfr_array[idx] = INT_FILL_VALUE_MPZ + else: + mpfr_array[idx] = mpfr(val, precision) + + mpfr_array = mpfr_array.reshape(input_array.shape) - # Then convert the input array to mpfr array - mpfr_array = np.array([gmpy2.mpfr(x, precision) for x in input_array.ravel()]).reshape(input_array.shape) return mpfr_array From 165d1ebb73d6bd14d01d379076191ff088638c53 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Fri, 19 May 2023 10:38:00 -0700 Subject: [PATCH 06/59] Remove the global precision setting --- uxarray/multi_precision_helpers.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/uxarray/multi_precision_helpers.py b/uxarray/multi_precision_helpers.py index 1d5fd9e31..eba6f52d6 100644 --- a/uxarray/multi_precision_helpers.py +++ b/uxarray/multi_precision_helpers.py @@ -32,8 +32,6 @@ def convert_to_mpfr(input_array, str_mode=True, precision=FLOAT_PRECISION_BITS): rounding, selectable rounding modes, and many trigonometric, exponential, and special functions. A context manager is used to control precision, rounding modes, and the behavior of exceptions. """ - gmpy2.set_context(gmpy2.context()) - gmpy2.get_context().precision = precision # To take advantage of the higher precision provided by the mpfr type, always pass constants as strings. # https://gmpy2.readthedocs.io/en/latest/mpfr.html @@ -77,9 +75,6 @@ def unique_coordinates_mpfr(input_array_mpfr, precision=FLOAT_PRECISION_BITS): """ - # Reset the mpfr precision - gmpy2.get_context().precision = precision - # Check if the input_array is in th mpfr type try: # Flatten the input_array_mpfr to a 1D array so that we can check the type of each element From 0ae5242246acc6195368a37dab252d399840e2e0 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Fri, 19 May 2023 14:41:50 -0700 Subject: [PATCH 07/59] `__from_vert__()` supports the multi-precision read-in now --- uxarray/grid.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/uxarray/grid.py b/uxarray/grid.py index 2a968061d..aee57d638 100644 --- a/uxarray/grid.py +++ b/uxarray/grid.py @@ -39,7 +39,16 @@ def __init__(self, dataset, multi_precision=False, precision = FLOAT_PRECISION_B ---------- dataset : xarray.Dataset, ndarray, list, tuple, required Input xarray.Dataset or vertex coordinates that form one face. - + multi_precision : bool, optional + Specify if want to use multi-precision (mpfr) for all grid operations (default: False). + Each node coordinates will be stored as the gmpy2.mpfr/mpz(filled value) type + instead of the python ``float``/``int`` type. + Note: when using multi-precision, all grid operations are done in multi-precision using the GMPY2 library. And + the run time will significantly increase. To gain the full benefit of multi-precision, it is recommended to input + the grid coordinates in string format (eg. '0.1', '0.2', etc) and use string 'INT_FILL_VALUE' + to specify the fill value. + precision : int, optional + Specify the precision of the grid coordinates (default: FLOAT_PRECISION_BITS (python `float` bit precision)). Other Parameters ---------------- islatlon : bool, optional @@ -95,6 +104,7 @@ def __init__(self, dataset, multi_precision=False, precision = FLOAT_PRECISION_B # TODO: re-add gridspec initialization when implemented elif isinstance(dataset, xr.Dataset): self.mesh_type = parse_grid_type(dataset) + #TODO: Support multiprecision for __from_ds__ self.__from_ds__(dataset=dataset) else: raise RuntimeError("Dataset is not a valid input type.") From 963588d6b1020d7f217bc6ebf09d67c0745ae632 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Sat, 20 May 2023 12:20:16 -0700 Subject: [PATCH 08/59] Finished the docstring writing --- ci/docs.yml | 2 + ci/environment.yml | 2 + ci/upstream-dev-environment.yml | 2 + docs/user_api/index.rst | 10 ++ test/test_grid.py | 28 ++-- test/test_helpers.py | 1 - test/test_multi_precision_helpers.py | 231 ++++++++++++++++++++------- uxarray/grid.py | 74 ++++++--- uxarray/helpers.py | 8 - uxarray/multi_precision_helpers.py | 87 +++++++--- 10 files changed, 323 insertions(+), 122 deletions(-) diff --git a/ci/docs.yml b/ci/docs.yml index c3523f2b2..2e33e05dc 100644 --- a/ci/docs.yml +++ b/ci/docs.yml @@ -22,3 +22,5 @@ dependencies: - myst-nb - sphinx-design - nbsphinx + - gmpy2 + - mpmath diff --git a/ci/environment.yml b/ci/environment.yml index 1d53cd2bc..c3df18390 100644 --- a/ci/environment.yml +++ b/ci/environment.yml @@ -12,3 +12,5 @@ dependencies: - pytest-cov - scipy - xarray + - gmpy2 + - mpmath diff --git a/ci/upstream-dev-environment.yml b/ci/upstream-dev-environment.yml index e03435753..81db543d1 100644 --- a/ci/upstream-dev-environment.yml +++ b/ci/upstream-dev-environment.yml @@ -10,3 +10,5 @@ dependencies: - pytest - pytest-cov - scipy + - gmpy2 + - mpmath diff --git a/docs/user_api/index.rst b/docs/user_api/index.rst index 1c0a1970a..19a980ec3 100644 --- a/docs/user_api/index.rst +++ b/docs/user_api/index.rst @@ -43,3 +43,13 @@ Helper Functions helpers.node_lonlat_rad_to_xyz helpers.normalize_in_place helpers.close_face_nodes + +Multi-Precision Helper Functions +------------------------------- +.. autosummary:: + :toctree: _autosummary + + multi_precision_helpers.convert_to_multiprecision + multi_precision_helpers.unique_coordinates_multiprecision + multi_precision_helpers.decimal_digits_to_precision_bits + multi_precision_helpers.precision_bits_to_decimal_digits diff --git a/test/test_grid.py b/test/test_grid.py index b9f44778f..485b42246 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -152,10 +152,12 @@ def test_init_verts_multi_precision_triangle(self): # between any two nodes is less than 1e-17 (the precision of the nodes is 60 bits but we will use 65 bits for # testing) for i in range(4): - theta = gmpy2.div(i * 2 * gmpy2.const_pi(precision=65), 4) # Angle in radians + theta = gmpy2.div(i * 2 * gmpy2.const_pi(precision=65), + 4) # Angle in radians x = gmpy2.cos(theta) y = gmpy2.sin(theta) - z = mpfr(0) # All nodes will have z-coordinate as 0 on the unit sphere + z = mpfr( + 0) # All nodes will have z-coordinate as 0 on the unit sphere nodes.append([x, y, z]) # Generate the face nodes connectivity @@ -167,7 +169,9 @@ def test_init_verts_multi_precision_triangle(self): ]) # Create the grid - vgrid = ux.Grid(face_nodes_connectivity, multi_precision=True, precision= 65, + vgrid = ux.Grid(face_nodes_connectivity, + multi_precision=True, + precision=65, vertices=True, islatlon=False, concave=False) @@ -177,7 +181,6 @@ def test_init_verts_multi_precision_triangle(self): def test_init_verts_multi_precision_fill_values(self): # Set the desired precision - gmpy2.get_context().precision = 100 # Generate the nodes on the unit sphere, and the nodes are extremely close to each other: the edge length # between any two nodes is less than 1e-17 (the precision of the nodes is 60 bits but we will use 65 bits for @@ -185,22 +188,29 @@ def test_init_verts_multi_precision_fill_values(self): nodes = [] for i in range(4): - theta = gmpy2.div(i * 2 * gmpy2.const_pi(precision=65), 4) # Angle in radians + theta = gmpy2.div(i * 2 * gmpy2.const_pi(precision=65), + 4) # Angle in radians x = gmpy2.cos(theta) y = gmpy2.sin(theta) - z = mpfr('0') # All nodes will have z-coordinate as 0 on the unit sphere + z = mpfr( + '0') # All nodes will have z-coordinate as 0 on the unit sphere nodes.append([x, y, z]) # Generate the face nodes connectivity - dumb_nodes = [ux.INT_FILL_VALUE_MPZ, ux.INT_FILL_VALUE_MPZ, ux.INT_FILL_VALUE_MPZ] + dumb_nodes = [ + ux.INT_FILL_VALUE_MPZ, ux.INT_FILL_VALUE_MPZ, ux.INT_FILL_VALUE_MPZ + ] face_nodes_connectivity = np.array([ np.array([nodes[0], nodes[1], nodes[2], dumb_nodes], dtype=object), np.array([nodes[0], nodes[2], nodes[3], dumb_nodes], dtype=object), np.array([nodes[0], nodes[3], nodes[1], dumb_nodes], dtype=object), np.array([nodes[1], nodes[2], nodes[3], nodes[0]], dtype=object) - ], dtype=object) + ], + dtype=object) # Create the grid - vgrid = ux.Grid(face_nodes_connectivity, multi_precision=True, precision= 65, + vgrid = ux.Grid(face_nodes_connectivity, + multi_precision=True, + precision=65, vertices=True, islatlon=False, concave=False) diff --git a/test/test_helpers.py b/test/test_helpers.py index b34d57f28..fd6a748cd 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -178,4 +178,3 @@ def test_replace_fill_values_invalid(self): original_fill=-1, new_fill=INT_FILL_VALUE, new_dtype=np.int16) - diff --git a/test/test_multi_precision_helpers.py b/test/test_multi_precision_helpers.py index 3369e175a..c2414ba2e 100644 --- a/test/test_multi_precision_helpers.py +++ b/test/test_multi_precision_helpers.py @@ -1,5 +1,3 @@ - - import os import numpy as np import gmpy2 @@ -11,7 +9,7 @@ import uxarray as ux from uxarray.constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ -from uxarray.multi_precision_helpers import convert_to_mpfr, unique_coordinates_mpfr, precision_bits_to_decimal_digits, decimal_digits_to_precision_bits +from uxarray.multi_precision_helpers import convert_to_multiprecision, unique_coordinates_multiprecision, precision_bits_to_decimal_digits, decimal_digits_to_precision_bits import math try: @@ -28,34 +26,75 @@ class TestMultiPrecision(TestCase): - def test_convert_to_mpfr(self): + + def test_convert_to_multiprecision(self): """Tests if the convert_to_mpfr() helper function converts a numpy array to a numpy array of the correct dtype.""" # test different datatypes for face_nodes test_precision = 64 - f0_deg = np.array([np.array([120, -20]), np.array([130, -10]), np.array([120, 0]), - np.array([105, 0]), np.array([95, -10]), np.array([105, -20])]) - f1_deg = np.array([np.array([120, 0]), np.array([120, 10]), np.array([115, 0]), - np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), - np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), - np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) - f2_deg = np.array([np.array([115, 0]), np.array([120, 10]), np.array([100, 10]), - np.array([105, 0]), np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), - np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) - f3_deg = np.array([np.array([95, -10]), np.array([105, 0]), np.array([95, 30]), - np.array([80, 30]), np.array([70, 0]), np.array([75, -10])]) - f4_deg = np.array([np.array([65, -20]), np.array([75, -10]), np.array([70, 0]), - np.array([55, 0]), np.array([45, -10]), np.array([55, -20])]) - f5_deg = np.array([np.array([70, 0]), np.array([80, 30]), np.array([70, 30]), - np.array([60, 0]), np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), - np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) - f6_deg = np.array([np.array([60, 0]), np.array([70, 30]), np.array([40, 30]), - np.array([45, 0]), np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), - np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE])]) - - verts = np.array([f0_deg, f1_deg, f2_deg, f3_deg, f4_deg, f5_deg, f6_deg]) - - verts_mpfr = convert_to_mpfr(verts, str_mode=False,precision=test_precision) + f0_deg = np.array([ + np.array([120, -20]), + np.array([130, -10]), + np.array([120, 0]), + np.array([105, 0]), + np.array([95, -10]), + np.array([105, -20]) + ]) + f1_deg = np.array([ + np.array([120, 0]), + np.array([120, 10]), + np.array([115, 0]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]) + ]) + f2_deg = np.array([ + np.array([115, 0]), + np.array([120, 10]), + np.array([100, 10]), + np.array([105, 0]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]) + ]) + f3_deg = np.array([ + np.array([95, -10]), + np.array([105, 0]), + np.array([95, 30]), + np.array([80, 30]), + np.array([70, 0]), + np.array([75, -10]) + ]) + f4_deg = np.array([ + np.array([65, -20]), + np.array([75, -10]), + np.array([70, 0]), + np.array([55, 0]), + np.array([45, -10]), + np.array([55, -20]) + ]) + f5_deg = np.array([ + np.array([70, 0]), + np.array([80, 30]), + np.array([70, 30]), + np.array([60, 0]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]) + ]) + f6_deg = np.array([ + np.array([60, 0]), + np.array([70, 30]), + np.array([40, 30]), + np.array([45, 0]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]), + np.array([ux.INT_FILL_VALUE, ux.INT_FILL_VALUE]) + ]) + + verts = np.array( + [f0_deg, f1_deg, f2_deg, f3_deg, f4_deg, f5_deg, f6_deg]) + + verts_mpfr = convert_to_multiprecision(verts, + str_mode=False, + precision=test_precision) # Test if every object in the verts_mpfr array is of type mpfr for i in range(verts_mpfr.shape[0]): @@ -64,23 +103,26 @@ def test_convert_to_mpfr(self): if type(val) != mpz: self.assertEqual(val.precision, test_precision) # Then compare the values between verts and verts_mpfr up to the 53 bits of precision - self.assertAlmostEqual(verts[i, j][idx], val, places=FLOAT_PRECISION_BITS) + self.assertAlmostEqual(verts[i, j][idx], + val, + places=FLOAT_PRECISION_BITS) else: self.assertTrue(gmpy2.cmp(val, INT_FILL_VALUE_MPZ) == 0) - def test_mpfr_unique_normal_case(self): - """ - The input cartesian coordinates represents 8 vertices on a cube - 7---------6 - /| /| - / | / | - 3---------2 | - | | | | - | 4------|--5 - | / | / - |/ |/ - 0---------1 + def test_unique_coordinates_multiprecision_normal_case(self): + """The input cartesian coordinates represents 8 vertices on a cube 7. + + ---------6. + + /| /| + / | / | + 3---------2 | + | | | | + | 4------|--5 + | / | / + |/ |/ + 0---------1 """ test_precision = 64 @@ -122,22 +164,37 @@ def test_mpfr_unique_normal_case(self): # Now consturct the grid using the faces_coords verts_cart = np.array(faces_coords) - verts_cart_mpfr = convert_to_mpfr(verts_cart, str_mode=False, precision=test_precision) - verts_cart_mpfr_unique, unique_inverse = unique_coordinates_mpfr(verts_cart_mpfr.reshape( - -1, verts_cart_mpfr.shape[-1]), precision=test_precision) + verts_cart_mpfr = convert_to_multiprecision(verts_cart, + str_mode=False, + precision=test_precision) + verts_cart_mpfr_unique, unique_inverse = unique_coordinates_multiprecision( + verts_cart_mpfr.reshape(-1, verts_cart_mpfr.shape[-1]), + precision=test_precision) recovered_verts_cart_mpfr = verts_cart_mpfr_unique[unique_inverse] # Compare the recovered verts_cart_mpfr with the original verts_cart_mpfr.reshape(-1, verts_cart_mpfr.shape[-1]) expected = verts_cart_mpfr.reshape(-1, verts_cart_mpfr.shape[-1]) for i in range(recovered_verts_cart_mpfr.shape[0]): for j in range(recovered_verts_cart_mpfr.shape[1]): - self.assertEqual(recovered_verts_cart_mpfr[i, j].precision, test_precision) + self.assertEqual(recovered_verts_cart_mpfr[i, j].precision, + test_precision) # Then compare the values between verts and verts_mpfr up to the 53 bits of precision - self.assertAlmostEqual(expected[i, j], recovered_verts_cart_mpfr[i, j], places=FLOAT_PRECISION_BITS) - - def test_mpfr_unique_extreme_case(self): - coord1 = [gmpy2.mpfr('1.23456789'), gmpy2.mpfr('2.34567890'), gmpy2.mpfr('3.45678901')] - coord2 = [gmpy2.mpfr('1.23456789'), gmpy2.mpfr('2.34567890'), gmpy2.mpfr('3.45678901')] + self.assertAlmostEqual(expected[i, j], + recovered_verts_cart_mpfr[i, j], + places=FLOAT_PRECISION_BITS) + + def test_unique_coordinates_multiprecision_extreme_case(self): + # Construct coordinates that are extremely closed to each other + coord1 = [ + gmpy2.mpfr('1.23456789'), + gmpy2.mpfr('2.34567890'), + gmpy2.mpfr('3.45678901') + ] + coord2 = [ + gmpy2.mpfr('1.23456789'), + gmpy2.mpfr('2.34567890'), + gmpy2.mpfr('3.45678901') + ] # Convert the coordinates to string format coord1_str = ["{0:.17f}".format(coord) for coord in coord1] coord2_str = ["{0:.17f}".format(coord) for coord in coord2] @@ -147,16 +204,30 @@ def test_mpfr_unique_extreme_case(self): coord2_final = [coord + '1' for coord in coord2_str] verts = np.array([coord1_final, coord2_final]) bit_precision = decimal_digits_to_precision_bits(18) - verts_60 = convert_to_mpfr(verts, str_mode=True, precision=bit_precision) - verts_57 = convert_to_mpfr(verts, str_mode=True, precision=bit_precision - 1) - - verts_cart_mpfr_unique_64, unique_inverse_64 = unique_coordinates_mpfr(verts_60, precision=bit_precision) - verts_cart_mpfr_unique_57, unique_inverse_57 = unique_coordinates_mpfr(verts_57, precision=bit_precision - 1) + verts_60 = convert_to_multiprecision(verts, + str_mode=True, + precision=bit_precision) + verts_57 = convert_to_multiprecision(verts, + str_mode=True, + precision=bit_precision - 1) + + verts_cart_mpfr_unique_64, unique_inverse_64 = unique_coordinates_multiprecision( + verts_60, precision=bit_precision) + verts_cart_mpfr_unique_57, unique_inverse_57 = unique_coordinates_multiprecision( + verts_57, precision=bit_precision - 1) self.assertTrue(len(verts_cart_mpfr_unique_64) == 2) self.assertTrue(len(verts_cart_mpfr_unique_57) == 1) - coord1 = [gmpy2.mpfr('1.23456789'), gmpy2.mpfr('2.34567890'), gmpy2.mpfr('3.45678901')] - coord2 = [gmpy2.mpfr('1.23456789'), gmpy2.mpfr('2.34567890'), gmpy2.mpfr('3.45678901')] + coord1 = [ + gmpy2.mpfr('1.23456789'), + gmpy2.mpfr('2.34567890'), + gmpy2.mpfr('3.45678901') + ] + coord2 = [ + gmpy2.mpfr('1.23456789'), + gmpy2.mpfr('2.34567890'), + gmpy2.mpfr('3.45678901') + ] # Convert the coordinates to string format precision_bits = 200 decimal_digits = precision_bits_to_decimal_digits(precision_bits - 1) @@ -171,14 +242,50 @@ def test_mpfr_unique_extreme_case(self): coord2_final = [coord + '1' for coord in coord2_str] verts = np.array([coord1_final, coord2_final]) - verts_200 = convert_to_mpfr(verts, str_mode=True, precision=precision_bits) - verts_199 = convert_to_mpfr(verts, str_mode=True, precision=check - 1) - - verts_cart_mpfr_unique_200, unique_inverse_200 = unique_coordinates_mpfr(verts_200, precision=precision_bits) - verts_cart_mpfr_unique_199, unique_inverse_199 = unique_coordinates_mpfr(verts_199, precision=check - 1) + verts_200 = convert_to_multiprecision(verts, + str_mode=True, + precision=precision_bits) + verts_199 = convert_to_multiprecision(verts, + str_mode=True, + precision=check - 1) + + verts_cart_mpfr_unique_200, unique_inverse_200 = unique_coordinates_multiprecision( + verts_200, precision=precision_bits) + verts_cart_mpfr_unique_199, unique_inverse_199 = unique_coordinates_multiprecision( + verts_199, precision=check - 1) self.assertTrue(len(verts_cart_mpfr_unique_200) == 2) self.assertTrue(len(verts_cart_mpfr_unique_199) == 1) + def test_unique_coordinates_multiprecision_filled_values(self): + # The mpft_unique function should be able to handle filled values + coord1 = [ + gmpy2.mpfr('1.23456789'), + gmpy2.mpfr('2.34567890'), + gmpy2.mpfr('3.45678901') + ] + coord2 = [ + gmpy2.mpfr('1.23456789'), + gmpy2.mpfr('2.34567890'), + gmpy2.mpfr('3.45678901') + ] + # Convert the coordinates to string format + precision_bits = 200 + decimal_digits = precision_bits_to_decimal_digits(precision_bits - 1) + format_str = "{0:." + str(decimal_digits) + "f}" + coord1_str = [format_str.format(coord) for coord in coord1] + coord2_str = [format_str.format(coord) for coord in coord2] - + # Create the final coordinates with the differing 64th bit + coord1_final = [coord + '0' for coord in coord1_str] + coord2_final = [coord + '1' for coord in coord2_str] + verts = np.array([ + coord1_final, coord2_final, + ['INT_FILL_VALUE', 'INT_FILL_VALUE', 'INT_FILL_VALUE'] + ]) + verts_200 = convert_to_multiprecision(verts, + str_mode=True, + precision=precision_bits) + unique_coords, unique_inverse = unique_coordinates_multiprecision( + verts_200, precision=precision_bits) + self.assertTrue(len(unique_coords) == 3) diff --git a/uxarray/grid.py b/uxarray/grid.py index aee57d638..b5f3ad027 100644 --- a/uxarray/grid.py +++ b/uxarray/grid.py @@ -13,7 +13,7 @@ from ._mpas import _read_mpas from .helpers import get_all_face_area_from_coords, parse_grid_type, node_xyz_to_lonlat_rad, node_lonlat_rad_to_xyz, close_face_nodes from .constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ -from .multi_precision_helpers import convert_to_mpfr, unique_coordinates_mpfr, precision_bits_to_decimal_digits, decimal_digits_to_precision_bits +from .multi_precision_helpers import convert_to_multiprecision, unique_coordinates_multiprecision, precision_bits_to_decimal_digits, decimal_digits_to_precision_bits class Grid: @@ -31,7 +31,11 @@ class Grid: >>> mesh.encode_as("ugrid") """ - def __init__(self, dataset, multi_precision=False, precision = FLOAT_PRECISION_BITS, **kwargs): + def __init__(self, + dataset, + multi_precision=False, + precision=FLOAT_PRECISION_BITS, + **kwargs): """Initialize grid variables, decide if loading happens via file, verts or gridspec. @@ -88,12 +92,16 @@ def __init__(self, dataset, multi_precision=False, precision = FLOAT_PRECISION_B dataset = np.asarray(dataset) # grid with multiple faces if dataset.ndim == 3: - self.__from_vert__(dataset, multi_precision=multi_precision, precision=precision) + self.__from_vert__(dataset, + multi_precision=multi_precision, + precision=precision) self.source_grid = "From vertices" # grid with a single face elif dataset.ndim == 2: dataset = np.array([dataset]) - self.__from_vert__(dataset, multi_precision=multi_precision, precision=precision) + self.__from_vert__(dataset, + multi_precision=multi_precision, + precision=precision) self.source_grid = "From vertices" else: raise RuntimeError( @@ -172,7 +180,10 @@ def __init_grid_var_attrs__(self) -> None: if value in self.ds.dims: setattr(self, key, len(self.ds[value])) - def __from_vert__(self, dataset, multi_precision=False, precision= FLOAT_PRECISION_BITS): + def __from_vert__(self, + dataset, + multi_precision=False, + precision=FLOAT_PRECISION_BITS): """Create a grid with faces constructed from vertices specified by the given argument. @@ -218,13 +229,15 @@ def __from_vert__(self, dataset, multi_precision=False, precision= FLOAT_PRECISI # Flatten the input_array_mpfr to a 1D array so that we can check the type of each element input_array_mpfr_copy = np.ravel(dataset) for i in range(len(input_array_mpfr_copy)): - if type(input_array_mpfr_copy[i]) != gmpy2.mpfr and type(input_array_mpfr_copy[i]) != gmpy2.mpz: - dataset= convert_to_mpfr(dataset, precision=precision) + if type(input_array_mpfr_copy[i]) != gmpy2.mpfr and type( + input_array_mpfr_copy[i]) != gmpy2.mpz: + dataset = convert_to_multiprecision(dataset, + precision=precision) except Exception as e: raise e - unique_verts, indices = unique_coordinates_mpfr(dataset.reshape( - -1, dataset.shape[-1]), precision=precision) + unique_verts, indices = unique_coordinates_multiprecision( + dataset.reshape(-1, dataset.shape[-1]), precision=precision) else: unique_verts, indices = np.unique(dataset.reshape( @@ -236,22 +249,39 @@ def __from_vert__(self, dataset, multi_precision=False, precision= FLOAT_PRECISI if multi_precision: # Perform element-wise comparison using gmpy.cmp() fill_value_mask = np.logical_or( - np.array([gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 for x in unique_verts[:, 0]], dtype=bool), - np.array([gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 for x in unique_verts[:, 1]], dtype=bool) - ) + np.array([ + gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 + for x in unique_verts[:, 0] + ], + dtype=bool), + np.array([ + gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 + for x in unique_verts[:, 1] + ], + dtype=bool)) if dataset[0][0].size > 2: fill_value_mask = np.logical_or( - np.array([gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 for x in - unique_verts[:, 0]], dtype=bool), - np.array([gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 for x in - unique_verts[:, 1]], dtype=bool), - np.array([gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 for x in - unique_verts[:, 2]], dtype=bool)) + np.array([ + gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 + for x in unique_verts[:, 0] + ], + dtype=bool), + np.array([ + gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 + for x in unique_verts[:, 1] + ], + dtype=bool), + np.array([ + gmpy2.cmp(x, INT_FILL_VALUE_MPZ) == 0 + for x in unique_verts[:, 2] + ], + dtype=bool)) else: - fill_value_mask = np.logical_or(unique_verts[:, 0] == INT_FILL_VALUE, - unique_verts[:, 1] == INT_FILL_VALUE) + fill_value_mask = np.logical_or( + unique_verts[:, 0] == INT_FILL_VALUE, + unique_verts[:, 1] == INT_FILL_VALUE) if dataset[0][0].size > 2: fill_value_mask = np.logical_or( unique_verts[:, 0] == INT_FILL_VALUE, @@ -273,7 +303,9 @@ def __from_vert__(self, dataset, multi_precision=False, precision= FLOAT_PRECISI for i, idx in enumerate(false_indices): indices[indices == idx] = INT_FILL_VALUE_MPZ for j in range(indices.size): - if gmpy2.cmp(mpz(indices[j]), mpz(idx)) > 0 and gmpy2.cmp(mpz(indices[j]), INT_FILL_VALUE_MPZ) != 0: + if gmpy2.cmp(mpz( + indices[j]), mpz(idx)) > 0 and gmpy2.cmp( + mpz(indices[j]), INT_FILL_VALUE_MPZ) != 0: indices[j] -= 1 else: diff --git a/uxarray/helpers.py b/uxarray/helpers.py index 1c94b082f..5a88748b1 100644 --- a/uxarray/helpers.py +++ b/uxarray/helpers.py @@ -1,7 +1,5 @@ import numpy as np import xarray as xr - -from pathlib import PurePath from .get_quadratureDG import get_gauss_quadratureDG, get_tri_quadratureDG from numba import njit, config import math @@ -674,9 +672,3 @@ def close_face_nodes(Mesh2_face_nodes, nMesh2_face, nMaxMesh2_face_nodes): np.put(closed.ravel(), first_fv_idx_1d, first_node_value) return closed - - - - - - diff --git a/uxarray/multi_precision_helpers.py b/uxarray/multi_precision_helpers.py index eba6f52d6..4a92e02bb 100644 --- a/uxarray/multi_precision_helpers.py +++ b/uxarray/multi_precision_helpers.py @@ -2,14 +2,18 @@ from gmpy2 import mpfr, mpz import numpy as np import math -from .constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ +from .constants import FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ -def convert_to_mpfr(input_array, str_mode=True, precision=FLOAT_PRECISION_BITS): - """ - Convert a numpy array to a list of mpfr numbers. +def convert_to_multiprecision(input_array, + str_mode=True, + precision=FLOAT_PRECISION_BITS): + """Convert a numpy array to a list of mpfr numbers. + The default precision of an mpfr is 53 bits - the same precision as Python’s `float` type. https://gmpy2.readthedocs.io/en/latest/mpfr.html + If the input array contains fill values INT_FILL_VALUE, the fill values will be converted to INT_FILL_VALUE_MPZ, + which is the multi-precision integer representation of INT_FILL_VALUE. Parameters ---------- @@ -31,6 +35,12 @@ def convert_to_mpfr(input_array, str_mode=True, precision=FLOAT_PRECISION_BITS): The output array with mpfr type, which supports correct rounding, selectable rounding modes, and many trigonometric, exponential, and special functions. A context manager is used to control precision, rounding modes, and the behavior of exceptions. + + Raises + ---------- + ValueError + The input array should be string when str_mode is True, if not, raise + ValueError('The input array should be string when str_mode is True.') """ # To take advantage of the higher precision provided by the mpfr type, always pass constants as strings. @@ -39,7 +49,7 @@ def convert_to_mpfr(input_array, str_mode=True, precision=FLOAT_PRECISION_BITS): mpfr_array = np.array(flattened_array, dtype=object) if not str_mode: # Cast the input 2D array to string array - for idx,val in enumerate(flattened_array): + for idx, val in enumerate(flattened_array): if gmpy2.cmp(mpz(val), INT_FILL_VALUE_MPZ) == 0: mpfr_array[idx] = INT_FILL_VALUE_MPZ else: @@ -50,8 +60,12 @@ def convert_to_mpfr(input_array, str_mode=True, precision=FLOAT_PRECISION_BITS): else: - if ~np.all([np.issubdtype(type(element), np.str_) for element in flattened_array]): - raise ValueError('The input array should be string when str_mode is True.') + if ~np.all([ + np.issubdtype(type(element), np.str_) + for element in flattened_array + ]): + raise ValueError( + 'The input array should be string when str_mode is True.') # Then convert the input array to mpfr array for idx, val in enumerate(flattened_array): if val == "INT_FILL_VALUE": @@ -64,15 +78,35 @@ def convert_to_mpfr(input_array, str_mode=True, precision=FLOAT_PRECISION_BITS): return mpfr_array -def unique_coordinates_mpfr(input_array_mpfr, precision=FLOAT_PRECISION_BITS): - """ - Find the unique coordinates in the input array with mpfr numbers. +def unique_coordinates_multiprecision(input_array_mpfr, + precision=FLOAT_PRECISION_BITS): + """Find the unique coordinates in the input array with mpfr numbers. + The default precision of an mpfr is 53 bits - the same precision as Python’s `float` type. + It can recognize the fill values INT_FILL_VALUE_MPZ, which is the multi-precision integer representation of + INT_FILL_VALUE. Parameters: ---------- + input_array_mpfr : numpy.ndarray, gmpy2.mpfr type + The input array containing mpfr numbers. + precision : int, optional + The precision in bits used for the mpfr calculations. Default is FLOAT_PRECISION_BITS. + Returns: + ------- + unique_arr : numpy.ndarray, gmpy2.mpfr + Array of unique coordinates in the input array. + + inverse_indices: numpy.ndarray, int + The indices to reconstruct the original array from the unique array. Only provided if return_inverse is True. + + Raises + ---------- + ValueError + The input array should be string when str_mode is True, if not, raise + ValueError('The input array should be string when str_mode is True.') """ # Check if the input_array is in th mpfr type @@ -80,9 +114,11 @@ def unique_coordinates_mpfr(input_array_mpfr, precision=FLOAT_PRECISION_BITS): # Flatten the input_array_mpfr to a 1D array so that we can check the type of each element input_array_mpfr_copy = np.ravel(input_array_mpfr) for i in range(len(input_array_mpfr_copy)): - if type(input_array_mpfr_copy[i]) != gmpy2.mpfr and type(input_array_mpfr_copy[i]) != gmpy2.mpz: - raise ValueError('The input array should be in the mpfr type. You can use convert_to_mpfr() to ' - 'convert the input array to mpfr.') + if type(input_array_mpfr_copy[i]) != gmpy2.mpfr and type( + input_array_mpfr_copy[i]) != gmpy2.mpz: + raise ValueError( + 'The input array should be in the mpfr type. You can use convert_to_mpfr() to ' + 'convert the input array to mpfr.') except Exception as e: raise e @@ -94,8 +130,18 @@ def unique_coordinates_mpfr(input_array_mpfr, precision=FLOAT_PRECISION_BITS): current_index = 0 for i in range(m): - format_string = "{0:+." + str(precision + 1) + "Uf}" - hashable_row = tuple(format_string.format(gmpy2.mpfr(x, precision)) for x in input_array_mpfr[i]) + # We only need to check the first element of each row since the elements in the same row are the same type + # (Either mpfr for valid coordinates or INT_FILL_VALUE_MPZ for fill values) + if type(input_array_mpfr[i][0]) == gmpy2.mpfr: + format_string = "{0:+." + str(precision + 1) + "Uf}" + elif type(input_array_mpfr[i][0]) == gmpy2.mpz: + format_string = "{:<+" + str(precision + 1) + "d}" + else: + raise ValueError( + 'The input array should be in the mpfr/mpz type. You can use convert_to_multiprecision() ' + 'to convert the input array to multiprecision format.') + hashable_row = tuple( + format_string.format(x) for x in input_array_mpfr[i]) if hashable_row not in unique_dict: unique_dict[hashable_row] = current_index @@ -112,8 +158,7 @@ def unique_coordinates_mpfr(input_array_mpfr, precision=FLOAT_PRECISION_BITS): def decimal_digits_to_precision_bits(decimal_digits): - """ - Convert the number of decimal digits to the number of bits of precision + """Convert the number of decimal digits to the number of bits of precision. Parameters ---------- @@ -130,8 +175,7 @@ def decimal_digits_to_precision_bits(decimal_digits): def precision_bits_to_decimal_digits(precision): - """ - Convert the number of bits of precision to the number of decimal digits + """Convert the number of bits of precision to the number of decimal digits. Parameters ---------- @@ -145,10 +189,11 @@ def precision_bits_to_decimal_digits(precision): """ # Compute the decimal digit count using gmpy2.log10() log10_2 = gmpy2.log10(gmpy2.mpfr(2)) # Logarithm base 10 of 2 - log10_precision = gmpy2.div(1, log10_2) # Logarithm base 10 of the precision + log10_precision = gmpy2.div(1, + log10_2) # Logarithm base 10 of the precision decimal_digits = gmpy2.div(precision, log10_precision) # Convert the result to an integer decimal_digits = int(math.floor(decimal_digits)) - return decimal_digits \ No newline at end of file + return decimal_digits From 4ccb76cac94eee2e5be571c7e32b66417fec9611 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Sat, 20 May 2023 12:50:51 -0700 Subject: [PATCH 09/59] Add jupyter-notebook example --- docs/examples.rst | 1 + docs/examples/000-template.ipynb | 20 +- docs/examples/001-read-grid-data.ipynb | 66 ++++- docs/examples/002-access-grid-info.ipynb | 72 ++++- docs/examples/003-area-calc.ipynb | 165 ++++++++--- docs/examples/004-multiprecision-usage.ipynb | 277 +++++++++++++++++++ docs/gallery.yml | 4 + 7 files changed, 542 insertions(+), 63 deletions(-) create mode 100644 docs/examples/004-multiprecision-usage.ipynb diff --git a/docs/examples.rst b/docs/examples.rst index 312eb5eef..60870ae82 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -18,3 +18,4 @@ see the :doc:`contributing`. examples/001-read-grid-data.ipynb examples/002-access-grid-info.ipynb examples/003-area-calc.ipynb + examples/004-multiprecision-usage.ipynb diff --git a/docs/examples/000-template.ipynb b/docs/examples/000-template.ipynb index 2c2d42a08..3f1bd2b3e 100644 --- a/docs/examples/000-template.ipynb +++ b/docs/examples/000-template.ipynb @@ -2,7 +2,11 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, "source": [ "# Blank Template Notebook" ] @@ -10,7 +14,11 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [], "source": [ "import numpy as np\n", @@ -20,7 +28,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [], "source": [] } @@ -52,4 +64,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/docs/examples/001-read-grid-data.ipynb b/docs/examples/001-read-grid-data.ipynb index 3fa71b8bf..ebda6a937 100644 --- a/docs/examples/001-read-grid-data.ipynb +++ b/docs/examples/001-read-grid-data.ipynb @@ -2,7 +2,11 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, "source": [ "# Reading In Grid Data\n", "\n", @@ -33,7 +37,10 @@ "```" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } }, { @@ -46,13 +53,20 @@ "combining multiple data variables into a single `xarray.Dataset`." ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } }, { "cell_type": "code", "execution_count": 4, - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [], "source": [ "import xarray as xr" @@ -61,7 +75,11 @@ { "cell_type": "code", "execution_count": 5, - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [], "source": [ "# Base data path\n", @@ -78,7 +96,11 @@ { "cell_type": "code", "execution_count": 6, - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [ { "name": "stderr", @@ -117,7 +139,10 @@ "grid_ds" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } } }, { @@ -138,7 +163,10 @@ "multi_data_ds" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } } }, { @@ -147,7 +175,10 @@ "## Representing The Unstructured Grid with UXarray" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } }, { @@ -173,7 +204,10 @@ "grid = ux.Grid(grid_ds)" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } } }, { @@ -194,7 +228,10 @@ "grid.ds" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } } }, { @@ -206,7 +243,10 @@ "it, which can be explored in the next notebooks." ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } } ], @@ -237,4 +277,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/docs/examples/002-access-grid-info.ipynb b/docs/examples/002-access-grid-info.ipynb index 3e51c5b47..d7b1105d2 100644 --- a/docs/examples/002-access-grid-info.ipynb +++ b/docs/examples/002-access-grid-info.ipynb @@ -35,7 +35,10 @@ "3. UXarray's Standardized UGRID Names (Most convenient)" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } }, { @@ -50,7 +53,10 @@ "Let us first read in the data:" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } }, { @@ -62,7 +68,10 @@ "import xarray as xr" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } } }, { @@ -85,7 +94,10 @@ "ugrid_02 = ux.Grid(ugrid_02_ds)" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } } }, { @@ -95,7 +107,10 @@ "in variable names:" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } }, { @@ -127,7 +142,10 @@ "print(\"ugrid_02 variable names:\", ugrid_02_names)" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } } }, { @@ -141,7 +159,10 @@ "index for accessing them will be different for both grids." ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } }, { @@ -160,7 +181,10 @@ "n_face_nodes_2 = ugrid_02.ds['nFaceNodes']" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } } }, { @@ -178,7 +202,10 @@ "makes the indexing code much longer than the previous method." ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } }, { @@ -199,7 +226,10 @@ "n_face_nodes_2 = ugrid_02.ds[var_names_dict['nMesh2_node']]" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } } }, { @@ -210,7 +240,10 @@ "`Mesh2_node_x`, likewise in `ugrid_01`." ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } }, { @@ -228,7 +261,10 @@ "we find this as the most convenient approach." ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } }, { @@ -247,7 +283,10 @@ "n_face_nodes_2 = ugrid_02.nMesh2_node" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } } }, { @@ -259,7 +298,10 @@ "each of these access ways." ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } } ], @@ -284,4 +326,4 @@ }, "nbformat": 4, "nbformat_minor": 0 -} +} \ No newline at end of file diff --git a/docs/examples/003-area-calc.ipynb b/docs/examples/003-area-calc.ipynb index 096fabaeb..b9faff682 100644 --- a/docs/examples/003-area-calc.ipynb +++ b/docs/examples/003-area-calc.ipynb @@ -4,7 +4,10 @@ "attachments": {}, "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } }, "source": [ "# Face Area Calculations\n", @@ -28,7 +31,10 @@ "attachments": {}, "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } }, "source": [ "We will be using the `outCSne30.ug` grid file, which is encoded in the UGRID convention." @@ -42,7 +48,10 @@ "end_time": "2023-04-19T09:57:02.171354Z", "start_time": "2023-04-19T09:57:02.144399Z" }, - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } }, "outputs": [], "source": [ @@ -59,7 +68,10 @@ "end_time": "2023-04-19T09:57:02.172124Z", "start_time": "2023-04-19T09:57:02.148439Z" }, - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } }, "outputs": [], "source": [ @@ -80,7 +92,10 @@ "attachments": {}, "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } }, "source": [ "## 1. Calculate Total Face Area\n", @@ -95,7 +110,10 @@ "end_time": "2023-04-19T09:57:03.685968Z", "start_time": "2023-04-19T09:57:02.154548Z" }, - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } }, "outputs": [], "source": [ @@ -107,7 +125,10 @@ "attachments": {}, "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } }, "source": [ "## 2. Options for `Grid.calculate_total_face_area` Function\n", @@ -129,6 +150,9 @@ "ExecuteTime": { "end_time": "2023-04-19T09:57:03.690062Z", "start_time": "2023-04-19T09:57:03.687612Z" + }, + "pycharm": { + "name": "#%%\n" } }, "outputs": [], @@ -140,7 +164,11 @@ { "attachments": {}, "cell_type": "markdown", - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, "source": [ "For the result above, notice that the area is slightly different than the first calculation we made.\n", "\n", @@ -155,7 +183,10 @@ "attachments": {}, "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } }, "source": [ "## 3. Getting Area of Individual Faces\n", @@ -172,7 +203,10 @@ "end_time": "2023-04-19T09:57:03.693036Z", "start_time": "2023-04-19T09:57:03.690838Z" }, - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } }, "outputs": [], "source": [ @@ -183,7 +217,10 @@ "attachments": {}, "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } }, "source": [ "Now calculate the area again with the `Grid.compute_face_areas` function using arguments: quadrature_rule \"gaussian\" and order 4" @@ -196,6 +233,9 @@ "ExecuteTime": { "end_time": "2023-04-19T09:57:03.696254Z", "start_time": "2023-04-19T09:57:03.693676Z" + }, + "pycharm": { + "name": "#%%\n" } }, "outputs": [], @@ -208,7 +248,11 @@ { "attachments": {}, "cell_type": "markdown", - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, "source": [ "Now we compare the values with actual know value and report error for each of the three cases above." ] @@ -216,7 +260,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [], "source": [ "actual_area = 4 * np.pi\n", @@ -233,14 +281,20 @@ "As we can see, it is clear that the Gaussian Quadrature Rule with Order 4 is the most accurate, and the Triangular Quadrature Rule with Order 1 is the least accurate.\n" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } }, { "attachments": {}, "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } }, "source": [ "## 4. Calculate Area of a Single Triangle in Cartesian Coordinates\n", @@ -258,7 +312,10 @@ "end_time": "2023-04-19T09:57:03.703400Z", "start_time": "2023-04-19T09:57:03.696048Z" }, - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } }, "outputs": [], "source": [ @@ -283,7 +340,10 @@ "end_time": "2023-04-19T09:57:03.705404Z", "start_time": "2023-04-19T09:57:03.703896Z" }, - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } }, "outputs": [], "source": [ @@ -293,7 +353,11 @@ { "attachments": {}, "cell_type": "markdown", - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, "source": [ "Additionally, if you are using a unit other than meters, you can update the units as follows" ] @@ -314,14 +378,20 @@ "area_gaussian" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } } }, { "attachments": {}, "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } }, "source": [] }, @@ -329,7 +399,10 @@ "attachments": {}, "cell_type": "markdown", "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } }, "source": [ "## 5. Calculate Area from Multiple Faces in Spherical Coordinates\n", @@ -345,7 +418,10 @@ "end_time": "2023-04-19T09:57:03.733266Z", "start_time": "2023-04-19T09:57:03.712481Z" }, - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } }, "outputs": [], "source": [ @@ -365,7 +441,10 @@ "We want our units to be spherical, so we pass through `islatlon=True`. Additionally, if `islatlon` is not passed through, it will default to spherical coordinates." ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } }, { @@ -376,7 +455,10 @@ "end_time": "2023-04-19T09:57:03.733502Z", "start_time": "2023-04-19T09:57:03.715386Z" }, - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } }, "outputs": [], "source": [ @@ -395,7 +477,10 @@ "end_time": "2023-04-19T09:57:03.733582Z", "start_time": "2023-04-19T09:57:03.724024Z" }, - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } }, "outputs": [], "source": [ @@ -405,7 +490,11 @@ { "attachments": {}, "cell_type": "markdown", - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, "source": [ "## 6. Area Calculation without Grid Object\n", "\n", @@ -420,7 +509,10 @@ "from uxarray.helpers import calculate_face_area" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } } }, { @@ -429,13 +521,20 @@ "`Grid.calculate_face_area` takes in three coordinate variables (x, y, z) in the form of numpy arrays and the coordinate type (either spherical or artesian) and computes the face area from the set of points" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } } }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [], "source": [ "cart_x = np.array([\n", @@ -455,7 +554,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [], "source": [ "calculate_face_area(cart_x, cart_y, cart_z, coords_type=\"cartesian\")" @@ -483,4 +586,4 @@ }, "nbformat": 4, "nbformat_minor": 0 -} +} \ No newline at end of file diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb new file mode 100644 index 000000000..91b9b4386 --- /dev/null +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -0,0 +1,277 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Multiprecision Arithmetic UXArray\n", + "In this notebook we will explore the use of the UXArray which supports multiprecision arithmetic." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n", + "is_executing": true + } + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import uxarray as ux\n", + "import gmpy2\n", + "from gmpy2 import mpfr, mpz" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Generate the input coordinates on the unit sphere using the gmpy2 mpfr type.\n", + "The distance between any two nodes is less than 1e-17 (the precision of the nodes is 60 bits, which is larger than the the python `float` type precision of 53 bits)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n", + "is_executing": true + } + }, + "outputs": [], + "source": [ + "# Generate the nodes on the unit sphere, and the nodes are extremely close to each other: the edge length\n", + "# between any two nodes is less than 1e-17 (the precision of the nodes is 60 bits but we will use 65 bits for\n", + "# better demonstration)\n", + "\n", + "nodes = []\n", + "for i in range(4):\n", + " theta = gmpy2.div(i * 2 * gmpy2.const_pi(precision=65),\n", + " 4) # Angle in radians\n", + " x = gmpy2.cos(theta)\n", + " y = gmpy2.sin(theta)\n", + " z = mpfr(\n", + " '0') # All nodes will have z-coordinate as 0 on the unit sphere\n", + " nodes.append([x, y, z])\n", + "\n", + "# Generate the face nodes connectivity\n", + "dumb_nodes = [\n", + " ux.INT_FILL_VALUE_MPZ, ux.INT_FILL_VALUE_MPZ, ux.INT_FILL_VALUE_MPZ\n", + "]\n", + "\n", + "face_nodes_connectivity = np.array([\n", + " np.array([nodes[0], nodes[1], nodes[2], dumb_nodes], dtype=object),\n", + " np.array([nodes[0], nodes[2], nodes[3], dumb_nodes], dtype=object),\n", + " np.array([nodes[0], nodes[3], nodes[1], dumb_nodes], dtype=object),\n", + " np.array([nodes[1], nodes[2], nodes[3], nodes[0]], dtype=object)\n", + "],\n", + " dtype=object)" + ] + }, + { + "cell_type": "markdown", + "source": [ + "Now we can see this topology has 4 faces and 4 nodes. Three of the facess have 3 nodes and one face has 4 nodes. So we also use the\n", + "fill value `ux.INT_FILL_VALUE_MPZ` to fill the fourth node of the first three faces." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "face_nodes_connectivity" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Construct the Grid object with the multiprecision mode on" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Create the grid\n", + "vgrid = ux.Grid(face_nodes_connectivity,\n", + " multi_precision=True,\n", + " precision=65,\n", + " vertices=True,\n", + " islatlon=False,\n", + " concave=False)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Check the Grid Information\n", + "We can see that even though the input nodes are extremely close to each other, the grid is still valid and not degenerated:\n", + "The face number is still 4 and the node number is still 4." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "vgrid.nMesh2_face\n", + "vgrid.nMesh2_node" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# Generate the input coordinates on the unit sphere using the string type.\n", + "When we want to achieve higher precision than 53 bits and we don't want to install the gmpy2 package, we can use the string type to represent the coordinates." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Generate the input coordinates on the unit sphere using the string type.\n", + "Note: if we want to use the fill value, we need to use the `INT_FILL_VALUE`" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "\n", + "# Provide nodes that are extremely closed to each other\n", + "face_nodes_connectivity = [['1.000000000000000000', '1.000000000000000000001', '1.0000000000000000000012','INT_FILL_VALUE'],\n", + " ['1.000000000000000001', '1.000000000000000000002', '1.0000000000000000000013','1.0000000000000000000014']]\n", + "\n", + "uxgrid = ux.Grid(face_nodes_connectivity, multi_precision=True, precision= 100,\n", + " vertices=True,\n", + " islatlon=False,\n", + " concave=False)\n", + "\n", + "# Perform any provided grid manipulation and then the results is precise up the the 65 bits" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Check the Grid Information" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "print(uxgrid.nMesh2_face)\n", + "print(uxgrid.nMesh2_node)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.9.13 ('uxarray-docs')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "5df6a94ef43b22c97c277f6a9dce3b546a324b9de41c6d8e82aecf8eafd14442" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/docs/gallery.yml b/docs/gallery.yml index 2e5f35b0e..c76a7c685 100644 --- a/docs/gallery.yml +++ b/docs/gallery.yml @@ -9,3 +9,7 @@ - title: Face Area Calculations path: examples/003-area-calc.ipynb thumbnail: _static/thumbnails/default.svg + +- title: Multiprecision Arithmetic Grid + path: examples/004-multiprecision-usage.ipynb + thumbnail: _static/thumbnails/default.svg From 0c34582a3037c91f16e24727732f0d2d10b64bb4 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Sat, 20 May 2023 12:53:49 -0700 Subject: [PATCH 10/59] Precommit --- test/test_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_grid.py b/test/test_grid.py index 485b42246..5b8b1958c 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -193,7 +193,7 @@ def test_init_verts_multi_precision_fill_values(self): x = gmpy2.cos(theta) y = gmpy2.sin(theta) z = mpfr( - '0') # All nodes will have z-coordinate as 0 on the unit sphere + '0') nodes.append([x, y, z]) # Generate the face nodes connectivity From 4eb723f32ed47d3db19137365ac782a2341e90fc Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Sat, 20 May 2023 12:55:05 -0700 Subject: [PATCH 11/59] Precommit --- test/test_grid.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_grid.py b/test/test_grid.py index 5b8b1958c..54a380f11 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -192,8 +192,7 @@ def test_init_verts_multi_precision_fill_values(self): 4) # Angle in radians x = gmpy2.cos(theta) y = gmpy2.sin(theta) - z = mpfr( - '0') + z = mpfr('0') nodes.append([x, y, z]) # Generate the face nodes connectivity From 304f7e8abcb11f689ad3d09b9dd4e103a50eff13 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Sat, 20 May 2023 13:04:51 -0700 Subject: [PATCH 12/59] Precommit for jupyter notebook --- docs/examples/004-multiprecision-usage.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index 91b9b4386..5d52000ba 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -274,4 +274,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} From b347f9ceafb8d9675ea0e472046939aab2c78d31 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Sat, 20 May 2023 13:10:41 -0700 Subject: [PATCH 13/59] The precommit for jupyter notebook example again --- docs/examples/004-multiprecision-usage.ipynb | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index 5d52000ba..7833c575d 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -56,7 +56,6 @@ "# Generate the nodes on the unit sphere, and the nodes are extremely close to each other: the edge length\n", "# between any two nodes is less than 1e-17 (the precision of the nodes is 60 bits but we will use 65 bits for\n", "# better demonstration)\n", - "\n", "nodes = []\n", "for i in range(4):\n", " theta = gmpy2.div(i * 2 * gmpy2.const_pi(precision=65),\n", @@ -77,8 +76,7 @@ " np.array([nodes[0], nodes[2], nodes[3], dumb_nodes], dtype=object),\n", " np.array([nodes[0], nodes[3], nodes[1], dumb_nodes], dtype=object),\n", " np.array([nodes[1], nodes[2], nodes[3], nodes[0]], dtype=object)\n", - "],\n", - " dtype=object)" + "], dtype=object)" ] }, { @@ -200,7 +198,6 @@ "execution_count": null, "outputs": [], "source": [ - "\n", "# Provide nodes that are extremely closed to each other\n", "face_nodes_connectivity = [['1.000000000000000000', '1.000000000000000000001', '1.0000000000000000000012','INT_FILL_VALUE'],\n", " ['1.000000000000000001', '1.000000000000000000002', '1.0000000000000000000013','1.0000000000000000000014']]\n", @@ -208,9 +205,7 @@ "uxgrid = ux.Grid(face_nodes_connectivity, multi_precision=True, precision= 100,\n", " vertices=True,\n", " islatlon=False,\n", - " concave=False)\n", - "\n", - "# Perform any provided grid manipulation and then the results is precise up the the 65 bits" + " concave=False)" ], "metadata": { "collapsed": false, @@ -274,4 +269,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file From 8e01d7466675f315c05b5725ace0906276162efa Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Sat, 20 May 2023 13:19:02 -0700 Subject: [PATCH 14/59] The precommit for jupyter notebook example again --- docs/examples/004-multiprecision-usage.ipynb | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index 7833c575d..820962a1c 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -169,23 +169,7 @@ }, { "cell_type": "markdown", - "source": [ - "# Generate the input coordinates on the unit sphere using the string type.\n", - "When we want to achieve higher precision than 53 bits and we don't want to install the gmpy2 package, we can use the string type to represent the coordinates." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## Generate the input coordinates on the unit sphere using the string type.\n", - "Note: if we want to use the fill value, we need to use the `INT_FILL_VALUE`" - ], + "source": [], "metadata": { "collapsed": false, "pycharm": { @@ -217,7 +201,7 @@ { "cell_type": "markdown", "source": [ - "## Check the Grid Information" + "# Check the Grid Information" ], "metadata": { "collapsed": false, From ca052c612dc21bd83de0029c0e3545464a37ed0e Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Sat, 20 May 2023 13:19:02 -0700 Subject: [PATCH 15/59] Your updated commit message here --- docs/examples/004-multiprecision-usage.ipynb | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index 7833c575d..820962a1c 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -169,23 +169,7 @@ }, { "cell_type": "markdown", - "source": [ - "# Generate the input coordinates on the unit sphere using the string type.\n", - "When we want to achieve higher precision than 53 bits and we don't want to install the gmpy2 package, we can use the string type to represent the coordinates." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## Generate the input coordinates on the unit sphere using the string type.\n", - "Note: if we want to use the fill value, we need to use the `INT_FILL_VALUE`" - ], + "source": [], "metadata": { "collapsed": false, "pycharm": { @@ -217,7 +201,7 @@ { "cell_type": "markdown", "source": [ - "## Check the Grid Information" + "# Check the Grid Information" ], "metadata": { "collapsed": false, From 3065422d878d66c8e64402d66b3abce0008c3b0c Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Sat, 20 May 2023 17:06:36 -0700 Subject: [PATCH 16/59] Done multiprecision normalized in place --- test/test_helpers.py | 14 +++++++++++++- uxarray/helpers.py | 23 +++++++++++++++++------ uxarray/multi_precision_helpers.py | 26 ++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/test/test_helpers.py b/test/test_helpers.py index fd6a748cd..748aaf0b7 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -3,7 +3,9 @@ import numpy.testing as nt import random import xarray as xr - +import gmpy2 +from gmpy2 import mpfr, mpz +import mpmath from unittest import TestCase from pathlib import Path @@ -11,6 +13,7 @@ from uxarray.helpers import _replace_fill_values from uxarray.constants import INT_DTYPE, INT_FILL_VALUE +from uxarray.multi_precision_helpers import convert_to_multiprecision, set_global_precision, decimal_digits_to_precision_bits try: import constants @@ -100,6 +103,15 @@ def test_normalize_in_place(self): random.random()]) self.assertLessEqual(np.absolute(np.sqrt(x * x + y * y + z * z) - 1), err_tolerance) + precision = decimal_digits_to_precision_bits(19) + set_global_precision(precision) + [x_mpfr, y_mpfr, z_mpfr] = convert_to_multiprecision(np.array(['1.0000000000000000001', '0.0000000000000000009', '0.0000000000000000001']), precision=precision) + normalized = ux.helpers.normalize_in_place([x_mpfr, y_mpfr, z_mpfr]) + # Calculate the sum of squares using gmpy2.fsum() + sum_of_squares = gmpy2.fsum([gmpy2.square(value) for value in normalized]) + abs = gmpy2.mul(gmpy2.reldiff(mpfr('1.0'),sum_of_squares), mpfr('1.0')) + self.assertAlmostEqual(abs, 0,places=19) + def test_node_xyz_to_lonlat_rad(self): [x, y, z] = ux.helpers.normalize_in_place([ diff --git a/uxarray/helpers.py b/uxarray/helpers.py index 5a88748b1..d26e09a22 100644 --- a/uxarray/helpers.py +++ b/uxarray/helpers.py @@ -1,5 +1,8 @@ import numpy as np import xarray as xr +import gmpy2 +from gmpy2 import mpfr, mpz +import mpmath from .get_quadratureDG import get_gauss_quadratureDG, get_tri_quadratureDG from numba import njit, config import math @@ -493,7 +496,6 @@ def node_lonlat_rad_to_xyz(node_coord): return [np.cos(lon) * np.cos(lat), np.sin(lon) * np.cos(lat), np.sin(lat)] -@njit def node_xyz_to_lonlat_rad(node_coord): """Calculate the latitude and longitude in radiance for a node represented in the [x, y, z] 3D Cartesian coordinates. @@ -536,8 +538,6 @@ def node_xyz_to_lonlat_rad(node_coord): return [d_lon_rad, d_lat_rad] - -@njit def normalize_in_place(node): """Helper function to project an arbitrary node in 3D coordinates [x, y, z] on the unit sphere. It uses the `np.linalg.norm` internally to calculate @@ -545,12 +545,12 @@ def normalize_in_place(node): Parameters ---------- - node: float list + node: list, python `float` or gmpy2.mpfr 3D Cartesian Coordinates [x, y, z] Returns ---------- - float list + normalized_node: list, python `float` or gmpy2.mpfr the result unit vector [x, y, z] where :math:`x^2 + y^2 + z^2 = 1` Raises @@ -561,7 +561,18 @@ def normalize_in_place(node): if len(node) != 3: raise RuntimeError("Input array should have a length of 3: [x, y, z]") - return np.array(node) / np.linalg.norm(np.array(node), ord=2) + if np.any(np.vectorize(lambda x: isinstance(x, (gmpy2.mpfr, gmpy2.mpz)))(node)): + # Convert the node to mpmath array + squres = [gmpy2.square(value) for value in node] + norm = gmpy2.sqrt(gmpy2.fsum([gmpy2.square(value) for value in node])) + + # Divide each element of the node by the norm using gmpy2 + normalized_node = [gmpy2.div(element, norm) for element in node] + + # Convert the result back to numpy array + return np.array(normalized_node) + else: + return np.array(node) / np.linalg.norm(np.array(node), ord=2) def _replace_fill_values(grid_var, original_fill, new_fill, new_dtype=None): diff --git a/uxarray/multi_precision_helpers.py b/uxarray/multi_precision_helpers.py index 4a92e02bb..2ea5ff34f 100644 --- a/uxarray/multi_precision_helpers.py +++ b/uxarray/multi_precision_helpers.py @@ -5,6 +5,28 @@ from .constants import FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ +def set_global_precision(global_precision=FLOAT_PRECISION_BITS): + """Set the global precision of the mpfr numbers. + Important Note: + 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified + in the code. + 2. Modifying the precision by calling this function will modify all following codes running context until + another call to this function. + + Parameters + ---------- + global_precision : int, optional + The global precision of the expected multiprecision float. + The default precision of an mpfr is 53 bits - the same precision as Python’s `float` type. + + Returns + ------- + None + """ + + gmpy2.get_context().precision = global_precision + + def convert_to_multiprecision(input_array, str_mode=True, precision=FLOAT_PRECISION_BITS): @@ -61,8 +83,8 @@ def convert_to_multiprecision(input_array, else: if ~np.all([ - np.issubdtype(type(element), np.str_) - for element in flattened_array + np.issubdtype(type(element), np.str_) + for element in flattened_array ]): raise ValueError( 'The input array should be string when str_mode is True.') From 757cb4b4334b9215aeef341933bd348a1bdf0d84 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Sat, 20 May 2023 18:11:47 -0700 Subject: [PATCH 17/59] Done multiprecision normalized in place --- test/test_helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_helpers.py b/test/test_helpers.py index 748aaf0b7..7e58f6506 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -103,6 +103,8 @@ def test_normalize_in_place(self): random.random()]) self.assertLessEqual(np.absolute(np.sqrt(x * x + y * y + z * z) - 1), err_tolerance) + + # Multiprecision test for places=19 precision = decimal_digits_to_precision_bits(19) set_global_precision(precision) [x_mpfr, y_mpfr, z_mpfr] = convert_to_multiprecision(np.array(['1.0000000000000000001', '0.0000000000000000009', '0.0000000000000000001']), precision=precision) From 020aa4b4bec41093c25937d9a9fea0a525313406 Mon Sep 17 00:00:00 2001 From: Philip Chmielowiec <67855069+philipc2@users.noreply.github.com> Date: Mon, 22 May 2023 14:13:04 -0500 Subject: [PATCH 18/59] pre-commit on new example --- docs/examples/004-multiprecision-usage.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index 820962a1c..32014dec9 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -253,4 +253,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} From 81f8290b3caa12b49546c4d5b4cd0200376e04fe Mon Sep 17 00:00:00 2001 From: Philip Chmielowiec <67855069+philipc2@users.noreply.github.com> Date: Mon, 22 May 2023 14:14:08 -0500 Subject: [PATCH 19/59] run pre-commit on all notebooks --- docs/examples/000-template.ipynb | 2 +- docs/examples/001-read-grid-data.ipynb | 2 +- docs/examples/002-access-grid-info.ipynb | 2 +- docs/examples/003-area-calc.ipynb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/examples/000-template.ipynb b/docs/examples/000-template.ipynb index 3f1bd2b3e..d0fb939cb 100644 --- a/docs/examples/000-template.ipynb +++ b/docs/examples/000-template.ipynb @@ -64,4 +64,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/docs/examples/001-read-grid-data.ipynb b/docs/examples/001-read-grid-data.ipynb index ebda6a937..c7b94875c 100644 --- a/docs/examples/001-read-grid-data.ipynb +++ b/docs/examples/001-read-grid-data.ipynb @@ -277,4 +277,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/docs/examples/002-access-grid-info.ipynb b/docs/examples/002-access-grid-info.ipynb index d7b1105d2..ed274d352 100644 --- a/docs/examples/002-access-grid-info.ipynb +++ b/docs/examples/002-access-grid-info.ipynb @@ -326,4 +326,4 @@ }, "nbformat": 4, "nbformat_minor": 0 -} \ No newline at end of file +} diff --git a/docs/examples/003-area-calc.ipynb b/docs/examples/003-area-calc.ipynb index b9faff682..80005d181 100644 --- a/docs/examples/003-area-calc.ipynb +++ b/docs/examples/003-area-calc.ipynb @@ -586,4 +586,4 @@ }, "nbformat": 4, "nbformat_minor": 0 -} \ No newline at end of file +} From 476198f86da99c42b79fa7a95e81e33e098ca914 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Mon, 22 May 2023 14:18:26 -0700 Subject: [PATCH 20/59] Done multiprecision node_xyz_to_lonlat_rad --- test/test_helpers.py | 18 ++++++++++++++ uxarray/helpers.py | 56 +++++++++++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/test/test_helpers.py b/test/test_helpers.py index 7e58f6506..d14bce292 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -114,6 +114,9 @@ def test_normalize_in_place(self): abs = gmpy2.mul(gmpy2.reldiff(mpfr('1.0'),sum_of_squares), mpfr('1.0')) self.assertAlmostEqual(abs, 0,places=19) + # Reset global precision to default + set_global_precision() + def test_node_xyz_to_lonlat_rad(self): [x, y, z] = ux.helpers.normalize_in_place([ @@ -127,6 +130,21 @@ def test_node_xyz_to_lonlat_rad(self): self.assertLessEqual(np.absolute(new_y - y), err_tolerance) self.assertLessEqual(np.absolute(new_z - z), err_tolerance) + # Multiprecision test for places=19 + precision = decimal_digits_to_precision_bits(19) + set_global_precision(precision) + # Assign 1 at the 21th decimal place, which is beyond the precision of 19 decimal places + [x_mpfr, y_mpfr, z_mpfr] = convert_to_multiprecision(np.array(['1.000000000000000000001', '0.000000000000000000001', '0.000000000000000000001']), precision=precision) + [lon_mpfr, lat_mpfr] = ux.helpers.node_xyz_to_lonlat_rad([x_mpfr, y_mpfr, z_mpfr]) + self.assertAlmostEqual(lon_mpfr, 0, places=19) + self.assertAlmostEqual(lat_mpfr, 0, places=19) + + # Remove 1 at the 21th decimal place, and the total digit place are 19. the results should be perfectly equal to [0,0] + [x_mpfr, y_mpfr, z_mpfr] = convert_to_multiprecision(np.array(['1.0000000000000000000', '0.0000000000000000000', '0.0000000000000000000']), precision=precision) + [lon_mpfr, lat_mpfr] = ux.helpers.node_xyz_to_lonlat_rad([x_mpfr, y_mpfr, z_mpfr]) + self.assertEqual(lon_mpfr, 0,) + self.assertEqual(lat_mpfr, 0) + def test_node_latlon_rad_to_xyz(self): [lon, lat] = [ random.uniform(0, 2 * np.pi), diff --git a/uxarray/helpers.py b/uxarray/helpers.py index d26e09a22..e5b1360d9 100644 --- a/uxarray/helpers.py +++ b/uxarray/helpers.py @@ -502,12 +502,12 @@ def node_xyz_to_lonlat_rad(node_coord): Parameters ---------- - node_coord: float list + node_coord: list, python `float` or `gmpy2.mpfr` 3D Cartesian Coordinates [x, y, z] of the node Returns ---------- - float list + node_coord_rad: list, python `float` or `gmpy2.mpfr` the result array of longitude and latitude in radian [longitude_rad, latitude_rad] Raises @@ -517,26 +517,41 @@ def node_xyz_to_lonlat_rad(node_coord): """ if len(node_coord) != 3: raise RuntimeError("Input array should have a length of 3: [x, y, z]") - reference_tolerance = 1.0e-12 - [dx, dy, dz] = normalize_in_place(node_coord) - dx /= np.absolute(dx * dx + dy * dy + dz * dz) - dy /= np.absolute(dx * dx + dy * dy + dz * dz) - dz /= np.absolute(dx * dx + dy * dy + dz * dz) - - if np.absolute(dz) < (1.0 - reference_tolerance): - d_lon_rad = math.atan2(dy, dx) - d_lat_rad = np.arcsin(dz) - - if d_lon_rad < 0.0: - d_lon_rad += 2.0 * np.pi - elif dz > 0.0: - d_lon_rad = 0.0 - d_lat_rad = 0.5 * np.pi + if np.any(np.vectorize(lambda x: isinstance(x, (gmpy2.mpfr, gmpy2.mpz)))(node_coord)): + [dx, dy, dz] = normalize_in_place(node_coord) + # The precision is set by the gmpy2.context through the set_global_precision() function + if gmpy2.cmp_abs(dz, mpfr('1.0')): + d_lon_rad = gmpy2.atan2(dy, dx) + d_lat_rad = gmpy2.asin(dz) + + if gmpy2.cmp(d_lon_rad, mpfr('0.0')) < 0: + d_lon_rad += gmpy2.mul(mpfr('2.0'), gmpy2.const_pi()) + elif gmpy2.cmp(dz, mpfr('0.0')) > 0: + d_lon_rad = mpfr('0.0') + d_lat_rad = gmpy2.mul(mpfr('0.5'), gmpy2.const_pi()) + else: + d_lon_rad = mpfr('0.0') + d_lat_rad = gmpy2.mul(mpfr('-0.5'), gmpy2.const_pi()) + + return [d_lon_rad, d_lat_rad] else: - d_lon_rad = 0.0 - d_lat_rad = -0.5 * np.pi + reference_tolerance = 1.0e-12 + [dx, dy, dz] = normalize_in_place(node_coord) + + if np.absolute(dz) < (1.0 - reference_tolerance): + d_lon_rad = math.atan2(dy, dx) + d_lat_rad = np.arcsin(dz) + + if d_lon_rad < 0.0: + d_lon_rad += 2.0 * np.pi + elif dz > 0.0: + d_lon_rad = 0.0 + d_lat_rad = 0.5 * np.pi + else: + d_lon_rad = 0.0 + d_lat_rad = -0.5 * np.pi - return [d_lon_rad, d_lat_rad] + return [d_lon_rad, d_lat_rad] def normalize_in_place(node): """Helper function to project an arbitrary node in 3D coordinates [x, y, z] @@ -563,7 +578,6 @@ def normalize_in_place(node): if np.any(np.vectorize(lambda x: isinstance(x, (gmpy2.mpfr, gmpy2.mpz)))(node)): # Convert the node to mpmath array - squres = [gmpy2.square(value) for value in node] norm = gmpy2.sqrt(gmpy2.fsum([gmpy2.square(value) for value in node])) # Divide each element of the node by the norm using gmpy2 From fca07008425d6446d0d22cd4d31782d284f73e99 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Mon, 22 May 2023 14:49:51 -0700 Subject: [PATCH 21/59] Done multiprecision node_latlon_rad_to_xyz --- test/test_helpers.py | 50 ++++++++++++++++++++++++++++++++++++++++++-- uxarray/helpers.py | 16 ++++++++++---- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/test/test_helpers.py b/test/test_helpers.py index d14bce292..ec315228e 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -142,8 +142,11 @@ def test_node_xyz_to_lonlat_rad(self): # Remove 1 at the 21th decimal place, and the total digit place are 19. the results should be perfectly equal to [0,0] [x_mpfr, y_mpfr, z_mpfr] = convert_to_multiprecision(np.array(['1.0000000000000000000', '0.0000000000000000000', '0.0000000000000000000']), precision=precision) [lon_mpfr, lat_mpfr] = ux.helpers.node_xyz_to_lonlat_rad([x_mpfr, y_mpfr, z_mpfr]) - self.assertEqual(lon_mpfr, 0,) - self.assertEqual(lat_mpfr, 0) + self.assertTrue(gmpy2.cmp(lon_mpfr, mpfr('0')) == 0) + self.assertTrue(gmpy2.cmp(lat_mpfr, mpfr('0')) == 0) + + # Reset global precision to default + set_global_precision() def test_node_latlon_rad_to_xyz(self): [lon, lat] = [ @@ -155,6 +158,49 @@ def test_node_latlon_rad_to_xyz(self): self.assertLessEqual(np.absolute(new_lon - lon), err_tolerance) self.assertLessEqual(np.absolute(new_lat - lat), err_tolerance) + # Multiprecision test for places=19 + precision = decimal_digits_to_precision_bits(19) + set_global_precision(precision) + # Assign 1 at the 21th decimal place, which is beyond the precision of 19 decimal places + [lon_mpfr, lat_mpfr] = convert_to_multiprecision(np.array(['0.000000000000000000001', '0.000000000000000000001']), precision=precision) + [x_mpfr, y_mpfr, z_mpfr] = ux.helpers.node_lonlat_rad_to_xyz([lon_mpfr, lat_mpfr]) + self.assertAlmostEqual(x_mpfr, 1, places=19) + self.assertAlmostEqual(y_mpfr, 0, places=19) + self.assertAlmostEqual(z_mpfr, 0, places=19) + + # Remove 1 at the 21th decimal place, and the total digit place are 19. the results should be perfectly equal to [1,0,0] + [lon_mpfr, lat_mpfr] = convert_to_multiprecision(np.array(['0.0000000000000000000', '0.0000000000000000000']), precision=precision) + [x_mpfr, y_mpfr, z_mpfr] = ux.helpers.node_lonlat_rad_to_xyz([lon_mpfr, lat_mpfr]) + self.assertTrue(gmpy2.cmp(x_mpfr, mpfr('1')) == 0) + self.assertTrue(gmpy2.cmp(y_mpfr, mpfr('0')) == 0) + self.assertTrue(gmpy2.cmp(z_mpfr, mpfr('0')) == 0) + + # Reset global precision to default + set_global_precision() + + def test_precise_coordinates_conversion(self): + # Multiprecision test for places=19, And we set the global precision places to 20 + # Repeat the conversion between latitude and longitude and xyz for 1000 times + # And see if the results are the same + precision = decimal_digits_to_precision_bits(20) + set_global_precision(precision) + + # The initial coordinates + [init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')]) + new_x = init_x + new_y = init_y + new_z = init_z + for iter in range(1000): + [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) + [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat]) + self.assertAlmostEqual(new_x, init_x, places=19) + self.assertAlmostEqual(new_y, init_y, places=19) + self.assertAlmostEqual(new_z, init_z, places=19) + + + + + class TestConstants(TestCase): # DTYPE as set in constants.py diff --git a/uxarray/helpers.py b/uxarray/helpers.py index e5b1360d9..f790f2f93 100644 --- a/uxarray/helpers.py +++ b/uxarray/helpers.py @@ -468,7 +468,6 @@ def grid_center_lat_lon(ds): return center_lat, center_lon -@njit def node_lonlat_rad_to_xyz(node_coord): """Helper function to Convert the node coordinate from 2D longitude/latitude to normalized 3D xyz. @@ -491,9 +490,18 @@ def node_lonlat_rad_to_xyz(node_coord): if len(node_coord) != 2: raise RuntimeError( "Input array should have a length of 2: [longitude, latitude]") - lon = node_coord[0] - lat = node_coord[1] - return [np.cos(lon) * np.cos(lat), np.sin(lon) * np.cos(lat), np.sin(lat)] + if np.any(np.vectorize(lambda x: isinstance(x, (gmpy2.mpfr, gmpy2.mpz)))(node_coord)): + lon = node_coord[0] + lat = node_coord[1] + return [ + gmpy2.mul(gmpy2.cos(lon), gmpy2.cos(lat)), + gmpy2.mul(gmpy2.sin(lon), gmpy2.cos(lat)), + gmpy2.sin(lat) + ] + else: + lon = node_coord[0] + lat = node_coord[1] + return [np.cos(lon) * np.cos(lat), np.sin(lon) * np.cos(lat), np.sin(lat)] def node_xyz_to_lonlat_rad(node_coord): From 21432fcbf53249b091d925fb53a89287ce3c919f Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Mon, 22 May 2023 16:57:10 -0700 Subject: [PATCH 22/59] Done multiprecision coordinates population --- test/test_grid.py | 65 ++++++++++++++++++++++++++++---------------- test/test_helpers.py | 16 +++++++++-- uxarray/grid.py | 56 ++++++++++++++++++++++++++------------ uxarray/helpers.py | 4 +-- 4 files changed, 94 insertions(+), 47 deletions(-) diff --git a/test/test_grid.py b/test/test_grid.py index 54a380f11..1643eac9f 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -515,7 +515,7 @@ def test_populate_cartesian_xyz_coord(self): ] verts_degree = np.stack((lon_deg, lat_deg), axis=1) - vgrid = ux.Grid([verts_degree], islatlon=False) + vgrid = ux.Grid([verts_degree], islatlon=True, vertices=True) vgrid._populate_cartesian_xyz_coord() for i in range(0, vgrid.nMesh2_node): nt.assert_almost_equal(vgrid.ds["Mesh2_node_cart_x"].values[i], @@ -528,6 +528,46 @@ def test_populate_cartesian_xyz_coord(self): cart_z[i], decimal=12) + lon_deg = [ + '45.0001052295749', '45.0001052295749', '314.9998947704251', + '314.9998947704251' + ] + lat_deg = [ + '35.2655522903022', '-35.2655522903022', '35.2655522903022', '-35.2655522903022' + ] + + cart_x = [ + '0.577340924821405', '0.577340924821405', '0.577340924821405', '0.577340924821405' + ] + + cart_y = [ + '0.577343045516932', '0.577343045516932', '-0.577343045516932', '-0.577343045516932' + ] + + cart_z = [ + '0.577366836872017', '-0.577366836872017', '0.577366836872017', '-0.577366836872017' + ] + + verts_degree = np.stack((lon_deg, lat_deg), axis=1) + vgrid = ux.Grid([verts_degree], multi_precision=True, precision=64, islatlon=True, vertices=True) + vgrid._populate_cartesian_xyz_coord() + ans_x = vgrid.ds["Mesh2_node_cart_x"].values + ans_y = vgrid.ds["Mesh2_node_cart_y"].values + ans_z = vgrid.ds["Mesh2_node_cart_z"].values + [test_x, test_y, test_z] = ux.helpers.node_lonlat_rad_to_xyz([gmpy2.radians(mpfr('45.0001052295749')), gmpy2.radians(mpfr('35.2655522903022'))]) + [real_x, real_y, real_z] = ux.helpers.node_lonlat_rad_to_xyz([np.deg2rad(45.0001052295749), np.deg2rad(35.2655522903022)]) + pass + for i in range(0, vgrid.nMesh2_node): + nt.assert_almost_equal(vgrid.ds["Mesh2_node_cart_x"].values[i], + mpfr(cart_x[i]), + decimal=14) + nt.assert_almost_equal(vgrid.ds["Mesh2_node_cart_y"].values[i], + mpfr(cart_y[i]), + decimal=14) + nt.assert_almost_equal(vgrid.ds["Mesh2_node_cart_z"].values[i], + mpfr(cart_z[i]), + decimal=14) + def test_populate_lonlat_coord(self): # The following testcases are generated through the matlab cart2sph/sph2cart functions # These points correspond to the 4 vertexes on a cube. @@ -756,29 +796,6 @@ def test_build_face_edges_connectivity(self): np.array_equal(reverted_mesh2_edge_nodes[i], original_face_nodes_connectivity[i])) - def test_build_face_edges_connectivity_mpas(self): - xr_ds = xr.open_dataset(self.mpas_filepath) - tgrid = ux.Grid(xr_ds) - - mesh2_face_nodes = tgrid.ds["Mesh2_face_nodes"] - - tgrid._build_face_edges_connectivity() - mesh2_face_edges = tgrid.ds.Mesh2_face_edges - mesh2_edge_nodes = tgrid.ds.Mesh2_edge_nodes - - # Assert if the mesh2_face_edges sizes are correct. - self.assertEqual(mesh2_face_edges.sizes["nMesh2_face"], - mesh2_face_nodes.sizes["nMesh2_face"]) - self.assertEqual(mesh2_face_edges.sizes["nMaxMesh2_face_edges"], - mesh2_face_nodes.sizes["nMaxMesh2_face_nodes"]) - - # Assert if the mesh2_edge_nodes sizes are correct. - # Euler formular for determining the edge numbers: n_face = n_edges - n_nodes + 2 - num_edges = mesh2_face_edges.sizes["nMesh2_face"] + tgrid.ds[ - "Mesh2_node_x"].sizes["nMesh2_node"] - 2 - size = mesh2_edge_nodes.sizes["nMesh2_edge"] - self.assertEqual(mesh2_edge_nodes.sizes["nMesh2_edge"], num_edges) - def test_build_face_edges_connectivity_fillvalues(self): f0_deg = [[120, -20], [130, -10], [120, 0], [105, 0], [95, -10], [105, -20]] diff --git a/test/test_helpers.py b/test/test_helpers.py index ec315228e..94f32dc30 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -197,10 +197,20 @@ def test_precise_coordinates_conversion(self): self.assertAlmostEqual(new_y, init_y, places=19) self.assertAlmostEqual(new_z, init_z, places=19) + # Test for the longitude and latitude conversion + # The initial coordinates + [init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')] + # Reset global precision to default + new_lat = init_lat + new_lon = init_lon + for iter in range(1000): + [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat]) + [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) + self.assertAlmostEqual(new_lon, init_lon, places=19) + self.assertAlmostEqual(new_lat, init_lat, places=19) - - - + # Reset global precision to default + set_global_precision() class TestConstants(TestCase): # DTYPE as set in constants.py diff --git a/uxarray/grid.py b/uxarray/grid.py index b5f3ad027..d184c4634 100644 --- a/uxarray/grid.py +++ b/uxarray/grid.py @@ -76,6 +76,10 @@ def __init__(self, # initialize face_area variable self._face_areas = None + # initialize the multi-precision flag + self._multi_precision = multi_precision + self._precision = precision + # TODO: fix when adding/exercising gridspec # unpack kwargs @@ -90,18 +94,27 @@ def __init__(self, # check if initializing from verts: if isinstance(dataset, (list, tuple, np.ndarray)): dataset = np.asarray(dataset) + # Pre-process dataset if the multi-precision flag is set + if self._multi_precision: + # If the input are floats + if dataset.dtype == np.float64 or dataset.dtype == np.float or dataset.dtype == np.float32: + dataset = convert_to_multiprecision(dataset, str_mode=False, precision=self._precision) + # If the input are strings + elif np.all([np.issubdtype(type(element), np.str_)for element in dataset.ravel()]): + dataset = convert_to_multiprecision(dataset, str_mode=True, precision=self._precision) + else: + # If the input are not floats or strings or gmpy2.mpfr/mpz, raise an error + if ~np.any(np.vectorize(lambda x: isinstance(x, (gmpy2.mpfr, gmpy2.mpz)))(dataset.ravel())): + raise ValueError( + 'The input array should be either floats, strings, or gmpy2.mpfr/mpz') # grid with multiple faces if dataset.ndim == 3: - self.__from_vert__(dataset, - multi_precision=multi_precision, - precision=precision) + self.__from_vert__(dataset) self.source_grid = "From vertices" # grid with a single face elif dataset.ndim == 2: dataset = np.array([dataset]) - self.__from_vert__(dataset, - multi_precision=multi_precision, - precision=precision) + self.__from_vert__(dataset) self.source_grid = "From vertices" else: raise RuntimeError( @@ -181,9 +194,7 @@ def __init_grid_var_attrs__(self) -> None: setattr(self, key, len(self.ds[value])) def __from_vert__(self, - dataset, - multi_precision=False, - precision=FLOAT_PRECISION_BITS): + dataset): """Create a grid with faces constructed from vertices specified by the given argument. @@ -193,6 +204,7 @@ def __from_vert__(self, Input vertex coordinates that form our face(s) """ self.ds = xr.Dataset() + self.ds["Mesh2"] = xr.DataArray( attrs={ "cf_role": "mesh_topology", @@ -223,7 +235,7 @@ def __from_vert__(self, z_coord = x_coord * 0.0 # Identify unique vertices and their indices - if multi_precision: + if self._multi_precision: # Check if the input_array is in th mpfr type try: # Flatten the input_array_mpfr to a 1D array so that we can check the type of each element @@ -232,12 +244,12 @@ def __from_vert__(self, if type(input_array_mpfr_copy[i]) != gmpy2.mpfr and type( input_array_mpfr_copy[i]) != gmpy2.mpz: dataset = convert_to_multiprecision(dataset, - precision=precision) + precision=self._precision) except Exception as e: raise e unique_verts, indices = unique_coordinates_multiprecision( - dataset.reshape(-1, dataset.shape[-1]), precision=precision) + dataset.reshape(-1, dataset.shape[-1]), precision=self._precision) else: unique_verts, indices = np.unique(dataset.reshape( @@ -246,7 +258,7 @@ def __from_vert__(self, return_inverse=True) # Nodes index that contain a fill value - if multi_precision: + if self._multi_precision: # Perform element-wise comparison using gmpy.cmp() fill_value_mask = np.logical_or( np.array([ @@ -299,7 +311,7 @@ def __from_vert__(self, unique_verts = np.delete(unique_verts, false_indices, axis=0) # Update indices accordingly - if multi_precision: + if self._multi_precision: for i, idx in enumerate(false_indices): indices[indices == idx] = INT_FILL_VALUE_MPZ for j in range(indices.size): @@ -332,7 +344,7 @@ def __from_vert__(self, # Create connectivity array using indices of unique vertices connectivity = indices.reshape(dataset.shape[:-1]) - if multi_precision: + if self._multi_precision: self.ds["Mesh2_face_nodes"] = xr.DataArray( data=xr.DataArray(connectivity).astype(INT_DTYPE), dims=["nMesh2_face", "nMaxMesh2_face_nodes"], @@ -693,8 +705,12 @@ def _populate_cartesian_xyz_coord(self): return # check for units and create Mesh2_node_cart_x/y/z set to self.ds - nodes_lon_rad = np.deg2rad(self.Mesh2_node_x.values) - nodes_lat_rad = np.deg2rad(self.Mesh2_node_y.values) + if self._multi_precision: + nodes_lon_rad = np.array([gmpy2.radians(x) for x in self.Mesh2_node_x.values]) + nodes_lat_rad = np.array([gmpy2.radians(y) for y in self.Mesh2_node_y.values]) + else: + nodes_lon_rad = np.deg2rad(self.Mesh2_node_x.values) + nodes_lat_rad = np.deg2rad(self.Mesh2_node_y.values) nodes_rad = np.stack((nodes_lon_rad, nodes_lat_rad), axis=1) nodes_cart = np.asarray( list(map(node_lonlat_rad_to_xyz, list(nodes_rad)))) @@ -777,7 +793,11 @@ def _populate_lonlat_coord(self): self.ds["Mesh2_node_z"].values), axis=1).tolist() nodes_rad = list(map(node_xyz_to_lonlat_rad, nodes_cart)) - nodes_degree = np.rad2deg(nodes_rad) + + if self._multi_precision: + nodes_degree = np.array([[gmpy2.degrees(x), gmpy2.degrees(y)] for x, y in nodes_rad]) + else: + nodes_degree = np.rad2deg(nodes_rad) self.ds["Mesh2_node_x"] = xr.DataArray( data=nodes_degree[:, 0], dims=["nMesh2_node"], diff --git a/uxarray/helpers.py b/uxarray/helpers.py index f790f2f93..d7ec0dc58 100644 --- a/uxarray/helpers.py +++ b/uxarray/helpers.py @@ -474,12 +474,12 @@ def node_lonlat_rad_to_xyz(node_coord): Parameters ---------- - node: float list + node: list, python `float` or `gmpy2.mpfr` 2D coordinates[longitude, latitude] in radiance Returns ---------- - float list + node_cartesian: list, python `float` or `gmpy2.mpfr` the result array of the unit 3D coordinates [x, y, z] vector where :math:`x^2 + y^2 + z^2 = 1` Raises From c1c6af382b91321271ebbdaee6ab43b939ff9c63 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Tue, 23 May 2023 12:40:13 -0700 Subject: [PATCH 23/59] Remove the @njit decorator(not compatible with the gmpy2) --- test/test_grid.py | 5 ----- uxarray/helpers.py | 13 +++++++------ 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/test/test_grid.py b/test/test_grid.py index 1643eac9f..0bfc4c9c7 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -551,11 +551,6 @@ def test_populate_cartesian_xyz_coord(self): verts_degree = np.stack((lon_deg, lat_deg), axis=1) vgrid = ux.Grid([verts_degree], multi_precision=True, precision=64, islatlon=True, vertices=True) vgrid._populate_cartesian_xyz_coord() - ans_x = vgrid.ds["Mesh2_node_cart_x"].values - ans_y = vgrid.ds["Mesh2_node_cart_y"].values - ans_z = vgrid.ds["Mesh2_node_cart_z"].values - [test_x, test_y, test_z] = ux.helpers.node_lonlat_rad_to_xyz([gmpy2.radians(mpfr('45.0001052295749')), gmpy2.radians(mpfr('35.2655522903022'))]) - [real_x, real_y, real_z] = ux.helpers.node_lonlat_rad_to_xyz([np.deg2rad(45.0001052295749), np.deg2rad(35.2655522903022)]) pass for i in range(0, vgrid.nMesh2_node): nt.assert_almost_equal(vgrid.ds["Mesh2_node_cart_x"].values[i], diff --git a/uxarray/helpers.py b/uxarray/helpers.py index d7ec0dc58..0341a6d2f 100644 --- a/uxarray/helpers.py +++ b/uxarray/helpers.py @@ -98,7 +98,7 @@ def parse_grid_type(dataset): # Calculate the area of all faces. -@njit +# @njit def calculate_face_area(x, y, z, @@ -185,7 +185,7 @@ def calculate_face_area(x, return area -@njit +# @njit def get_all_face_area_from_coords(x, y, z, @@ -255,7 +255,7 @@ def get_all_face_area_from_coords(x, return area -@njit +# @njit def calculate_spherical_triangle_jacobian(node1, node2, node3, dA, dB): """Calculate Jacobian of a spherical triangle. This is a helper function for calculating face area. @@ -331,7 +331,7 @@ def calculate_spherical_triangle_jacobian(node1, node2, node3, dA, dB): return dJacobian -@njit +# @njit def calculate_spherical_triangle_jacobian_barycentric(node1, node2, node3, dA, dB): """Calculate Jacobian of a spherical triangle. This is a helper function @@ -460,7 +460,7 @@ def grid_center_lat_lon(ds): z = np.sum(np.sin(rad_corner_lat), axis=1) / nodes_per_face center_lon = np.rad2deg(np.arctan2(y, x)) - center_lat = np.rad2deg(np.arctan2(z, np.sqrt(x**2 + y**2))) + center_lat = np.rad2deg(np.arctan2(z, np.sqrt(x ** 2 + y ** 2))) # Make negative lons positive center_lon[center_lon < 0] += 360 @@ -561,6 +561,7 @@ def node_xyz_to_lonlat_rad(node_coord): return [d_lon_rad, d_lat_rad] + def normalize_in_place(node): """Helper function to project an arbitrary node in 3D coordinates [x, y, z] on the unit sphere. It uses the `np.linalg.norm` internally to calculate @@ -696,7 +697,7 @@ def close_face_nodes(Mesh2_face_nodes, nMesh2_face, nMaxMesh2_face_nodes): # 2d to 1d index for np.put() first_fv_idx_1d = first_fv_idx_2d + ( - (nMaxMesh2_face_nodes + 1) * np.arange(0, nMesh2_face)) + (nMaxMesh2_face_nodes + 1) * np.arange(0, nMesh2_face)) # column of first node values first_node_value = Mesh2_face_nodes[:, 0].copy() From 5a214cdf391f083322345c7d9b195125ae8a7709 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Tue, 23 May 2023 14:02:24 -0700 Subject: [PATCH 24/59] Update user exmaples --- docs/examples/004-multiprecision-usage.ipynb | 94 +++++++++++++++++++- test/test_grid.py | 59 ++++++++++-- test/test_helpers.py | 72 +++++++++++---- uxarray/grid.py | 37 +++++--- uxarray/helpers.py | 22 +++-- uxarray/multi_precision_helpers.py | 4 +- 6 files changed, 239 insertions(+), 49 deletions(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index 820962a1c..e9b9fa995 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -32,7 +32,7 @@ { "cell_type": "markdown", "source": [ - "# Generate the input coordinates on the unit sphere using the gmpy2 mpfr type.\n", + "# Using GMPY2.mpfr type for Multiprecision Arithmetic\n", "The distance between any two nodes is less than 1e-17 (the precision of the nodes is 60 bits, which is larger than the the python `float` type precision of 53 bits)" ], "metadata": { @@ -169,7 +169,10 @@ }, { "cell_type": "markdown", - "source": [], + "source": [ + "# Using python string type for Multiprecision Arithmetic\n", + "You can also input the coordinates as string. The string will be converted to the mpfr type." + ], "metadata": { "collapsed": false, "pycharm": { @@ -201,7 +204,7 @@ { "cell_type": "markdown", "source": [ - "# Check the Grid Information" + "## Check the Grid Information" ], "metadata": { "collapsed": false, @@ -224,6 +227,91 @@ "name": "#%%\n" } } + }, + { + "cell_type": "markdown", + "source": [ + "# Precise Conversion between Spherical and Cartesian Coordinates\n", + "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Test for the longitude and latitude conversion helper functions with multiprecision\n", + "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", + "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import uxarray as ux\n", + "import uxarray.helpers as helpers\n", + "import uxarray.multi_precision_helpers as mp_helpers\n", + "\n", + "\n", + "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", + "# Multiprecision test for places=19, And we set the global precision places to 20\n", + "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", + "# And see if the results are the same as the initial coordinates\n", + "\n", + "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", + "\n", + "# Set the global precision of the mpfr numbers.\n", + "# Important Note:\n", + "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", + "# in the code.\n", + "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", + "# another call to this function.\n", + "mp_helpers.set_global_precision(precision)\n", + "\n", + "# The initial coordinates\n", + "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", + "new_x = init_x\n", + "new_y = init_y\n", + "new_z = init_z\n", + "for iter in range(1000):\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " assert new_x == init_x\n", + " assert new_y == init_y\n", + "\n", + "# Test for the longitude and latitude conversion\n", + "# The initial coordinates\n", + "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", + "# Reset global precision to default\n", + "new_lat = init_lat\n", + "new_lon = init_lon\n", + "for iter in range(1000):\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " assert new_lon == init_lon\n", + " assert new_lat == init_lat\n", + "\n", + "# Reset global precision to default\n", + "mp_helpers.set_global_precision()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } } ], "metadata": { diff --git a/test/test_grid.py b/test/test_grid.py index 0bfc4c9c7..3eb974522 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -533,25 +533,32 @@ def test_populate_cartesian_xyz_coord(self): '314.9998947704251' ] lat_deg = [ - '35.2655522903022', '-35.2655522903022', '35.2655522903022', '-35.2655522903022' + '35.2655522903022', '-35.2655522903022', '35.2655522903022', + '-35.2655522903022' ] cart_x = [ - '0.577340924821405', '0.577340924821405', '0.577340924821405', '0.577340924821405' + '0.577340924821405', '0.577340924821405', '0.577340924821405', + '0.577340924821405' ] cart_y = [ - '0.577343045516932', '0.577343045516932', '-0.577343045516932', '-0.577343045516932' + '0.577343045516932', '0.577343045516932', '-0.577343045516932', + '-0.577343045516932' ] cart_z = [ - '0.577366836872017', '-0.577366836872017', '0.577366836872017', '-0.577366836872017' + '0.577366836872017', '-0.577366836872017', '0.577366836872017', + '-0.577366836872017' ] verts_degree = np.stack((lon_deg, lat_deg), axis=1) - vgrid = ux.Grid([verts_degree], multi_precision=True, precision=64, islatlon=True, vertices=True) + vgrid = ux.Grid([verts_degree], + multi_precision=True, + precision=64, + islatlon=True, + vertices=True) vgrid._populate_cartesian_xyz_coord() - pass for i in range(0, vgrid.nMesh2_node): nt.assert_almost_equal(vgrid.ds["Mesh2_node_cart_x"].values[i], mpfr(cart_x[i]), @@ -601,6 +608,46 @@ def test_populate_lonlat_coord(self): lat_deg[i], decimal=12) + lon_deg = [ + '45.0001052295749', '45.0001052295749', '314.9998947704251', + '314.9998947704251' + ] + lat_deg = [ + '35.2655522903022', '-35.2655522903022', '35.2655522903022', + '-35.2655522903022' + ] + + cart_x = [ + '0.577340924821405', '0.577340924821405', '0.577340924821405', + '0.577340924821405' + ] + + cart_y = [ + '0.577343045516932', '0.577343045516932', '-0.577343045516932', + '-0.577343045516932' + ] + + cart_z = [ + '0.577366836872017', '-0.577366836872017', '0.577366836872017', + '-0.577366836872017' + ] + + verts_cart = np.stack((cart_x, cart_y, cart_z), axis=1) + vgrid = ux.Grid([verts_cart], + multi_precision=True, + precision=64, + islatlon=False) + vgrid._populate_lonlat_coord() + # The connectivity in `__from_vert__()` will be formed in a reverse order + lon_deg, lat_deg = zip(*reversed(list(zip(lon_deg, lat_deg)))) + for i in range(0, vgrid.nMesh2_node): + nt.assert_almost_equal(vgrid.ds["Mesh2_node_x"].values[i], + mpfr(lon_deg[i]), + decimal=14) + nt.assert_almost_equal(vgrid.ds["Mesh2_node_y"].values[i], + mpfr(lat_deg[i]), + decimal=14) + class TestConnectivity(TestCase): mpas_filepath = current_path / "meshfiles" / "mpas" / "QU" / "mesh.QU.1920km.151026.nc" diff --git a/test/test_helpers.py b/test/test_helpers.py index 94f32dc30..9b60288bb 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -107,17 +107,22 @@ def test_normalize_in_place(self): # Multiprecision test for places=19 precision = decimal_digits_to_precision_bits(19) set_global_precision(precision) - [x_mpfr, y_mpfr, z_mpfr] = convert_to_multiprecision(np.array(['1.0000000000000000001', '0.0000000000000000009', '0.0000000000000000001']), precision=precision) + [x_mpfr, y_mpfr, + z_mpfr] = convert_to_multiprecision(np.array([ + '1.0000000000000000001', '0.0000000000000000009', + '0.0000000000000000001' + ]), + precision=precision) normalized = ux.helpers.normalize_in_place([x_mpfr, y_mpfr, z_mpfr]) # Calculate the sum of squares using gmpy2.fsum() - sum_of_squares = gmpy2.fsum([gmpy2.square(value) for value in normalized]) - abs = gmpy2.mul(gmpy2.reldiff(mpfr('1.0'),sum_of_squares), mpfr('1.0')) - self.assertAlmostEqual(abs, 0,places=19) + sum_of_squares = gmpy2.fsum( + [gmpy2.square(value) for value in normalized]) + abs = gmpy2.mul(gmpy2.reldiff(mpfr('1.0'), sum_of_squares), mpfr('1.0')) + self.assertAlmostEqual(abs, 0, places=19) # Reset global precision to default set_global_precision() - def test_node_xyz_to_lonlat_rad(self): [x, y, z] = ux.helpers.normalize_in_place([ random.uniform(-1, 1), @@ -134,14 +139,26 @@ def test_node_xyz_to_lonlat_rad(self): precision = decimal_digits_to_precision_bits(19) set_global_precision(precision) # Assign 1 at the 21th decimal place, which is beyond the precision of 19 decimal places - [x_mpfr, y_mpfr, z_mpfr] = convert_to_multiprecision(np.array(['1.000000000000000000001', '0.000000000000000000001', '0.000000000000000000001']), precision=precision) - [lon_mpfr, lat_mpfr] = ux.helpers.node_xyz_to_lonlat_rad([x_mpfr, y_mpfr, z_mpfr]) + [x_mpfr, y_mpfr, + z_mpfr] = convert_to_multiprecision(np.array([ + '1.000000000000000000001', '0.000000000000000000001', + '0.000000000000000000001' + ]), + precision=precision) + [lon_mpfr, + lat_mpfr] = ux.helpers.node_xyz_to_lonlat_rad([x_mpfr, y_mpfr, z_mpfr]) self.assertAlmostEqual(lon_mpfr, 0, places=19) self.assertAlmostEqual(lat_mpfr, 0, places=19) # Remove 1 at the 21th decimal place, and the total digit place are 19. the results should be perfectly equal to [0,0] - [x_mpfr, y_mpfr, z_mpfr] = convert_to_multiprecision(np.array(['1.0000000000000000000', '0.0000000000000000000', '0.0000000000000000000']), precision=precision) - [lon_mpfr, lat_mpfr] = ux.helpers.node_xyz_to_lonlat_rad([x_mpfr, y_mpfr, z_mpfr]) + [x_mpfr, y_mpfr, + z_mpfr] = convert_to_multiprecision(np.array([ + '1.0000000000000000000', '0.0000000000000000000', + '0.0000000000000000000' + ]), + precision=precision) + [lon_mpfr, + lat_mpfr] = ux.helpers.node_xyz_to_lonlat_rad([x_mpfr, y_mpfr, z_mpfr]) self.assertTrue(gmpy2.cmp(lon_mpfr, mpfr('0')) == 0) self.assertTrue(gmpy2.cmp(lat_mpfr, mpfr('0')) == 0) @@ -162,15 +179,21 @@ def test_node_latlon_rad_to_xyz(self): precision = decimal_digits_to_precision_bits(19) set_global_precision(precision) # Assign 1 at the 21th decimal place, which is beyond the precision of 19 decimal places - [lon_mpfr, lat_mpfr] = convert_to_multiprecision(np.array(['0.000000000000000000001', '0.000000000000000000001']), precision=precision) - [x_mpfr, y_mpfr, z_mpfr] = ux.helpers.node_lonlat_rad_to_xyz([lon_mpfr, lat_mpfr]) + [lon_mpfr, lat_mpfr] = convert_to_multiprecision(np.array( + ['0.000000000000000000001', '0.000000000000000000001']), + precision=precision) + [x_mpfr, y_mpfr, + z_mpfr] = ux.helpers.node_lonlat_rad_to_xyz([lon_mpfr, lat_mpfr]) self.assertAlmostEqual(x_mpfr, 1, places=19) self.assertAlmostEqual(y_mpfr, 0, places=19) self.assertAlmostEqual(z_mpfr, 0, places=19) # Remove 1 at the 21th decimal place, and the total digit place are 19. the results should be perfectly equal to [1,0,0] - [lon_mpfr, lat_mpfr] = convert_to_multiprecision(np.array(['0.0000000000000000000', '0.0000000000000000000']), precision=precision) - [x_mpfr, y_mpfr, z_mpfr] = ux.helpers.node_lonlat_rad_to_xyz([lon_mpfr, lat_mpfr]) + [lon_mpfr, lat_mpfr] = convert_to_multiprecision(np.array( + ['0.0000000000000000000', '0.0000000000000000000']), + precision=precision) + [x_mpfr, y_mpfr, + z_mpfr] = ux.helpers.node_lonlat_rad_to_xyz([lon_mpfr, lat_mpfr]) self.assertTrue(gmpy2.cmp(x_mpfr, mpfr('1')) == 0) self.assertTrue(gmpy2.cmp(y_mpfr, mpfr('0')) == 0) self.assertTrue(gmpy2.cmp(z_mpfr, mpfr('0')) == 0) @@ -186,32 +209,43 @@ def test_precise_coordinates_conversion(self): set_global_precision(precision) # The initial coordinates - [init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')]) + [init_x, init_y, init_z] = ux.helpers.normalize_in_place([ + mpfr('0.12345678910111213149'), + mpfr('0.92345678910111213149'), + mpfr('1.72345678910111213149') + ]) new_x = init_x new_y = init_y new_z = init_z for iter in range(1000): - [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) - [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat]) + [new_lon, + new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) + [new_x, new_y, + new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat]) self.assertAlmostEqual(new_x, init_x, places=19) self.assertAlmostEqual(new_y, init_y, places=19) self.assertAlmostEqual(new_z, init_z, places=19) # Test for the longitude and latitude conversion # The initial coordinates - [init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')] + [init_lon, + init_lat] = [mpfr('1.4000332309896247'), + mpfr('1.190289949682531')] # Reset global precision to default new_lat = init_lat new_lon = init_lon for iter in range(1000): - [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat]) - [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) + [new_x, new_y, + new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat]) + [new_lon, + new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) self.assertAlmostEqual(new_lon, init_lon, places=19) self.assertAlmostEqual(new_lat, init_lat, places=19) # Reset global precision to default set_global_precision() + class TestConstants(TestCase): # DTYPE as set in constants.py expected_int_dtype = INT_DTYPE diff --git a/uxarray/grid.py b/uxarray/grid.py index d184c4634..6de9d65c4 100644 --- a/uxarray/grid.py +++ b/uxarray/grid.py @@ -98,15 +98,23 @@ def __init__(self, if self._multi_precision: # If the input are floats if dataset.dtype == np.float64 or dataset.dtype == np.float or dataset.dtype == np.float32: - dataset = convert_to_multiprecision(dataset, str_mode=False, precision=self._precision) + dataset = convert_to_multiprecision( + dataset, str_mode=False, precision=self._precision) # If the input are strings - elif np.all([np.issubdtype(type(element), np.str_)for element in dataset.ravel()]): - dataset = convert_to_multiprecision(dataset, str_mode=True, precision=self._precision) + elif np.all([ + np.issubdtype(type(element), np.str_) + for element in dataset.ravel() + ]): + dataset = convert_to_multiprecision( + dataset, str_mode=True, precision=self._precision) else: # If the input are not floats or strings or gmpy2.mpfr/mpz, raise an error - if ~np.any(np.vectorize(lambda x: isinstance(x, (gmpy2.mpfr, gmpy2.mpz)))(dataset.ravel())): + if ~np.any( + np.vectorize(lambda x: isinstance( + x, (gmpy2.mpfr, gmpy2.mpz)))(dataset.ravel())): raise ValueError( - 'The input array should be either floats, strings, or gmpy2.mpfr/mpz') + 'The input array should be either floats, strings, or gmpy2.mpfr/mpz' + ) # grid with multiple faces if dataset.ndim == 3: self.__from_vert__(dataset) @@ -193,8 +201,7 @@ def __init_grid_var_attrs__(self) -> None: if value in self.ds.dims: setattr(self, key, len(self.ds[value])) - def __from_vert__(self, - dataset): + def __from_vert__(self, dataset): """Create a grid with faces constructed from vertices specified by the given argument. @@ -243,13 +250,14 @@ def __from_vert__(self, for i in range(len(input_array_mpfr_copy)): if type(input_array_mpfr_copy[i]) != gmpy2.mpfr and type( input_array_mpfr_copy[i]) != gmpy2.mpz: - dataset = convert_to_multiprecision(dataset, - precision=self._precision) + dataset = convert_to_multiprecision( + dataset, precision=self._precision) except Exception as e: raise e unique_verts, indices = unique_coordinates_multiprecision( - dataset.reshape(-1, dataset.shape[-1]), precision=self._precision) + dataset.reshape(-1, dataset.shape[-1]), + precision=self._precision) else: unique_verts, indices = np.unique(dataset.reshape( @@ -706,8 +714,10 @@ def _populate_cartesian_xyz_coord(self): # check for units and create Mesh2_node_cart_x/y/z set to self.ds if self._multi_precision: - nodes_lon_rad = np.array([gmpy2.radians(x) for x in self.Mesh2_node_x.values]) - nodes_lat_rad = np.array([gmpy2.radians(y) for y in self.Mesh2_node_y.values]) + nodes_lon_rad = np.array( + [gmpy2.radians(x) for x in self.Mesh2_node_x.values]) + nodes_lat_rad = np.array( + [gmpy2.radians(y) for y in self.Mesh2_node_y.values]) else: nodes_lon_rad = np.deg2rad(self.Mesh2_node_x.values) nodes_lat_rad = np.deg2rad(self.Mesh2_node_y.values) @@ -795,7 +805,8 @@ def _populate_lonlat_coord(self): nodes_rad = list(map(node_xyz_to_lonlat_rad, nodes_cart)) if self._multi_precision: - nodes_degree = np.array([[gmpy2.degrees(x), gmpy2.degrees(y)] for x, y in nodes_rad]) + nodes_degree = np.array( + [[gmpy2.degrees(x), gmpy2.degrees(y)] for x, y in nodes_rad]) else: nodes_degree = np.rad2deg(nodes_rad) self.ds["Mesh2_node_x"] = xr.DataArray( diff --git a/uxarray/helpers.py b/uxarray/helpers.py index 0341a6d2f..b3915ab4c 100644 --- a/uxarray/helpers.py +++ b/uxarray/helpers.py @@ -460,7 +460,7 @@ def grid_center_lat_lon(ds): z = np.sum(np.sin(rad_corner_lat), axis=1) / nodes_per_face center_lon = np.rad2deg(np.arctan2(y, x)) - center_lat = np.rad2deg(np.arctan2(z, np.sqrt(x ** 2 + y ** 2))) + center_lat = np.rad2deg(np.arctan2(z, np.sqrt(x**2 + y**2))) # Make negative lons positive center_lon[center_lon < 0] += 360 @@ -490,7 +490,9 @@ def node_lonlat_rad_to_xyz(node_coord): if len(node_coord) != 2: raise RuntimeError( "Input array should have a length of 2: [longitude, latitude]") - if np.any(np.vectorize(lambda x: isinstance(x, (gmpy2.mpfr, gmpy2.mpz)))(node_coord)): + if np.any( + np.vectorize(lambda x: isinstance(x, (gmpy2.mpfr, gmpy2.mpz)))( + node_coord)): lon = node_coord[0] lat = node_coord[1] return [ @@ -501,7 +503,11 @@ def node_lonlat_rad_to_xyz(node_coord): else: lon = node_coord[0] lat = node_coord[1] - return [np.cos(lon) * np.cos(lat), np.sin(lon) * np.cos(lat), np.sin(lat)] + return [ + np.cos(lon) * np.cos(lat), + np.sin(lon) * np.cos(lat), + np.sin(lat) + ] def node_xyz_to_lonlat_rad(node_coord): @@ -525,7 +531,9 @@ def node_xyz_to_lonlat_rad(node_coord): """ if len(node_coord) != 3: raise RuntimeError("Input array should have a length of 3: [x, y, z]") - if np.any(np.vectorize(lambda x: isinstance(x, (gmpy2.mpfr, gmpy2.mpz)))(node_coord)): + if np.any( + np.vectorize(lambda x: isinstance(x, (gmpy2.mpfr, gmpy2.mpz)))( + node_coord)): [dx, dy, dz] = normalize_in_place(node_coord) # The precision is set by the gmpy2.context through the set_global_precision() function if gmpy2.cmp_abs(dz, mpfr('1.0')): @@ -585,7 +593,9 @@ def normalize_in_place(node): if len(node) != 3: raise RuntimeError("Input array should have a length of 3: [x, y, z]") - if np.any(np.vectorize(lambda x: isinstance(x, (gmpy2.mpfr, gmpy2.mpz)))(node)): + if np.any( + np.vectorize(lambda x: isinstance(x, (gmpy2.mpfr, gmpy2.mpz)))( + node)): # Convert the node to mpmath array norm = gmpy2.sqrt(gmpy2.fsum([gmpy2.square(value) for value in node])) @@ -697,7 +707,7 @@ def close_face_nodes(Mesh2_face_nodes, nMesh2_face, nMaxMesh2_face_nodes): # 2d to 1d index for np.put() first_fv_idx_1d = first_fv_idx_2d + ( - (nMaxMesh2_face_nodes + 1) * np.arange(0, nMesh2_face)) + (nMaxMesh2_face_nodes + 1) * np.arange(0, nMesh2_face)) # column of first node values first_node_value = Mesh2_face_nodes[:, 0].copy() diff --git a/uxarray/multi_precision_helpers.py b/uxarray/multi_precision_helpers.py index 2ea5ff34f..b723805f6 100644 --- a/uxarray/multi_precision_helpers.py +++ b/uxarray/multi_precision_helpers.py @@ -83,8 +83,8 @@ def convert_to_multiprecision(input_array, else: if ~np.all([ - np.issubdtype(type(element), np.str_) - for element in flattened_array + np.issubdtype(type(element), np.str_) + for element in flattened_array ]): raise ValueError( 'The input array should be string when str_mode is True.') From a3ef004ff260386ada8d56f751e4494cf54c8877 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Tue, 23 May 2023 14:04:16 -0700 Subject: [PATCH 25/59] Update user exmaples --- docs/examples/004-multiprecision-usage.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index baf03c8c0..e9b9fa995 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -341,4 +341,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file From f35aaf81f605320e03115be214581086e8e23452 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Tue, 23 May 2023 15:08:33 -0700 Subject: [PATCH 26/59] Fix the error in examples --- docs/examples/004-multiprecision-usage.ipynb | 16 +++++++-------- uxarray/grid.py | 21 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index 32014dec9..f984e615f 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -45,12 +45,6 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n", - "is_executing": true - } - }, "outputs": [], "source": [ "# Generate the nodes on the unit sphere, and the nodes are extremely close to each other: the edge length\n", @@ -77,7 +71,13 @@ " np.array([nodes[0], nodes[3], nodes[1], dumb_nodes], dtype=object),\n", " np.array([nodes[1], nodes[2], nodes[3], nodes[0]], dtype=object)\n", "], dtype=object)" - ] + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "markdown", @@ -253,4 +253,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/uxarray/grid.py b/uxarray/grid.py index ece619029..4f4b7a4d5 100644 --- a/uxarray/grid.py +++ b/uxarray/grid.py @@ -90,6 +90,27 @@ def __init__(self, # check if initializing from verts: if isinstance(dataset, (list, tuple, np.ndarray)): dataset = np.asarray(dataset) + # Pre-process dataset if the multi-precision flag is set + if self._multi_precision: + # If the input are floats + if dataset.dtype == np.float64 or dataset.dtype == np.float or dataset.dtype == np.float32: + dataset = convert_to_multiprecision( + dataset, str_mode=False, precision=self._precision) + # If the input are strings + elif np.all([ + np.issubdtype(type(element), np.str_) + for element in dataset.ravel() + ]): + dataset = convert_to_multiprecision( + dataset, str_mode=True, precision=self._precision) + else: + # If the input are not floats or strings or gmpy2.mpfr/mpz, raise an error + if ~np.any( + np.vectorize(lambda x: isinstance( + x, (gmpy2.mpfr, gmpy2.mpz)))(dataset.ravel())): + raise ValueError( + 'The input array should be either floats, strings, or gmpy2.mpfr/mpz' + ) # grid with multiple faces if dataset.ndim == 3: self.__from_vert__(dataset, From c4873cfaa7ad178485e5fcc0541ec3d942387e03 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Tue, 23 May 2023 15:19:31 -0700 Subject: [PATCH 27/59] Fix API bugs --- docs/examples/004-multiprecision-usage.ipynb | 7 +++---- uxarray/grid.py | 3 +++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index f984e615f..4752b6830 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -14,11 +14,10 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { "pycharm": { - "name": "#%%\n", - "is_executing": true + "name": "#%%\n" } }, "outputs": [], @@ -253,4 +252,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/uxarray/grid.py b/uxarray/grid.py index 4f4b7a4d5..3165015e8 100644 --- a/uxarray/grid.py +++ b/uxarray/grid.py @@ -76,6 +76,9 @@ def __init__(self, # initialize face_area variable self._face_areas = None + # initialize the multi-precision flag + self._multi_precision = multi_precision + self._precision = precision # TODO: fix when adding/exercising gridspec # unpack kwargs From 1608dbf15602c6c5da50127f8cf83b5c1b293958 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Tue, 23 May 2023 20:41:56 -0700 Subject: [PATCH 28/59] replace `np.float` with `float` --- uxarray/grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uxarray/grid.py b/uxarray/grid.py index 3165015e8..cec0f3a85 100644 --- a/uxarray/grid.py +++ b/uxarray/grid.py @@ -96,7 +96,7 @@ def __init__(self, # Pre-process dataset if the multi-precision flag is set if self._multi_precision: # If the input are floats - if dataset.dtype == np.float64 or dataset.dtype == np.float or dataset.dtype == np.float32: + if dataset.dtype == float: dataset = convert_to_multiprecision( dataset, str_mode=False, precision=self._precision) # If the input are strings From 53d42a905b4d92b43f28e57127a1d37f365612e8 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Tue, 23 May 2023 20:47:33 -0700 Subject: [PATCH 29/59] rerun `make html` --- docs/examples/004-multiprecision-usage.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index 4752b6830..ab586e23d 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -252,4 +252,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file From 258f81b91e789eb29804ae040498fcf4ab78aacb Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Tue, 23 May 2023 20:54:53 -0700 Subject: [PATCH 30/59] fix precommit `make html` --- uxarray/grid.py | 1 + 1 file changed, 1 insertion(+) diff --git a/uxarray/grid.py b/uxarray/grid.py index cec0f3a85..8dc364ac0 100644 --- a/uxarray/grid.py +++ b/uxarray/grid.py @@ -93,6 +93,7 @@ def __init__(self, # check if initializing from verts: if isinstance(dataset, (list, tuple, np.ndarray)): dataset = np.asarray(dataset) + # Pre-process dataset if the multi-precision flag is set if self._multi_precision: # If the input are floats From 875611d786a9c8715b06537a4c0b8b5e72236b93 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Tue, 23 May 2023 21:07:38 -0700 Subject: [PATCH 31/59] precommit fix for jupyternotebook --- docs/examples/004-multiprecision-usage.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index ab586e23d..4752b6830 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -252,4 +252,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} From 4eb4b1ad1d0efd66500d1da9032e681952aa9e7d Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Tue, 23 May 2023 21:31:31 -0700 Subject: [PATCH 32/59] Fix jupyter example --- docs/examples/004-multiprecision-usage.ipynb | 23 +++++++++++------ test/test_grid.py | 26 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index 4752b6830..b47b4d85b 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -31,7 +31,7 @@ { "cell_type": "markdown", "source": [ - "# Generate the input coordinates on the unit sphere using the gmpy2 mpfr type.\n", + "# Using GMPY2.mpfr type for Multiprecision Arithmetic\n", "The distance between any two nodes is less than 1e-17 (the precision of the nodes is 60 bits, which is larger than the the python `float` type precision of 53 bits)" ], "metadata": { @@ -156,8 +156,8 @@ "execution_count": null, "outputs": [], "source": [ - "vgrid.nMesh2_face\n", - "vgrid.nMesh2_node" + "print(vgrid.nMesh2_face)\n", + "print(vgrid.nMesh2_node)" ], "metadata": { "collapsed": false, @@ -168,7 +168,10 @@ }, { "cell_type": "markdown", - "source": [], + "source": [ + "# Using python string type for Multiprecision Arithmetic\n", + "You can also input the coordinates as string. The string will be converted to the mpfr type." + ], "metadata": { "collapsed": false, "pycharm": { @@ -182,8 +185,10 @@ "outputs": [], "source": [ "# Provide nodes that are extremely closed to each other\n", - "face_nodes_connectivity = [['1.000000000000000000', '1.000000000000000000001', '1.0000000000000000000012','INT_FILL_VALUE'],\n", - " ['1.000000000000000001', '1.000000000000000000002', '1.0000000000000000000013','1.0000000000000000000014']]\n", + " face_nodes_connectivity = [\n", + " ['1.000000000000000000', '1.000000000000000000001', '1.0000000000000000000012'],\n", + " ['1.000000000000000001', '1.000000000000000000002', '1.0000000000000000000013'],\n", + " ['1.00000000000000000001', '1.00000000000000000000', '1.0000000000000000000013']]\n", "\n", "uxgrid = ux.Grid(face_nodes_connectivity, multi_precision=True, precision= 100,\n", " vertices=True,\n", @@ -200,7 +205,9 @@ { "cell_type": "markdown", "source": [ - "# Check the Grid Information" + "## Check the Grid Information\n", + "Although the input nodes are extremely close to each other, the grid is still valid and not degenerated:\n", + "The face number is still 1 and the node number is still 3." ], "metadata": { "collapsed": false, @@ -252,4 +259,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/test/test_grid.py b/test/test_grid.py index 09d5bf59b..36d94579a 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -146,6 +146,32 @@ def test_init_verts(self): assert (vgrid.nMesh2_node == 6) vgrid.encode_as("ugrid") + def test_init_verts_from_string(self): + # Provide nodes that are extremely closed to each other + face_nodes_connectivity = [[ + '1.000000000000000000', '1.000000000000000000001', + '1.0000000000000000000012' + ], + [ + '1.000000000000000001', + '1.000000000000000000002', + '1.0000000000000000000013' + ], + [ + '1.00000000000000000001', + '1.00000000000000000000', + '1.0000000000000000000013' + ]] + + uxgrid = ux.Grid(face_nodes_connectivity, + multi_precision=True, + precision=100, + vertices=True, + islatlon=False, + concave=False) + self.assertEqual(uxgrid.nMesh2_face, 1) + self.assertEqual(uxgrid.nMesh2_node, 3) + def test_init_verts_multi_precision_triangle(self): nodes = [] # Generate the nodes on the unit sphere, and the nodes are extremely close to each other: the edge length From 96bcdf04dcbda5cd0f0da810cfe278df4e786fd7 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Tue, 23 May 2023 21:41:14 -0700 Subject: [PATCH 33/59] update the jupyter notebook --- docs/examples/004-multiprecision-usage.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index b47b4d85b..1eafbcb78 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -185,7 +185,7 @@ "outputs": [], "source": [ "# Provide nodes that are extremely closed to each other\n", - " face_nodes_connectivity = [\n", + "face_nodes_connectivity = [\n", " ['1.000000000000000000', '1.000000000000000000001', '1.0000000000000000000012'],\n", " ['1.000000000000000001', '1.000000000000000000002', '1.0000000000000000000013'],\n", " ['1.00000000000000000001', '1.00000000000000000000', '1.0000000000000000000013']]\n", From 10187f3162e34a369cd39cd829b5d8d3f30fdf24 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Tue, 23 May 2023 21:44:26 -0700 Subject: [PATCH 34/59] precommit jupyter notebook --- uxarray/multi_precision_helpers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/uxarray/multi_precision_helpers.py b/uxarray/multi_precision_helpers.py index 4a92e02bb..f2416c45b 100644 --- a/uxarray/multi_precision_helpers.py +++ b/uxarray/multi_precision_helpers.py @@ -125,7 +125,6 @@ def unique_coordinates_multiprecision(input_array_mpfr, unique_arr = [] inverse_indices = [] m, n = input_array_mpfr.shape - unique_dict = {} current_index = 0 From 8707a5c708e65a8f10d91dbfb6ad4b4d3937affd Mon Sep 17 00:00:00 2001 From: Hongyu Chen <76922794+hongyuchen1030@users.noreply.github.com> Date: Tue, 23 May 2023 21:51:41 -0700 Subject: [PATCH 35/59] Force Update 004-multiprecision-usage.ipynb --- docs/examples/004-multiprecision-usage.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index 1eafbcb78..e412cd9a7 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -259,4 +259,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} From 2adfbe39a04fa185a47789f2b12b894cadbc4a73 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Fri, 26 May 2023 20:07:21 -0700 Subject: [PATCH 36/59] add testcase for performance test --- docs/examples/004-multiprecision-usage.ipynb | 170 +++++++++++++++++++ test/test_grid.py | 22 +-- test/test_helpers.py | 55 ++++++ 3 files changed, 233 insertions(+), 14 deletions(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index e9b9fa995..b1e2eb03f 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -255,6 +255,176 @@ } } }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import uxarray as ux\n", + "import uxarray.helpers as helpers\n", + "import uxarray.multi_precision_helpers as mp_helpers\n", + "\n", + "\n", + "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", + "# Multiprecision test for places=19, And we set the global precision places to 20\n", + "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", + "# And see if the results are the same as the initial coordinates\n", + "\n", + "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", + "\n", + "# Set the global precision of the mpfr numbers.\n", + "# Important Note:\n", + "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", + "# in the code.\n", + "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", + "# another call to this function.\n", + "mp_helpers.set_global_precision(precision)\n", + "\n", + "# The initial coordinates\n", + "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", + "new_x = init_x\n", + "new_y = init_y\n", + "new_z = init_z\n", + "for iter in range(1000):\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " assert new_x == init_x\n", + " assert new_y == init_y\n", + "\n", + "# Test for the longitude and latitude conversion\n", + "# The initial coordinates\n", + "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", + "# Reset global precision to default\n", + "new_lat = init_lat\n", + "new_lon = init_lon\n", + "for iter in range(1000):\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " assert new_lon == init_lon\n", + " assert new_lat == init_lat\n", + "\n", + "# Reset global precision to default\n", + "mp_helpers.set_global_precision()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# Precise Conversion between Spherical and Cartesian Coordinates\n", + "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Test for the longitude and latitude conversion helper functions with multiprecision\n", + "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", + "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import uxarray as ux\n", + "import uxarray.helpers as helpers\n", + "import uxarray.multi_precision_helpers as mp_helpers\n", + "\n", + "\n", + "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", + "# Multiprecision test for places=19, And we set the global precision places to 20\n", + "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", + "# And see if the results are the same as the initial coordinates\n", + "\n", + "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", + "\n", + "# Set the global precision of the mpfr numbers.\n", + "# Important Note:\n", + "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", + "# in the code.\n", + "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", + "# another call to this function.\n", + "mp_helpers.set_global_precision(precision)\n", + "\n", + "# The initial coordinates\n", + "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", + "new_x = init_x\n", + "new_y = init_y\n", + "new_z = init_z\n", + "for iter in range(1000):\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " assert new_x == init_x\n", + " assert new_y == init_y\n", + "\n", + "# Test for the longitude and latitude conversion\n", + "# The initial coordinates\n", + "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", + "# Reset global precision to default\n", + "new_lat = init_lat\n", + "new_lon = init_lon\n", + "for iter in range(1000):\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " assert new_lon == init_lon\n", + " assert new_lat == init_lat\n", + "\n", + "# Reset global precision to default\n", + "mp_helpers.set_global_precision()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# Precise Conversion between Spherical and Cartesian Coordinates\n", + "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Test for the longitude and latitude conversion helper functions with multiprecision\n", + "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", + "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, { "cell_type": "code", "execution_count": null, diff --git a/test/test_grid.py b/test/test_grid.py index 340b85956..744e732a5 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -6,6 +6,7 @@ from unittest import TestCase from pathlib import Path +import timeit import xarray as xr import uxarray as ux @@ -318,13 +319,6 @@ def test_init_grid_var_attrs(self): self.assertEqual(n_faces, self.tgrid1.nMesh2_face) self.assertEqual(n_face_nodes, self.tgrid1.nMaxMesh2_face_nodes) - # xr.testing.assert_equal( - # self.tgrid1.nMesh2_node, - # self.tgrid1.ds[self.tgrid1.ds_var_names["nMesh2_node"]]) - # xr.testing.assert_equal( - # self.tgrid1.nMesh2_face, - # self.tgrid1.ds[self.tgrid1.ds_var_names["nMesh2_face"]]) - # Dataset with non-standard UGRID variable names path = current_path / "meshfiles" / "ugrid" / "geoflow-small" / "grid.nc" xr_grid = xr.open_dataset(path) @@ -378,7 +372,7 @@ def test_calculate_total_face_area_triangle(self): # load grid vgrid = ux.Grid(verts, vertices=True, islatlon=False, concave=False) - #calculate area + # calculate area area_gaussian = vgrid.calculate_total_face_area( quadrature_rule="gaussian", order=5) nt.assert_almost_equal(area_gaussian, constants.TRI_AREA, decimal=3) @@ -474,7 +468,7 @@ def test_verts_calc_area(self): class TestPopulateCoordinates(TestCase): - def test_populate_cartesian_xyz_coord(self): + def test_populate_cartesian_xyz_coord_float(self): # The following testcases are generated through the matlab cart2sph/sph2cart functions # These points correspond to the eight vertices of a cube. lon_deg = [ @@ -514,6 +508,7 @@ def test_populate_cartesian_xyz_coord(self): cart_z[i], decimal=12) + def test_populate_cartesian_xyz_coord_mpfr(self): lon_deg = [ '45.0001052295749', '45.0001052295749', '314.9998947704251', '314.9998947704251' @@ -556,7 +551,7 @@ def test_populate_cartesian_xyz_coord(self): mpfr(cart_z[i]), decimal=14) - def test_populate_lonlat_coord(self): + def test_populate_lonlat_coord_float(self): # The following testcases are generated through the matlab cart2sph/sph2cart functions # These points correspond to the 4 vertexes on a cube. @@ -594,6 +589,7 @@ def test_populate_lonlat_coord(self): lat_deg[i], decimal=12) + def test_populate_lonlat_coord_mpfr(self): lon_deg = [ '45.0001052295749', '45.0001052295749', '314.9998947704251', '314.9998947704251' @@ -624,15 +620,13 @@ def test_populate_lonlat_coord(self): precision=64, islatlon=False) vgrid._populate_lonlat_coord() - # The connectivity in `__from_vert__()` will be formed in a reverse order - lon_deg, lat_deg = zip(*reversed(list(zip(lon_deg, lat_deg)))) for i in range(0, vgrid.nMesh2_node): nt.assert_almost_equal(vgrid.ds["Mesh2_node_x"].values[i], mpfr(lon_deg[i]), - decimal=14) + decimal=13) nt.assert_almost_equal(vgrid.ds["Mesh2_node_y"].values[i], mpfr(lat_deg[i]), - decimal=14) + decimal=13) class TestConnectivity(TestCase): diff --git a/test/test_helpers.py b/test/test_helpers.py index 9b60288bb..ab26eb054 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -8,6 +8,7 @@ import mpmath from unittest import TestCase from pathlib import Path +import time import uxarray as ux @@ -245,6 +246,60 @@ def test_precise_coordinates_conversion(self): # Reset global precision to default set_global_precision() + def test_coordinates_conversion_accumulate_error(self): + # Get the accumulated error of each function call + ux.multi_precision_helpers.set_global_precision(64) + run_time = 100 + print("\n") + + # Using the float number + new_lon = 122.987654321098765 + new_lat = 36.123456789012345 + + start_time = time.time() + for iter in range(run_time): + [new_x, new_y, new_z + ] = ux.helpers.node_lonlat_rad_to_xyz(np.deg2rad([new_lon, + new_lat])) + [new_lon, + new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) + [new_lon, new_lat] = np.rad2deg([new_lon, new_lat]) + + end_time = time.time() + diff_lat = mpfr(str(new_lat)) - mpfr('36.123456789012345') + diff_lon = mpfr(str(new_lon)) - mpfr('122.987654321098765') + print("The floating point longitude accumulated error is: " + + str(diff_lon) + "and the latitude accumulated " + "error is: " + str(diff_lat)) + print("The floating point Execution time: ", end_time - start_time, + " seconds") + + # Get the accumulated error of each function call + print("\n") + + # Using the float number + + [init_lon, init_lat + ] = [mpfr('122.987654321098765', 64), + mpfr('36.123456789012345', 64)] + new_lon = mpfr('122.987654321098765', 64) + new_lat = mpfr('36.123456789012345', 64) + start_time = time.time() + + for iter in range(run_time): + [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz( + [gmpy2.radians(val) for val in [new_lon, new_lat]]) + [new_lon, + new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) + [new_lon, + new_lat] = [gmpy2.degrees(val) for val in [new_lon, new_lat]] + end_time = time.time() + diff_lat = new_lat - init_lat + diff_lon = new_lon - init_lon + print("The mpfr longitude accumulated error is: " + str(diff_lon) + + " and the latitude accumulated error is: " + str(diff_lat)) + print("The mpfr Execution time: ", end_time - start_time, " seconds") + class TestConstants(TestCase): # DTYPE as set in constants.py From 93b4c203784b963c91c3dcfeb2619c24c11c27fd Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Fri, 26 May 2023 20:51:37 -0700 Subject: [PATCH 37/59] Pass the pytest --- docs/examples/004-multiprecision-usage.ipynb | 528 ++++++++++++++++++- 1 file changed, 517 insertions(+), 11 deletions(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index ce1ec8904..a5098e761 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -157,8 +157,8 @@ "execution_count": null, "outputs": [], "source": [ - "print(vgrid.nMesh2_face)\n", - "print(vgrid.nMesh2_node)" + "vgrid.nMesh2_face\n", + "vgrid.nMesh2_node" ], "metadata": { "collapsed": false, @@ -170,7 +170,7 @@ { "cell_type": "markdown", "source": [ - "# Using python string type for Multiprecision Arithmetic\n", + "# Using python string type for Multiprecision Arithmetic\n", "You can also input the coordinates as string. The string will be converted to the mpfr type." ], "metadata": { @@ -186,10 +186,8 @@ "outputs": [], "source": [ "# Provide nodes that are extremely closed to each other\n", - "face_nodes_connectivity = [\n", - " ['1.000000000000000000', '1.000000000000000000001', '1.0000000000000000000012'],\n", - " ['1.000000000000000001', '1.000000000000000000002', '1.0000000000000000000013'],\n", - " ['1.00000000000000000001', '1.00000000000000000000', '1.0000000000000000000013']]\n", + "face_nodes_connectivity = [['1.000000000000000000', '1.000000000000000000001', '1.0000000000000000000012','INT_FILL_VALUE'],\n", + " ['1.000000000000000001', '1.000000000000000000002', '1.0000000000000000000013','1.0000000000000000000014']]\n", "\n", "uxgrid = ux.Grid(face_nodes_connectivity, multi_precision=True, precision= 100,\n", " vertices=True,\n", @@ -206,9 +204,7 @@ { "cell_type": "markdown", "source": [ - "## Check the Grid Information\n", - "Although the input nodes are extremely close to each other, the grid is still valid and not degenerated:\n", - "The face number is still 1 and the node number is still 3." + "## Check the Grid Information" ], "metadata": { "collapsed": false, @@ -429,6 +425,516 @@ } } }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import uxarray as ux\n", + "import uxarray.helpers as helpers\n", + "import uxarray.multi_precision_helpers as mp_helpers\n", + "\n", + "\n", + "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", + "# Multiprecision test for places=19, And we set the global precision places to 20\n", + "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", + "# And see if the results are the same as the initial coordinates\n", + "\n", + "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", + "\n", + "# Set the global precision of the mpfr numbers.\n", + "# Important Note:\n", + "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", + "# in the code.\n", + "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", + "# another call to this function.\n", + "mp_helpers.set_global_precision(precision)\n", + "\n", + "# The initial coordinates\n", + "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", + "new_x = init_x\n", + "new_y = init_y\n", + "new_z = init_z\n", + "for iter in range(1000):\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " assert new_x == init_x\n", + " assert new_y == init_y\n", + "\n", + "# Test for the longitude and latitude conversion\n", + "# The initial coordinates\n", + "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", + "# Reset global precision to default\n", + "new_lat = init_lat\n", + "new_lon = init_lon\n", + "for iter in range(1000):\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " assert new_lon == init_lon\n", + " assert new_lat == init_lat\n", + "\n", + "# Reset global precision to default\n", + "mp_helpers.set_global_precision()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# Precise Conversion between Spherical and Cartesian Coordinates\n", + "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Test for the longitude and latitude conversion helper functions with multiprecision\n", + "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", + "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import uxarray as ux\n", + "import uxarray.helpers as helpers\n", + "import uxarray.multi_precision_helpers as mp_helpers\n", + "\n", + "\n", + "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", + "# Multiprecision test for places=19, And we set the global precision places to 20\n", + "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", + "# And see if the results are the same as the initial coordinates\n", + "\n", + "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", + "\n", + "# Set the global precision of the mpfr numbers.\n", + "# Important Note:\n", + "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", + "# in the code.\n", + "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", + "# another call to this function.\n", + "mp_helpers.set_global_precision(precision)\n", + "\n", + "# The initial coordinates\n", + "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", + "new_x = init_x\n", + "new_y = init_y\n", + "new_z = init_z\n", + "for iter in range(1000):\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " assert new_x == init_x\n", + " assert new_y == init_y\n", + "\n", + "# Test for the longitude and latitude conversion\n", + "# The initial coordinates\n", + "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", + "# Reset global precision to default\n", + "new_lat = init_lat\n", + "new_lon = init_lon\n", + "for iter in range(1000):\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " assert new_lon == init_lon\n", + " assert new_lat == init_lat\n", + "\n", + "# Reset global precision to default\n", + "mp_helpers.set_global_precision()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# Precise Conversion between Spherical and Cartesian Coordinates\n", + "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Test for the longitude and latitude conversion helper functions with multiprecision\n", + "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", + "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import uxarray as ux\n", + "import uxarray.helpers as helpers\n", + "import uxarray.multi_precision_helpers as mp_helpers\n", + "\n", + "\n", + "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", + "# Multiprecision test for places=19, And we set the global precision places to 20\n", + "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", + "# And see if the results are the same as the initial coordinates\n", + "\n", + "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", + "\n", + "# Set the global precision of the mpfr numbers.\n", + "# Important Note:\n", + "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", + "# in the code.\n", + "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", + "# another call to this function.\n", + "mp_helpers.set_global_precision(precision)\n", + "\n", + "# The initial coordinates\n", + "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", + "new_x = init_x\n", + "new_y = init_y\n", + "new_z = init_z\n", + "for iter in range(1000):\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " assert new_x == init_x\n", + " assert new_y == init_y\n", + "\n", + "# Test for the longitude and latitude conversion\n", + "# The initial coordinates\n", + "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", + "# Reset global precision to default\n", + "new_lat = init_lat\n", + "new_lon = init_lon\n", + "for iter in range(1000):\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " assert new_lon == init_lon\n", + " assert new_lat == init_lat\n", + "\n", + "# Reset global precision to default\n", + "mp_helpers.set_global_precision()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# Precise Conversion between Spherical and Cartesian Coordinates\n", + "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Test for the longitude and latitude conversion helper functions with multiprecision\n", + "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", + "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import uxarray as ux\n", + "import uxarray.helpers as helpers\n", + "import uxarray.multi_precision_helpers as mp_helpers\n", + "\n", + "\n", + "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", + "# Multiprecision test for places=19, And we set the global precision places to 20\n", + "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", + "# And see if the results are the same as the initial coordinates\n", + "\n", + "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", + "\n", + "# Set the global precision of the mpfr numbers.\n", + "# Important Note:\n", + "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", + "# in the code.\n", + "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", + "# another call to this function.\n", + "mp_helpers.set_global_precision(precision)\n", + "\n", + "# The initial coordinates\n", + "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", + "new_x = init_x\n", + "new_y = init_y\n", + "new_z = init_z\n", + "for iter in range(1000):\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " assert new_x == init_x\n", + " assert new_y == init_y\n", + "\n", + "# Test for the longitude and latitude conversion\n", + "# The initial coordinates\n", + "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", + "# Reset global precision to default\n", + "new_lat = init_lat\n", + "new_lon = init_lon\n", + "for iter in range(1000):\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " assert new_lon == init_lon\n", + " assert new_lat == init_lat\n", + "\n", + "# Reset global precision to default\n", + "mp_helpers.set_global_precision()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# Precise Conversion between Spherical and Cartesian Coordinates\n", + "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Test for the longitude and latitude conversion helper functions with multiprecision\n", + "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", + "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import uxarray as ux\n", + "import uxarray.helpers as helpers\n", + "import uxarray.multi_precision_helpers as mp_helpers\n", + "\n", + "\n", + "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", + "# Multiprecision test for places=19, And we set the global precision places to 20\n", + "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", + "# And see if the results are the same as the initial coordinates\n", + "\n", + "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", + "\n", + "# Set the global precision of the mpfr numbers.\n", + "# Important Note:\n", + "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", + "# in the code.\n", + "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", + "# another call to this function.\n", + "mp_helpers.set_global_precision(precision)\n", + "\n", + "# The initial coordinates\n", + "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", + "new_x = init_x\n", + "new_y = init_y\n", + "new_z = init_z\n", + "for iter in range(1000):\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " assert new_x == init_x\n", + " assert new_y == init_y\n", + "\n", + "# Test for the longitude and latitude conversion\n", + "# The initial coordinates\n", + "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", + "# Reset global precision to default\n", + "new_lat = init_lat\n", + "new_lon = init_lon\n", + "for iter in range(1000):\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " assert new_lon == init_lon\n", + " assert new_lat == init_lat\n", + "\n", + "# Reset global precision to default\n", + "mp_helpers.set_global_precision()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# Precise Conversion between Spherical and Cartesian Coordinates\n", + "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Test for the longitude and latitude conversion helper functions with multiprecision\n", + "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", + "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import uxarray as ux\n", + "import uxarray.helpers as helpers\n", + "import uxarray.multi_precision_helpers as mp_helpers\n", + "\n", + "\n", + "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", + "# Multiprecision test for places=19, And we set the global precision places to 20\n", + "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", + "# And see if the results are the same as the initial coordinates\n", + "\n", + "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", + "\n", + "# Set the global precision of the mpfr numbers.\n", + "# Important Note:\n", + "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", + "# in the code.\n", + "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", + "# another call to this function.\n", + "mp_helpers.set_global_precision(precision)\n", + "\n", + "# The initial coordinates\n", + "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", + "new_x = init_x\n", + "new_y = init_y\n", + "new_z = init_z\n", + "for iter in range(1000):\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " assert new_x == init_x\n", + " assert new_y == init_y\n", + "\n", + "# Test for the longitude and latitude conversion\n", + "# The initial coordinates\n", + "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", + "# Reset global precision to default\n", + "new_lat = init_lat\n", + "new_lon = init_lon\n", + "for iter in range(1000):\n", + " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", + " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", + " assert new_lon == init_lon\n", + " assert new_lat == init_lat\n", + "\n", + "# Reset global precision to default\n", + "mp_helpers.set_global_precision()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# Precise Conversion between Spherical and Cartesian Coordinates\n", + "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Test for the longitude and latitude conversion helper functions with multiprecision\n", + "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", + "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, { "cell_type": "code", "execution_count": null, @@ -515,4 +1021,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file From 18738fa06762d2d55ebb348ea402c13a15e361d0 Mon Sep 17 00:00:00 2001 From: Hongyu Chen <76922794+hongyuchen1030@users.noreply.github.com> Date: Fri, 26 May 2023 20:53:41 -0700 Subject: [PATCH 38/59] Update 004-multiprecision-usage.ipynb --- docs/examples/004-multiprecision-usage.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index a5098e761..a3c025cf4 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -1021,4 +1021,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} From aeeeaefe1766b611789ed6b81371f5e89ad0e419 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Fri, 26 May 2023 21:55:15 -0700 Subject: [PATCH 39/59] precommit the jupyternotebook --- docs/examples/004-multiprecision-usage.ipynb | 802 +------------------ 1 file changed, 20 insertions(+), 782 deletions(-) diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb index a5098e761..e412cd9a7 100644 --- a/docs/examples/004-multiprecision-usage.ipynb +++ b/docs/examples/004-multiprecision-usage.ipynb @@ -14,11 +14,10 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { "pycharm": { - "name": "#%%\n", - "is_executing": true + "name": "#%%\n" } }, "outputs": [], @@ -45,12 +44,6 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n", - "is_executing": true - } - }, "outputs": [], "source": [ "# Generate the nodes on the unit sphere, and the nodes are extremely close to each other: the edge length\n", @@ -77,7 +70,13 @@ " np.array([nodes[0], nodes[3], nodes[1], dumb_nodes], dtype=object),\n", " np.array([nodes[1], nodes[2], nodes[3], nodes[0]], dtype=object)\n", "], dtype=object)" - ] + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "markdown", @@ -157,8 +156,8 @@ "execution_count": null, "outputs": [], "source": [ - "vgrid.nMesh2_face\n", - "vgrid.nMesh2_node" + "print(vgrid.nMesh2_face)\n", + "print(vgrid.nMesh2_node)" ], "metadata": { "collapsed": false, @@ -170,7 +169,7 @@ { "cell_type": "markdown", "source": [ - "# Using python string type for Multiprecision Arithmetic\n", + "# Using python string type for Multiprecision Arithmetic\n", "You can also input the coordinates as string. The string will be converted to the mpfr type." ], "metadata": { @@ -186,8 +185,10 @@ "outputs": [], "source": [ "# Provide nodes that are extremely closed to each other\n", - "face_nodes_connectivity = [['1.000000000000000000', '1.000000000000000000001', '1.0000000000000000000012','INT_FILL_VALUE'],\n", - " ['1.000000000000000001', '1.000000000000000000002', '1.0000000000000000000013','1.0000000000000000000014']]\n", + "face_nodes_connectivity = [\n", + " ['1.000000000000000000', '1.000000000000000000001', '1.0000000000000000000012'],\n", + " ['1.000000000000000001', '1.000000000000000000002', '1.0000000000000000000013'],\n", + " ['1.00000000000000000001', '1.00000000000000000000', '1.0000000000000000000013']]\n", "\n", "uxgrid = ux.Grid(face_nodes_connectivity, multi_precision=True, precision= 100,\n", " vertices=True,\n", @@ -204,7 +205,9 @@ { "cell_type": "markdown", "source": [ - "## Check the Grid Information" + "## Check the Grid Information\n", + "Although the input nodes are extremely close to each other, the grid is still valid and not degenerated:\n", + "The face number is still 1 and the node number is still 3." ], "metadata": { "collapsed": false, @@ -227,771 +230,6 @@ "name": "#%%\n" } } - }, - { - "cell_type": "markdown", - "source": [ - "# Precise Conversion between Spherical and Cartesian Coordinates\n", - "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## Test for the longitude and latitude conversion helper functions with multiprecision\n", - "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", - "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "import uxarray as ux\n", - "import uxarray.helpers as helpers\n", - "import uxarray.multi_precision_helpers as mp_helpers\n", - "\n", - "\n", - "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", - "# Multiprecision test for places=19, And we set the global precision places to 20\n", - "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", - "# And see if the results are the same as the initial coordinates\n", - "\n", - "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", - "\n", - "# Set the global precision of the mpfr numbers.\n", - "# Important Note:\n", - "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", - "# in the code.\n", - "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", - "# another call to this function.\n", - "mp_helpers.set_global_precision(precision)\n", - "\n", - "# The initial coordinates\n", - "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", - "new_x = init_x\n", - "new_y = init_y\n", - "new_z = init_z\n", - "for iter in range(1000):\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " assert new_x == init_x\n", - " assert new_y == init_y\n", - "\n", - "# Test for the longitude and latitude conversion\n", - "# The initial coordinates\n", - "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", - "# Reset global precision to default\n", - "new_lat = init_lat\n", - "new_lon = init_lon\n", - "for iter in range(1000):\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " assert new_lon == init_lon\n", - " assert new_lat == init_lat\n", - "\n", - "# Reset global precision to default\n", - "mp_helpers.set_global_precision()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# Precise Conversion between Spherical and Cartesian Coordinates\n", - "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## Test for the longitude and latitude conversion helper functions with multiprecision\n", - "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", - "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "import uxarray as ux\n", - "import uxarray.helpers as helpers\n", - "import uxarray.multi_precision_helpers as mp_helpers\n", - "\n", - "\n", - "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", - "# Multiprecision test for places=19, And we set the global precision places to 20\n", - "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", - "# And see if the results are the same as the initial coordinates\n", - "\n", - "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", - "\n", - "# Set the global precision of the mpfr numbers.\n", - "# Important Note:\n", - "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", - "# in the code.\n", - "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", - "# another call to this function.\n", - "mp_helpers.set_global_precision(precision)\n", - "\n", - "# The initial coordinates\n", - "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", - "new_x = init_x\n", - "new_y = init_y\n", - "new_z = init_z\n", - "for iter in range(1000):\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " assert new_x == init_x\n", - " assert new_y == init_y\n", - "\n", - "# Test for the longitude and latitude conversion\n", - "# The initial coordinates\n", - "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", - "# Reset global precision to default\n", - "new_lat = init_lat\n", - "new_lon = init_lon\n", - "for iter in range(1000):\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " assert new_lon == init_lon\n", - " assert new_lat == init_lat\n", - "\n", - "# Reset global precision to default\n", - "mp_helpers.set_global_precision()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# Precise Conversion between Spherical and Cartesian Coordinates\n", - "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## Test for the longitude and latitude conversion helper functions with multiprecision\n", - "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", - "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "import uxarray as ux\n", - "import uxarray.helpers as helpers\n", - "import uxarray.multi_precision_helpers as mp_helpers\n", - "\n", - "\n", - "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", - "# Multiprecision test for places=19, And we set the global precision places to 20\n", - "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", - "# And see if the results are the same as the initial coordinates\n", - "\n", - "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", - "\n", - "# Set the global precision of the mpfr numbers.\n", - "# Important Note:\n", - "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", - "# in the code.\n", - "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", - "# another call to this function.\n", - "mp_helpers.set_global_precision(precision)\n", - "\n", - "# The initial coordinates\n", - "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", - "new_x = init_x\n", - "new_y = init_y\n", - "new_z = init_z\n", - "for iter in range(1000):\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " assert new_x == init_x\n", - " assert new_y == init_y\n", - "\n", - "# Test for the longitude and latitude conversion\n", - "# The initial coordinates\n", - "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", - "# Reset global precision to default\n", - "new_lat = init_lat\n", - "new_lon = init_lon\n", - "for iter in range(1000):\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " assert new_lon == init_lon\n", - " assert new_lat == init_lat\n", - "\n", - "# Reset global precision to default\n", - "mp_helpers.set_global_precision()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# Precise Conversion between Spherical and Cartesian Coordinates\n", - "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## Test for the longitude and latitude conversion helper functions with multiprecision\n", - "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", - "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "import uxarray as ux\n", - "import uxarray.helpers as helpers\n", - "import uxarray.multi_precision_helpers as mp_helpers\n", - "\n", - "\n", - "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", - "# Multiprecision test for places=19, And we set the global precision places to 20\n", - "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", - "# And see if the results are the same as the initial coordinates\n", - "\n", - "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", - "\n", - "# Set the global precision of the mpfr numbers.\n", - "# Important Note:\n", - "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", - "# in the code.\n", - "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", - "# another call to this function.\n", - "mp_helpers.set_global_precision(precision)\n", - "\n", - "# The initial coordinates\n", - "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", - "new_x = init_x\n", - "new_y = init_y\n", - "new_z = init_z\n", - "for iter in range(1000):\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " assert new_x == init_x\n", - " assert new_y == init_y\n", - "\n", - "# Test for the longitude and latitude conversion\n", - "# The initial coordinates\n", - "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", - "# Reset global precision to default\n", - "new_lat = init_lat\n", - "new_lon = init_lon\n", - "for iter in range(1000):\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " assert new_lon == init_lon\n", - " assert new_lat == init_lat\n", - "\n", - "# Reset global precision to default\n", - "mp_helpers.set_global_precision()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# Precise Conversion between Spherical and Cartesian Coordinates\n", - "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## Test for the longitude and latitude conversion helper functions with multiprecision\n", - "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", - "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "import uxarray as ux\n", - "import uxarray.helpers as helpers\n", - "import uxarray.multi_precision_helpers as mp_helpers\n", - "\n", - "\n", - "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", - "# Multiprecision test for places=19, And we set the global precision places to 20\n", - "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", - "# And see if the results are the same as the initial coordinates\n", - "\n", - "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", - "\n", - "# Set the global precision of the mpfr numbers.\n", - "# Important Note:\n", - "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", - "# in the code.\n", - "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", - "# another call to this function.\n", - "mp_helpers.set_global_precision(precision)\n", - "\n", - "# The initial coordinates\n", - "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", - "new_x = init_x\n", - "new_y = init_y\n", - "new_z = init_z\n", - "for iter in range(1000):\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " assert new_x == init_x\n", - " assert new_y == init_y\n", - "\n", - "# Test for the longitude and latitude conversion\n", - "# The initial coordinates\n", - "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", - "# Reset global precision to default\n", - "new_lat = init_lat\n", - "new_lon = init_lon\n", - "for iter in range(1000):\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " assert new_lon == init_lon\n", - " assert new_lat == init_lat\n", - "\n", - "# Reset global precision to default\n", - "mp_helpers.set_global_precision()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# Precise Conversion between Spherical and Cartesian Coordinates\n", - "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## Test for the longitude and latitude conversion helper functions with multiprecision\n", - "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", - "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "import uxarray as ux\n", - "import uxarray.helpers as helpers\n", - "import uxarray.multi_precision_helpers as mp_helpers\n", - "\n", - "\n", - "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", - "# Multiprecision test for places=19, And we set the global precision places to 20\n", - "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", - "# And see if the results are the same as the initial coordinates\n", - "\n", - "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", - "\n", - "# Set the global precision of the mpfr numbers.\n", - "# Important Note:\n", - "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", - "# in the code.\n", - "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", - "# another call to this function.\n", - "mp_helpers.set_global_precision(precision)\n", - "\n", - "# The initial coordinates\n", - "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", - "new_x = init_x\n", - "new_y = init_y\n", - "new_z = init_z\n", - "for iter in range(1000):\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " assert new_x == init_x\n", - " assert new_y == init_y\n", - "\n", - "# Test for the longitude and latitude conversion\n", - "# The initial coordinates\n", - "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", - "# Reset global precision to default\n", - "new_lat = init_lat\n", - "new_lon = init_lon\n", - "for iter in range(1000):\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " assert new_lon == init_lon\n", - " assert new_lat == init_lat\n", - "\n", - "# Reset global precision to default\n", - "mp_helpers.set_global_precision()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# Precise Conversion between Spherical and Cartesian Coordinates\n", - "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## Test for the longitude and latitude conversion helper functions with multiprecision\n", - "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", - "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "import uxarray as ux\n", - "import uxarray.helpers as helpers\n", - "import uxarray.multi_precision_helpers as mp_helpers\n", - "\n", - "\n", - "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", - "# Multiprecision test for places=19, And we set the global precision places to 20\n", - "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", - "# And see if the results are the same as the initial coordinates\n", - "\n", - "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", - "\n", - "# Set the global precision of the mpfr numbers.\n", - "# Important Note:\n", - "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", - "# in the code.\n", - "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", - "# another call to this function.\n", - "mp_helpers.set_global_precision(precision)\n", - "\n", - "# The initial coordinates\n", - "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", - "new_x = init_x\n", - "new_y = init_y\n", - "new_z = init_z\n", - "for iter in range(1000):\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " assert new_x == init_x\n", - " assert new_y == init_y\n", - "\n", - "# Test for the longitude and latitude conversion\n", - "# The initial coordinates\n", - "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", - "# Reset global precision to default\n", - "new_lat = init_lat\n", - "new_lon = init_lon\n", - "for iter in range(1000):\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " assert new_lon == init_lon\n", - " assert new_lat == init_lat\n", - "\n", - "# Reset global precision to default\n", - "mp_helpers.set_global_precision()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# Precise Conversion between Spherical and Cartesian Coordinates\n", - "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## Test for the longitude and latitude conversion helper functions with multiprecision\n", - "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", - "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "import uxarray as ux\n", - "import uxarray.helpers as helpers\n", - "import uxarray.multi_precision_helpers as mp_helpers\n", - "\n", - "\n", - "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", - "# Multiprecision test for places=19, And we set the global precision places to 20\n", - "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", - "# And see if the results are the same as the initial coordinates\n", - "\n", - "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", - "\n", - "# Set the global precision of the mpfr numbers.\n", - "# Important Note:\n", - "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", - "# in the code.\n", - "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", - "# another call to this function.\n", - "mp_helpers.set_global_precision(precision)\n", - "\n", - "# The initial coordinates\n", - "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", - "new_x = init_x\n", - "new_y = init_y\n", - "new_z = init_z\n", - "for iter in range(1000):\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " assert new_x == init_x\n", - " assert new_y == init_y\n", - "\n", - "# Test for the longitude and latitude conversion\n", - "# The initial coordinates\n", - "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", - "# Reset global precision to default\n", - "new_lat = init_lat\n", - "new_lon = init_lon\n", - "for iter in range(1000):\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " assert new_lon == init_lon\n", - " assert new_lat == init_lat\n", - "\n", - "# Reset global precision to default\n", - "mp_helpers.set_global_precision()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# Precise Conversion between Spherical and Cartesian Coordinates\n", - "By turning on the multiprecision mode, the conversion between spherical and cartesian coordinates will be exact in the given precision." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## Test for the longitude and latitude conversion helper functions with multiprecision\n", - "The following example shows that the conversion between longitude and latitude and xyz is exact in the given precision when using the\n", - "gmpy2.mpfr type. Such exact compuation is also supported in our Grid object.\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "import uxarray as ux\n", - "import uxarray.helpers as helpers\n", - "import uxarray.multi_precision_helpers as mp_helpers\n", - "\n", - "\n", - "# With the multiprecision mode on, the conversion between spherical and cartesian coordinates will be exact in the given precision\n", - "# Multiprecision test for places=19, And we set the global precision places to 20\n", - "# Repeat the conversion between latitude and longitude and xyz for 1000 times\n", - "# And see if the results are the same as the initial coordinates\n", - "\n", - "precision = mp_helpers.decimal_digits_to_precision_bits(20)\n", - "\n", - "# Set the global precision of the mpfr numbers.\n", - "# Important Note:\n", - "# 1. To avoid arithmetic overflow, the global precision should always be higher than any other precision speicified\n", - "# in the code.\n", - "# 2. Modifying the precision by calling this function will modify all following codes running context until\n", - "# another call to this function.\n", - "mp_helpers.set_global_precision(precision)\n", - "\n", - "# The initial coordinates\n", - "[init_x, init_y, init_z] = ux.helpers.normalize_in_place([mpfr('0.12345678910111213149'),mpfr('0.92345678910111213149'),mpfr('1.72345678910111213149')])\n", - "new_x = init_x\n", - "new_y = init_y\n", - "new_z = init_z\n", - "for iter in range(1000):\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " assert new_x == init_x\n", - " assert new_y == init_y\n", - "\n", - "# Test for the longitude and latitude conversion\n", - "# The initial coordinates\n", - "[init_lon, init_lat] = [mpfr('1.4000332309896247'),mpfr('1.190289949682531')]\n", - "# Reset global precision to default\n", - "new_lat = init_lat\n", - "new_lon = init_lon\n", - "for iter in range(1000):\n", - " [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat])\n", - " [new_lon, new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z])\n", - " assert new_lon == init_lon\n", - " assert new_lat == init_lat\n", - "\n", - "# Reset global precision to default\n", - "mp_helpers.set_global_precision()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } } ], "metadata": { @@ -1021,4 +259,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} From db819fea5f95d19593c9c042eff5da392966493f Mon Sep 17 00:00:00 2001 From: aazedwick Date: Thu, 8 Jun 2023 08:59:02 -0500 Subject: [PATCH 40/59] Updated from_ds function, my test case isn't working. Rajeev said to have you look at it if I got stuck --- test/test_grid.py | 25 +++++++++++++++++++++++++ uxarray/grid.py | 25 ++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/test/test_grid.py b/test/test_grid.py index 36d94579a..05356c05e 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -874,3 +874,28 @@ def test_build_face_edges_connectivity_fillvalues(self): self.assertTrue( np.array_equal(res_face_nodes_connectivity, uds.ds["Mesh2_face_nodes"].values)) + + +class TestMeshLoader(TestCase): + + def testFromDs(self): + exodus_filepath = current_path / "meshfiles" / "exodus" / "outCSne8" / "outCSne8.g" + + xrds_exodus = xr.open_dataset(exodus_filepath) + + # Create the grid with multi-precision off + gridOff = ux.Grid(xrds_exodus) + resultsPrecisionOff = gridOff.__from_ds__(xrds_exodus, multi_precision=False) + + # Create the grid with multi-precision on + gridOn = ux.Grid(xrds_exodus) + resultsPrecisionOn = gridOn.__from_ds__(xrds_exodus, multi_precision=True, precision=64) + + # Check that the nodes are no longer equal + firstNodePrecisionOn = resultsPrecisionOn["nMesh2_node"].values[0] + firstNodePrecisionOff = resultsPrecisionOff["nMesh2_node"].values[0] + self.assertNotEqual(firstNodePrecisionOn, firstNodePrecisionOff) + + # Check that the flags are set properly + self.assertTrue(gridOn._multi_precision) + self.assertEqual(gridOn._precision, 64) \ No newline at end of file diff --git a/uxarray/grid.py b/uxarray/grid.py index cc58760b3..9d4ab1a03 100644 --- a/uxarray/grid.py +++ b/uxarray/grid.py @@ -377,7 +377,7 @@ def __from_vert__(self, }) # load mesh from a file - def __from_ds__(self, dataset): + def __from_ds__(self, dataset, multi_precision=False, precision=FLOAT_PRECISION_BITS): """Loads a mesh dataset.""" # call reader as per mesh_type if self.mesh_type == "exo": @@ -397,8 +397,31 @@ def __from_ds__(self, dataset): else: raise RuntimeError("unknown mesh type") + # set precision flags + self._multi_precision = multi_precision + self._precision = precision + + # loops through the dataset + if self._multi_precision: + for varName, varData in dataset.data_vars.items(): + # check if the variable data is of float, integer, or string type and convert it based on the type + if np.issubdtype(varData.dtype, np.floating): + convertedData = convert_to_multiprecision( + varData.values, str_mode=False, precision=self._precision) + self.ds[varName] = (varData.dims, convertedData) + elif np.issubdtype(varData.dtype, np.integer): + convertedData = convert_to_multiprecision( + varData.values, str_mode=False, precision=self._precision) + self.ds[varName] = (varData.dims, convertedData) + elif np.issubdtype(varData.dtype, np.str): + convertedData = convert_to_multiprecision( + varData.values, str_mode=True, precision=self._precision) + self.ds[varName] = (varData.dims, convertedData) + dataset.close() + return self.ds + def encode_as(self, grid_type): """Encodes the grid as a new `xarray.Dataset` per grid format supplied in the `grid_type` argument. From b44a3fa9d9b7b4e103520fb053b4d2da0029ff7b Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Thu, 8 Jun 2023 10:07:53 -0700 Subject: [PATCH 41/59] Revert "Updated from_ds function, my test case isn't working. Rajeev said to have you look at it if I got stuck" This reverts commit db819fea5f95d19593c9c042eff5da392966493f. --- test/test_grid.py | 25 ------------------------- uxarray/grid.py | 25 +------------------------ 2 files changed, 1 insertion(+), 49 deletions(-) diff --git a/test/test_grid.py b/test/test_grid.py index 05356c05e..36d94579a 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -874,28 +874,3 @@ def test_build_face_edges_connectivity_fillvalues(self): self.assertTrue( np.array_equal(res_face_nodes_connectivity, uds.ds["Mesh2_face_nodes"].values)) - - -class TestMeshLoader(TestCase): - - def testFromDs(self): - exodus_filepath = current_path / "meshfiles" / "exodus" / "outCSne8" / "outCSne8.g" - - xrds_exodus = xr.open_dataset(exodus_filepath) - - # Create the grid with multi-precision off - gridOff = ux.Grid(xrds_exodus) - resultsPrecisionOff = gridOff.__from_ds__(xrds_exodus, multi_precision=False) - - # Create the grid with multi-precision on - gridOn = ux.Grid(xrds_exodus) - resultsPrecisionOn = gridOn.__from_ds__(xrds_exodus, multi_precision=True, precision=64) - - # Check that the nodes are no longer equal - firstNodePrecisionOn = resultsPrecisionOn["nMesh2_node"].values[0] - firstNodePrecisionOff = resultsPrecisionOff["nMesh2_node"].values[0] - self.assertNotEqual(firstNodePrecisionOn, firstNodePrecisionOff) - - # Check that the flags are set properly - self.assertTrue(gridOn._multi_precision) - self.assertEqual(gridOn._precision, 64) \ No newline at end of file diff --git a/uxarray/grid.py b/uxarray/grid.py index 9d4ab1a03..cc58760b3 100644 --- a/uxarray/grid.py +++ b/uxarray/grid.py @@ -377,7 +377,7 @@ def __from_vert__(self, }) # load mesh from a file - def __from_ds__(self, dataset, multi_precision=False, precision=FLOAT_PRECISION_BITS): + def __from_ds__(self, dataset): """Loads a mesh dataset.""" # call reader as per mesh_type if self.mesh_type == "exo": @@ -397,31 +397,8 @@ def __from_ds__(self, dataset, multi_precision=False, precision=FLOAT_PRECISION_ else: raise RuntimeError("unknown mesh type") - # set precision flags - self._multi_precision = multi_precision - self._precision = precision - - # loops through the dataset - if self._multi_precision: - for varName, varData in dataset.data_vars.items(): - # check if the variable data is of float, integer, or string type and convert it based on the type - if np.issubdtype(varData.dtype, np.floating): - convertedData = convert_to_multiprecision( - varData.values, str_mode=False, precision=self._precision) - self.ds[varName] = (varData.dims, convertedData) - elif np.issubdtype(varData.dtype, np.integer): - convertedData = convert_to_multiprecision( - varData.values, str_mode=False, precision=self._precision) - self.ds[varName] = (varData.dims, convertedData) - elif np.issubdtype(varData.dtype, np.str): - convertedData = convert_to_multiprecision( - varData.values, str_mode=True, precision=self._precision) - self.ds[varName] = (varData.dims, convertedData) - dataset.close() - return self.ds - def encode_as(self, grid_type): """Encodes the grid as a new `xarray.Dataset` per grid format supplied in the `grid_type` argument. From 5ac2bdc7c2b23fcbc6bc2b2cd95badbb63f5dc09 Mon Sep 17 00:00:00 2001 From: aazedwick Date: Thu, 8 Jun 2023 08:59:02 -0500 Subject: [PATCH 42/59] Updated from_ds function, my test case isn't working. Rajeev said to have you look at it if I got stuck --- test/test_grid.py | 25 +++++++++++++++++++++++++ uxarray/grid.py | 25 ++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/test/test_grid.py b/test/test_grid.py index 25677fff4..335143750 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -927,3 +927,28 @@ def test_build_face_edges_connectivity_fillvalues(self): self.assertTrue( np.array_equal(res_face_nodes_connectivity, uds.ds["Mesh2_face_nodes"].values)) + + +class TestMeshLoader(TestCase): + + def testFromDs(self): + exodus_filepath = current_path / "meshfiles" / "exodus" / "outCSne8" / "outCSne8.g" + + xrds_exodus = xr.open_dataset(exodus_filepath) + + # Create the grid with multi-precision off + gridOff = ux.Grid(xrds_exodus) + resultsPrecisionOff = gridOff.__from_ds__(xrds_exodus, multi_precision=False) + + # Create the grid with multi-precision on + gridOn = ux.Grid(xrds_exodus) + resultsPrecisionOn = gridOn.__from_ds__(xrds_exodus, multi_precision=True, precision=64) + + # Check that the nodes are no longer equal + firstNodePrecisionOn = resultsPrecisionOn["nMesh2_node"].values[0] + firstNodePrecisionOff = resultsPrecisionOff["nMesh2_node"].values[0] + self.assertNotEqual(firstNodePrecisionOn, firstNodePrecisionOff) + + # Check that the flags are set properly + self.assertTrue(gridOn._multi_precision) + self.assertEqual(gridOn._precision, 64) \ No newline at end of file diff --git a/uxarray/grid.py b/uxarray/grid.py index 058f75b17..6320569d8 100644 --- a/uxarray/grid.py +++ b/uxarray/grid.py @@ -371,7 +371,7 @@ def __from_vert__(self, dataset): }) # load mesh from a file - def __from_ds__(self, dataset): + def __from_ds__(self, dataset, multi_precision=False, precision=FLOAT_PRECISION_BITS): """Loads a mesh dataset.""" # call reader as per mesh_type if self.mesh_type == "exo": @@ -391,8 +391,31 @@ def __from_ds__(self, dataset): else: raise RuntimeError("unknown mesh type") + # set precision flags + self._multi_precision = multi_precision + self._precision = precision + + # loops through the dataset + if self._multi_precision: + for varName, varData in dataset.data_vars.items(): + # check if the variable data is of float, integer, or string type and convert it based on the type + if np.issubdtype(varData.dtype, np.floating): + convertedData = convert_to_multiprecision( + varData.values, str_mode=False, precision=self._precision) + self.ds[varName] = (varData.dims, convertedData) + elif np.issubdtype(varData.dtype, np.integer): + convertedData = convert_to_multiprecision( + varData.values, str_mode=False, precision=self._precision) + self.ds[varName] = (varData.dims, convertedData) + elif np.issubdtype(varData.dtype, np.str): + convertedData = convert_to_multiprecision( + varData.values, str_mode=True, precision=self._precision) + self.ds[varName] = (varData.dims, convertedData) + dataset.close() + return self.ds + def encode_as(self, grid_type): """Encodes the grid as a new `xarray.Dataset` per grid format supplied in the `grid_type` argument. From 7264d2c538acf78e39be02f06d106ee7dad34eb7 Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Thu, 8 Jun 2023 10:07:53 -0700 Subject: [PATCH 43/59] Revert "Updated from_ds function, my test case isn't working. Rajeev said to have you look at it if I got stuck" This reverts commit db819fea5f95d19593c9c042eff5da392966493f. --- test/test_grid.py | 25 ------------------------- uxarray/grid.py | 25 +------------------------ 2 files changed, 1 insertion(+), 49 deletions(-) diff --git a/test/test_grid.py b/test/test_grid.py index 335143750..25677fff4 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -927,28 +927,3 @@ def test_build_face_edges_connectivity_fillvalues(self): self.assertTrue( np.array_equal(res_face_nodes_connectivity, uds.ds["Mesh2_face_nodes"].values)) - - -class TestMeshLoader(TestCase): - - def testFromDs(self): - exodus_filepath = current_path / "meshfiles" / "exodus" / "outCSne8" / "outCSne8.g" - - xrds_exodus = xr.open_dataset(exodus_filepath) - - # Create the grid with multi-precision off - gridOff = ux.Grid(xrds_exodus) - resultsPrecisionOff = gridOff.__from_ds__(xrds_exodus, multi_precision=False) - - # Create the grid with multi-precision on - gridOn = ux.Grid(xrds_exodus) - resultsPrecisionOn = gridOn.__from_ds__(xrds_exodus, multi_precision=True, precision=64) - - # Check that the nodes are no longer equal - firstNodePrecisionOn = resultsPrecisionOn["nMesh2_node"].values[0] - firstNodePrecisionOff = resultsPrecisionOff["nMesh2_node"].values[0] - self.assertNotEqual(firstNodePrecisionOn, firstNodePrecisionOff) - - # Check that the flags are set properly - self.assertTrue(gridOn._multi_precision) - self.assertEqual(gridOn._precision, 64) \ No newline at end of file diff --git a/uxarray/grid.py b/uxarray/grid.py index 6320569d8..058f75b17 100644 --- a/uxarray/grid.py +++ b/uxarray/grid.py @@ -371,7 +371,7 @@ def __from_vert__(self, dataset): }) # load mesh from a file - def __from_ds__(self, dataset, multi_precision=False, precision=FLOAT_PRECISION_BITS): + def __from_ds__(self, dataset): """Loads a mesh dataset.""" # call reader as per mesh_type if self.mesh_type == "exo": @@ -391,31 +391,8 @@ def __from_ds__(self, dataset, multi_precision=False, precision=FLOAT_PRECISION_ else: raise RuntimeError("unknown mesh type") - # set precision flags - self._multi_precision = multi_precision - self._precision = precision - - # loops through the dataset - if self._multi_precision: - for varName, varData in dataset.data_vars.items(): - # check if the variable data is of float, integer, or string type and convert it based on the type - if np.issubdtype(varData.dtype, np.floating): - convertedData = convert_to_multiprecision( - varData.values, str_mode=False, precision=self._precision) - self.ds[varName] = (varData.dims, convertedData) - elif np.issubdtype(varData.dtype, np.integer): - convertedData = convert_to_multiprecision( - varData.values, str_mode=False, precision=self._precision) - self.ds[varName] = (varData.dims, convertedData) - elif np.issubdtype(varData.dtype, np.str): - convertedData = convert_to_multiprecision( - varData.values, str_mode=True, precision=self._precision) - self.ds[varName] = (varData.dims, convertedData) - dataset.close() - return self.ds - def encode_as(self, grid_type): """Encodes the grid as a new `xarray.Dataset` per grid format supplied in the `grid_type` argument. From e69a3e588e06ec2747e211b098ddbde5fbcb4efa Mon Sep 17 00:00:00 2001 From: Hongyu Chen Date: Tue, 13 Jun 2023 19:32:44 -0700 Subject: [PATCH 44/59] coordinates conversion overloads --- uxarray/constants.py | 1 + uxarray/helpers.py | 67 ++++++++++++++++++++++++++------------------ 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/uxarray/constants.py b/uxarray/constants.py index 1c5cdadd2..60ecebb24 100644 --- a/uxarray/constants.py +++ b/uxarray/constants.py @@ -6,3 +6,4 @@ INT_FILL_VALUE = np.iinfo(INT_DTYPE).min INT_FILL_VALUE_MPZ = mpz(str(INT_FILL_VALUE)) FLOAT_PRECISION_BITS = 53 +ERROR_TOLERANCE = 1.0e-15 diff --git a/uxarray/helpers.py b/uxarray/helpers.py index b3915ab4c..25ac8ebbb 100644 --- a/uxarray/helpers.py +++ b/uxarray/helpers.py @@ -7,7 +7,7 @@ from numba import njit, config import math -from uxarray.constants import INT_DTYPE, INT_FILL_VALUE +from uxarray.constants import INT_DTYPE, INT_FILL_VALUE, ERROR_TOLERANCE config.DISABLE_JIT = False @@ -474,12 +474,12 @@ def node_lonlat_rad_to_xyz(node_coord): Parameters ---------- - node: list, python `float` or `gmpy2.mpfr` + node: list or np.array, python `float` or `gmpy2.mpfr` 2D coordinates[longitude, latitude] in radiance Returns ---------- - node_cartesian: list, python `float` or `gmpy2.mpfr` + node_cartesian: np.array, python `float` or `gmpy2.mpfr` the result array of the unit 3D coordinates [x, y, z] vector where :math:`x^2 + y^2 + z^2 = 1` Raises @@ -487,6 +487,8 @@ def node_lonlat_rad_to_xyz(node_coord): RuntimeError The input array doesn't have the size of 3. """ + if isinstance(node_coord, list): + node_coord = np.array(node_coord) if len(node_coord) != 2: raise RuntimeError( "Input array should have a length of 2: [longitude, latitude]") @@ -495,19 +497,18 @@ def node_lonlat_rad_to_xyz(node_coord): node_coord)): lon = node_coord[0] lat = node_coord[1] - return [ - gmpy2.mul(gmpy2.cos(lon), gmpy2.cos(lat)), - gmpy2.mul(gmpy2.sin(lon), gmpy2.cos(lat)), + return np.array([ + gmpy2.cos(lon) * gmpy2.cos(lat), + gmpy2.sin(lon) * gmpy2.cos(lat), gmpy2.sin(lat) - ] + ]) else: lon = node_coord[0] lat = node_coord[1] - return [ - np.cos(lon) * np.cos(lat), - np.sin(lon) * np.cos(lat), - np.sin(lat) - ] + return np.array( + [np.cos(lon) * np.cos(lat), + np.sin(lon) * np.cos(lat), + np.sin(lat)]) def node_xyz_to_lonlat_rad(node_coord): @@ -516,12 +517,12 @@ def node_xyz_to_lonlat_rad(node_coord): Parameters ---------- - node_coord: list, python `float` or `gmpy2.mpfr` + node_coord: np.array or python list of `float` or gmpy2.mpfr 3D Cartesian Coordinates [x, y, z] of the node Returns ---------- - node_coord_rad: list, python `float` or `gmpy2.mpfr` + node_coord_rad: np.array of `float` or gmpy2.mpfr the result array of longitude and latitude in radian [longitude_rad, latitude_rad] Raises @@ -529,6 +530,11 @@ def node_xyz_to_lonlat_rad(node_coord): RuntimeError The input array doesn't have the size of 3. """ + if isinstance(node_coord, list): + node_coord = np.array(node_coord) + elif not isinstance(node_coord, np.ndarray): + raise TypeError( + "Input node_coord should be either a numpy array or a list.") if len(node_coord) != 3: raise RuntimeError("Input array should have a length of 3: [x, y, z]") if np.any( @@ -541,20 +547,19 @@ def node_xyz_to_lonlat_rad(node_coord): d_lat_rad = gmpy2.asin(dz) if gmpy2.cmp(d_lon_rad, mpfr('0.0')) < 0: - d_lon_rad += gmpy2.mul(mpfr('2.0'), gmpy2.const_pi()) + d_lon_rad += mpfr('2.0') * gmpy2.const_pi() elif gmpy2.cmp(dz, mpfr('0.0')) > 0: d_lon_rad = mpfr('0.0') - d_lat_rad = gmpy2.mul(mpfr('0.5'), gmpy2.const_pi()) + d_lat_rad = mpfr('0.5') * gmpy2.const_pi() else: d_lon_rad = mpfr('0.0') - d_lat_rad = gmpy2.mul(mpfr('-0.5'), gmpy2.const_pi()) + d_lat_rad = mpfr('-0.5') * gmpy2.const_pi() - return [d_lon_rad, d_lat_rad] + return np.array([d_lon_rad, d_lat_rad]) else: - reference_tolerance = 1.0e-12 [dx, dy, dz] = normalize_in_place(node_coord) - if np.absolute(dz) < (1.0 - reference_tolerance): + if np.absolute(dz) < (1.0 - ERROR_TOLERANCE): d_lon_rad = math.atan2(dy, dx) d_lat_rad = np.arcsin(dz) @@ -567,7 +572,7 @@ def node_xyz_to_lonlat_rad(node_coord): d_lon_rad = 0.0 d_lat_rad = -0.5 * np.pi - return [d_lon_rad, d_lat_rad] + return np.array([d_lon_rad, d_lat_rad]) def normalize_in_place(node): @@ -577,12 +582,12 @@ def normalize_in_place(node): Parameters ---------- - node: list, python `float` or gmpy2.mpfr + node: np.array or python list of `float` or gmpy2.mpfr 3D Cartesian Coordinates [x, y, z] Returns ---------- - normalized_node: list, python `float` or gmpy2.mpfr + normalized_node: np. array of `float` or gmpy2.mpfr the result unit vector [x, y, z] where :math:`x^2 + y^2 + z^2 = 1` Raises @@ -590,7 +595,12 @@ def normalize_in_place(node): RuntimeError The input array doesn't have the size of 3. """ - if len(node) != 3: + if isinstance(node, list): + node = np.array(node) + elif not isinstance(node, np.ndarray): + raise TypeError("Input node should be either a numpy array or a list.") + + if node.size != 3: raise RuntimeError("Input array should have a length of 3: [x, y, z]") if np.any( @@ -602,10 +612,13 @@ def normalize_in_place(node): # Divide each element of the node by the norm using gmpy2 normalized_node = [gmpy2.div(element, norm) for element in node] - # Convert the result back to numpy array - return np.array(normalized_node) + # Convert the result back to numpy array or list depending on the input type + if isinstance(node, np.ndarray): + return np.array(normalized_node) + else: + return normalized_node else: - return np.array(node) / np.linalg.norm(np.array(node), ord=2) + return node / np.linalg.norm(node, ord=2) def _replace_fill_values(grid_var, original_fill, new_fill, new_dtype=None): From 5170e3cab35bbb5c702879bd90897585e5c1d7c7 Mon Sep 17 00:00:00 2001 From: philipc2 Date: Tue, 20 Jun 2023 13:41:06 -0500 Subject: [PATCH 45/59] fix import --- test/test_multi_precision_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_multi_precision_helpers.py b/test/test_multi_precision_helpers.py index c2414ba2e..48ecb3a6f 100644 --- a/test/test_multi_precision_helpers.py +++ b/test/test_multi_precision_helpers.py @@ -8,7 +8,7 @@ import uxarray as ux -from uxarray.constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ +from uxarray.utils.constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ from uxarray.multi_precision_helpers import convert_to_multiprecision, unique_coordinates_multiprecision, precision_bits_to_decimal_digits, decimal_digits_to_precision_bits import math From 6c10e6d1b77c38fec401933887c135ce890ec9f5 Mon Sep 17 00:00:00 2001 From: philipc2 Date: Tue, 20 Jun 2023 13:58:32 -0500 Subject: [PATCH 46/59] fix constants --- uxarray/multi_precision_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uxarray/multi_precision_helpers.py b/uxarray/multi_precision_helpers.py index f2416c45b..ec120dc9b 100644 --- a/uxarray/multi_precision_helpers.py +++ b/uxarray/multi_precision_helpers.py @@ -2,7 +2,7 @@ from gmpy2 import mpfr, mpz import numpy as np import math -from .constants import FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ +from uxarray.utils.constants import FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ def convert_to_multiprecision(input_array, From e81fe91a2f34014ac4cb98fd6b6b838bff6087f2 Mon Sep 17 00:00:00 2001 From: philipc2 Date: Tue, 20 Jun 2023 14:04:03 -0500 Subject: [PATCH 47/59] fix import --- uxarray/core/grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uxarray/core/grid.py b/uxarray/core/grid.py index d2bbebe31..7ebf4ed3c 100644 --- a/uxarray/core/grid.py +++ b/uxarray/core/grid.py @@ -15,7 +15,7 @@ parse_grid_type, node_xyz_to_lonlat_rad, node_lonlat_rad_to_xyz, close_face_nodes) -from uxarray.utils.constants import INT_DTYPE, INT_FILL_VALUE, INT_FILL_VALUE_MPZ +from uxarray.utils.constants import INT_DTYPE, INT_FILL_VALUE, INT_FILL_VALUE_MPZ, FLOAT_PRECISION_BITS from uxarray.multi_precision_helpers import convert_to_multiprecision, unique_coordinates_multiprecision, precision_bits_to_decimal_digits, decimal_digits_to_precision_bits From 3bcff547788c4e701673765d76495d843c7d4922 Mon Sep 17 00:00:00 2001 From: philipc2 Date: Tue, 20 Jun 2023 14:10:12 -0500 Subject: [PATCH 48/59] fix ds access --- uxarray/core/grid.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uxarray/core/grid.py b/uxarray/core/grid.py index 7ebf4ed3c..19dd8b5bb 100644 --- a/uxarray/core/grid.py +++ b/uxarray/core/grid.py @@ -358,7 +358,7 @@ def __from_vert__(self, connectivity = indices.reshape(dataset.shape[:-1]) if multi_precision: - self.ds["Mesh2_face_nodes"] = xr.DataArray( + self._ds["Mesh2_face_nodes"] = xr.DataArray( data=xr.DataArray(connectivity).astype(INT_DTYPE), dims=["nMesh2_face", "nMaxMesh2_face_nodes"], attrs={ @@ -367,7 +367,7 @@ def __from_vert__(self, "start_index": 0 }) else: - self.ds["Mesh2_face_nodes"] = xr.DataArray( + self._ds["Mesh2_face_nodes"] = xr.DataArray( data=xr.DataArray(connectivity).astype(INT_DTYPE), dims=["nMesh2_face", "nMaxMesh2_face_nodes"], attrs={ From 630534fe65509a086e6329106da3038e16875a50 Mon Sep 17 00:00:00 2001 From: philipc2 Date: Tue, 20 Jun 2023 14:19:14 -0500 Subject: [PATCH 49/59] fix constants --- uxarray/core/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/uxarray/core/__init__.py b/uxarray/core/__init__.py index e69de29bb..ed7263e10 100644 --- a/uxarray/core/__init__.py +++ b/uxarray/core/__init__.py @@ -0,0 +1 @@ +from uxarray.utils.constants import * From 3f193a1e48799855fd04f97cef25455326c99a45 Mon Sep 17 00:00:00 2001 From: philipc2 Date: Tue, 20 Jun 2023 14:27:33 -0500 Subject: [PATCH 50/59] use correct init --- uxarray/__init__.py | 2 +- uxarray/core/__init__.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/uxarray/__init__.py b/uxarray/__init__.py index b9aedd249..1ef44bb8b 100644 --- a/uxarray/__init__.py +++ b/uxarray/__init__.py @@ -1,5 +1,5 @@ from uxarray.utils.helpers import * -from uxarray.utils.constants import (INT_DTYPE, INT_FILL_VALUE) +from uxarray.utils.constants import * from uxarray.core.grid import Grid from uxarray.core.api import (open_grid, open_dataset, open_mfdataset) from uxarray.core.dataarray import UxDataArray diff --git a/uxarray/core/__init__.py b/uxarray/core/__init__.py index ed7263e10..e69de29bb 100644 --- a/uxarray/core/__init__.py +++ b/uxarray/core/__init__.py @@ -1 +0,0 @@ -from uxarray.utils.constants import * From 7f160b2156a41babed9c347e453346cb1df6547a Mon Sep 17 00:00:00 2001 From: philipc2 Date: Tue, 20 Jun 2023 14:37:43 -0500 Subject: [PATCH 51/59] fix ds --- test/test_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_grid.py b/test/test_grid.py index 17d790031..ca72f235f 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -248,7 +248,7 @@ def test_init_verts_multi_precision_fill_values(self): assert (vgrid.nMesh2_node == 4) # Test the all numbers in the vgird.ds["Mesh2_face_nodes"] are less than 4 - assert (np.all(vgrid.ds["Mesh2_face_nodes"] < 4)) + assert (np.all(vgrid._ds["Mesh2_face_nodes"] < 4)) def test_init_verts_different_input_datatype(self): """Create a uxarray grid from multiple face vertices with different From 60b1720a43482a5f8455cc1f59b61fa0314953fb Mon Sep 17 00:00:00 2001 From: Philip Chmielowiec <67855069+philipc2@users.noreply.github.com> Date: Wed, 21 Jun 2023 18:06:45 -0600 Subject: [PATCH 52/59] Update helpers.py --- uxarray/utils/helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/uxarray/utils/helpers.py b/uxarray/utils/helpers.py index 0f521f8d8..caf18b77c 100644 --- a/uxarray/utils/helpers.py +++ b/uxarray/utils/helpers.py @@ -1,5 +1,6 @@ import numpy as np import xarray as xr +from pathlib import PurePath from .get_quadratureDG import get_gauss_quadratureDG, get_tri_quadratureDG from numba import njit, config import math From a7a0176d582f16c0a0f42bd0785735c57d69740b Mon Sep 17 00:00:00 2001 From: Philip Chmielowiec Date: Thu, 29 Jun 2023 19:03:43 -0600 Subject: [PATCH 53/59] attempt to correct JSON RTD error --- uxarray/utils/helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/uxarray/utils/helpers.py b/uxarray/utils/helpers.py index 0f521f8d8..caf18b77c 100644 --- a/uxarray/utils/helpers.py +++ b/uxarray/utils/helpers.py @@ -1,5 +1,6 @@ import numpy as np import xarray as xr +from pathlib import PurePath from .get_quadratureDG import get_gauss_quadratureDG, get_tri_quadratureDG from numba import njit, config import math From 989607de62f58bc3733beb8a5b4040439762cdd5 Mon Sep 17 00:00:00 2001 From: Philip Chmielowiec Date: Thu, 29 Jun 2023 19:07:56 -0600 Subject: [PATCH 54/59] manually fix corrupted notebook --- docs/examples/003-area-calc.ipynb | 244 +----------------------------- 1 file changed, 4 insertions(+), 240 deletions(-) diff --git a/docs/examples/003-area-calc.ipynb b/docs/examples/003-area-calc.ipynb index 6f4171ae4..c60cebc8d 100644 --- a/docs/examples/003-area-calc.ipynb +++ b/docs/examples/003-area-calc.ipynb @@ -2,16 +2,7 @@ "cells": [ { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [ "# Face Area Calculations\n", "\n", @@ -32,16 +23,7 @@ }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [ "We will be using the `outCSne30.ug` grid file, which is encoded in the UGRID convention." ] @@ -55,13 +37,8 @@ "start_time": "2023-06-09T10:04:28.705400Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [], @@ -80,13 +57,8 @@ "start_time": "2023-06-09T10:04:28.718401Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [ @@ -128,16 +100,7 @@ }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [ "## 1. Calculate Total Face Area\n", "We can calculate the total face area by calling the function `Grid.calculate_total_face_area()`. Since our dataset lies on the unit sphere, our expected area is 4*pi, which is approximately 12.56" @@ -152,13 +115,8 @@ "start_time": "2023-06-09T10:04:28.732401Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [ @@ -180,16 +138,7 @@ }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [ "## 2. Options for `Grid.calculate_total_face_area` Function\n", "\n", @@ -208,16 +157,8 @@ "execution_count": 4, "metadata": { "ExecuteTime": { -<<<<<<< HEAD - "end_time": "2023-04-19T09:57:03.690062Z", - "start_time": "2023-04-19T09:57:03.687612Z" - }, - "pycharm": { - "name": "#%%\n" -======= "end_time": "2023-06-09T10:04:28.821401Z", "start_time": "2023-06-09T10:04:28.777401Z" ->>>>>>> main } }, "outputs": [ @@ -239,11 +180,7 @@ }, { "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, + "metadata": {}, "source": [ "For the result above, notice that the area is slightly different than the first calculation we made.\n", "\n", @@ -256,16 +193,7 @@ }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [ "## 3. Getting Area of Individual Faces\n", "\n", @@ -282,13 +210,8 @@ "start_time": "2023-06-09T10:04:28.810401Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [ @@ -310,16 +233,7 @@ }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [ "Now calculate the area again with the `Grid.compute_face_areas` function using arguments: quadrature_rule \"gaussian\" and order 4" ] @@ -329,16 +243,8 @@ "execution_count": 6, "metadata": { "ExecuteTime": { -<<<<<<< HEAD - "end_time": "2023-04-19T09:57:03.696254Z", - "start_time": "2023-04-19T09:57:03.693676Z" - }, - "pycharm": { - "name": "#%%\n" -======= "end_time": "2023-06-09T10:04:28.904401Z", "start_time": "2023-06-09T10:04:28.896401Z" ->>>>>>> main } }, "outputs": [ @@ -361,26 +267,13 @@ }, { "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, + "metadata": {}, "source": [ "Now we compare the values with actual know value and report error for each of the three cases above." ] }, { "cell_type": "code", -<<<<<<< HEAD - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], -======= "execution_count": 7, "metadata": { "ExecuteTime": { @@ -400,7 +293,6 @@ "output_type": "execute_result" } ], ->>>>>>> main "source": [ "actual_area = 4 * np.pi\n", "diff_t4_area = np.abs(t4_area - actual_area)\n", @@ -415,30 +307,11 @@ "metadata": {}, "source": [ "As we can see, it is clear that the Gaussian Quadrature Rule with Order 4 is the most accurate, and the Triangular Quadrature Rule with Order 1 is the least accurate.\n" -<<<<<<< HEAD - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } -======= ] ->>>>>>> main }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [ "## 4. Calculate Area of a Single Triangle in Cartesian Coordinates\n", "\n", @@ -456,13 +329,8 @@ "start_time": "2023-06-09T10:04:28.921401Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [ @@ -511,13 +379,8 @@ "start_time": "2023-06-09T10:04:28.951402Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [ @@ -538,11 +401,7 @@ }, { "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, + "metadata": {}, "source": [ "Additionally, if you are using a unit other than meters, you can update the units as follows" ] @@ -582,44 +441,16 @@ "area_gaussian = vgrid.calculate_total_face_area(\n", " quadrature_rule=\"gaussian\", order=5)\n", "area_gaussian" -<<<<<<< HEAD - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } -======= ] ->>>>>>> main }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [] }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [ "## 5. Calculate Area from Multiple Faces in Spherical Coordinates\n", "\n", @@ -635,13 +466,8 @@ "start_time": "2023-06-09T10:04:28.984401Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [], @@ -661,17 +487,7 @@ "metadata": {}, "source": [ "We want our units to be spherical, so we pass through `islatlon=True`. Additionally, if `islatlon` is not passed through, it will default to spherical coordinates." -<<<<<<< HEAD - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } -======= ] ->>>>>>> main }, { "cell_type": "code", @@ -682,13 +498,8 @@ "start_time": "2023-06-09T10:04:29.001401Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [ @@ -731,13 +542,8 @@ "start_time": "2023-06-09T10:04:29.034401Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [ @@ -758,11 +564,7 @@ }, { "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, + "metadata": {}, "source": [ "## 6. Area Calculation without Grid Object\n", "\n", @@ -784,41 +586,14 @@ }, "outputs": [], "source": [ -<<<<<<< HEAD - "from uxarray.helpers import calculate_face_area" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } -======= "from uxarray.utils.helpers import calculate_face_area" ] ->>>>>>> main }, { "cell_type": "markdown", "metadata": {}, "source": [ "`Grid.calculate_face_area` takes in three coordinate variables (x, y, z) in the form of numpy arrays and the coordinate type (either spherical or artesian) and computes the face area from the set of points" -<<<<<<< HEAD - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" -======= ] }, { @@ -828,7 +603,6 @@ "ExecuteTime": { "end_time": "2023-06-09T10:04:29.086401Z", "start_time": "2023-06-09T10:04:29.064401Z" ->>>>>>> main } }, "outputs": [], @@ -849,15 +623,6 @@ }, { "cell_type": "code", -<<<<<<< HEAD - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], -======= "execution_count": 20, "metadata": { "ExecuteTime": { @@ -877,7 +642,6 @@ "output_type": "execute_result" } ], ->>>>>>> main "source": [ "calculate_face_area(cart_x, cart_y, cart_z, coords_type=\"cartesian\")" ] From 39ff05eb33e722520a38b59dcb463a27369400c4 Mon Sep 17 00:00:00 2001 From: aazedwick Date: Thu, 13 Jul 2023 11:42:30 -0500 Subject: [PATCH 55/59] Fixed misc. test errors --- test/test_grid.py | 18 +++++----- test/test_helpers.py | 51 ++++++++++++++-------------- test/test_multi_precision_helpers.py | 2 +- uxarray/core/grid.py | 7 ++-- uxarray/multi_precision_helpers.py | 2 +- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/test/test_grid.py b/test/test_grid.py index 09762c47d..48cf88827 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -13,6 +13,8 @@ import uxarray.multi_precision_helpers as mph import numpy.testing as nt +from uxarray.utils.constants import INT_FILL_VALUE_MPZ + try: import constants except ImportError: @@ -228,7 +230,7 @@ def test_init_verts_multi_precision_fill_values(self): # Generate the face nodes connectivity dumb_nodes = [ - ux.INT_FILL_VALUE_MPZ, ux.INT_FILL_VALUE_MPZ, ux.INT_FILL_VALUE_MPZ + INT_FILL_VALUE_MPZ, INT_FILL_VALUE_MPZ, INT_FILL_VALUE_MPZ ] face_nodes_connectivity = np.array([ np.array([nodes[0], nodes[1], nodes[2], dumb_nodes], dtype=object), @@ -249,7 +251,7 @@ def test_init_verts_multi_precision_fill_values(self): assert (vgrid.nMesh2_node == 4) # Test the all numbers in the vgird.ds["Mesh2_face_nodes"] are less than 4 - assert (np.all(vgrid.ds["Mesh2_face_nodes"] < 4)) + assert (np.all(vgrid._ds["Mesh2_face_nodes"] < 4)) def test_init_verts_different_input_datatype(self): """Create a uxarray grid from multiple face vertices with different @@ -421,7 +423,7 @@ def test_calculate_total_face_area_triangle(self): isconcave=False) # calculate area - area_gaussian = vgrid.calculate_total_face_area( + area_gaussian = grid_verts.calculate_total_face_area( quadrature_rule="gaussian", order=5) nt.assert_almost_equal(area_gaussian, constants.TRI_AREA, decimal=3) @@ -579,13 +581,13 @@ def test_populate_cartesian_xyz_coord_mpfr(self): vertices=True) vgrid._populate_cartesian_xyz_coord() for i in range(0, vgrid.nMesh2_node): - nt.assert_almost_equal(vgrid.ds["Mesh2_node_cart_x"].values[i], + nt.assert_almost_equal(vgrid._ds["Mesh2_node_cart_x"].values[i], mpfr(cart_x[i]), decimal=14) - nt.assert_almost_equal(vgrid.ds["Mesh2_node_cart_y"].values[i], + nt.assert_almost_equal(vgrid._ds["Mesh2_node_cart_y"].values[i], mpfr(cart_y[i]), decimal=14) - nt.assert_almost_equal(vgrid.ds["Mesh2_node_cart_z"].values[i], + nt.assert_almost_equal(vgrid._ds["Mesh2_node_cart_z"].values[i], mpfr(cart_z[i]), decimal=14) @@ -660,10 +662,10 @@ def test_populate_lonlat_coord_mpfr(self): islatlon=False) vgrid._populate_lonlat_coord() for i in range(0, vgrid.nMesh2_node): - nt.assert_almost_equal(vgrid.ds["Mesh2_node_x"].values[i], + nt.assert_almost_equal(vgrid._ds["Mesh2_node_x"].values[i], mpfr(lon_deg[i]), decimal=13) - nt.assert_almost_equal(vgrid.ds["Mesh2_node_y"].values[i], + nt.assert_almost_equal(vgrid._ds["Mesh2_node_y"].values[i], mpfr(lat_deg[i]), decimal=13) diff --git a/test/test_helpers.py b/test/test_helpers.py index 8c5ff09e3..ea886b332 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -14,7 +14,8 @@ from uxarray.utils.helpers import _replace_fill_values from uxarray.utils.constants import INT_DTYPE, INT_FILL_VALUE -from uxarray.multi_precision_helpers import convert_to_multiprecision, set_global_precision, decimal_digits_to_precision_bits +from uxarray.multi_precision_helpers import convert_to_multiprecision, set_global_precision, \ + decimal_digits_to_precision_bits from uxarray.utils.helpers import _replace_fill_values from uxarray.utils.constants import INT_DTYPE, INT_FILL_VALUE @@ -124,7 +125,8 @@ def test_normalize_in_place(self): '0.0000000000000000001' ]), precision=precision) - normalized = ux.helpers.normalize_in_place([x_mpfr, y_mpfr, z_mpfr]) + normalized = ux.utils.helpers.normalize_in_place( + [x_mpfr, y_mpfr, z_mpfr]) # Calculate the sum of squares using gmpy2.fsum() sum_of_squares = gmpy2.fsum( [gmpy2.square(value) for value in normalized]) @@ -159,8 +161,8 @@ def test_node_xyz_to_lonlat_rad(self): '0.000000000000000000001' ]), precision=precision) - [lon_mpfr, - lat_mpfr] = ux.helpers.node_xyz_to_lonlat_rad([x_mpfr, y_mpfr, z_mpfr]) + [lon_mpfr, lat_mpfr + ] = ux.utils.helpers.node_xyz_to_lonlat_rad([x_mpfr, y_mpfr, z_mpfr]) self.assertAlmostEqual(lon_mpfr, 0, places=19) self.assertAlmostEqual(lat_mpfr, 0, places=19) @@ -171,8 +173,8 @@ def test_node_xyz_to_lonlat_rad(self): '0.0000000000000000000' ]), precision=precision) - [lon_mpfr, - lat_mpfr] = ux.helpers.node_xyz_to_lonlat_rad([x_mpfr, y_mpfr, z_mpfr]) + [lon_mpfr, lat_mpfr + ] = ux.utils.helpers.node_xyz_to_lonlat_rad([x_mpfr, y_mpfr, z_mpfr]) self.assertTrue(gmpy2.cmp(lon_mpfr, mpfr('0')) == 0) self.assertTrue(gmpy2.cmp(lat_mpfr, mpfr('0')) == 0) @@ -200,7 +202,7 @@ def test_node_latlon_rad_to_xyz(self): ['0.000000000000000000001', '0.000000000000000000001']), precision=precision) [x_mpfr, y_mpfr, - z_mpfr] = ux.helpers.node_lonlat_rad_to_xyz([lon_mpfr, lat_mpfr]) + z_mpfr] = ux.utils.helpers.node_lonlat_rad_to_xyz([lon_mpfr, lat_mpfr]) self.assertAlmostEqual(x_mpfr, 1, places=19) self.assertAlmostEqual(y_mpfr, 0, places=19) self.assertAlmostEqual(z_mpfr, 0, places=19) @@ -210,7 +212,7 @@ def test_node_latlon_rad_to_xyz(self): ['0.0000000000000000000', '0.0000000000000000000']), precision=precision) [x_mpfr, y_mpfr, - z_mpfr] = ux.helpers.node_lonlat_rad_to_xyz([lon_mpfr, lat_mpfr]) + z_mpfr] = ux.utils.helpers.node_lonlat_rad_to_xyz([lon_mpfr, lat_mpfr]) self.assertTrue(gmpy2.cmp(x_mpfr, mpfr('1')) == 0) self.assertTrue(gmpy2.cmp(y_mpfr, mpfr('0')) == 0) self.assertTrue(gmpy2.cmp(z_mpfr, mpfr('0')) == 0) @@ -226,7 +228,7 @@ def test_precise_coordinates_conversion(self): set_global_precision(precision) # The initial coordinates - [init_x, init_y, init_z] = ux.helpers.normalize_in_place([ + [init_x, init_y, init_z] = ux.utils.helpers.normalize_in_place([ mpfr('0.12345678910111213149'), mpfr('0.92345678910111213149'), mpfr('1.72345678910111213149') @@ -235,10 +237,10 @@ def test_precise_coordinates_conversion(self): new_y = init_y new_z = init_z for iter in range(1000): - [new_lon, - new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) - [new_x, new_y, - new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat]) + [new_lon, new_lat + ] = ux.utils.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) + [new_x, new_y, new_z + ] = ux.utils.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat]) self.assertAlmostEqual(new_x, init_x, places=19) self.assertAlmostEqual(new_y, init_y, places=19) self.assertAlmostEqual(new_z, init_z, places=19) @@ -252,10 +254,10 @@ def test_precise_coordinates_conversion(self): new_lat = init_lat new_lon = init_lon for iter in range(1000): - [new_x, new_y, - new_z] = ux.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat]) - [new_lon, - new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) + [new_x, new_y, new_z + ] = ux.utils.helpers.node_lonlat_rad_to_xyz([new_lon, new_lat]) + [new_lon, new_lat + ] = ux.utils.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) self.assertAlmostEqual(new_lon, init_lon, places=19) self.assertAlmostEqual(new_lat, init_lat, places=19) @@ -274,11 +276,10 @@ def test_coordinates_conversion_accumulate_error(self): start_time = time.time() for iter in range(run_time): - [new_x, new_y, new_z - ] = ux.helpers.node_lonlat_rad_to_xyz(np.deg2rad([new_lon, - new_lat])) - [new_lon, - new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) + [new_x, new_y, new_z] = ux.utils.helpers.node_lonlat_rad_to_xyz( + np.deg2rad([new_lon, new_lat])) + [new_lon, new_lat + ] = ux.utils.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) [new_lon, new_lat] = np.rad2deg([new_lon, new_lat]) end_time = time.time() @@ -303,10 +304,10 @@ def test_coordinates_conversion_accumulate_error(self): start_time = time.time() for iter in range(run_time): - [new_x, new_y, new_z] = ux.helpers.node_lonlat_rad_to_xyz( + [new_x, new_y, new_z] = ux.utils.helpers.node_lonlat_rad_to_xyz( [gmpy2.radians(val) for val in [new_lon, new_lat]]) - [new_lon, - new_lat] = ux.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) + [new_lon, new_lat + ] = ux.utils.helpers.node_xyz_to_lonlat_rad([new_x, new_y, new_z]) [new_lon, new_lat] = [gmpy2.degrees(val) for val in [new_lon, new_lat]] end_time = time.time() diff --git a/test/test_multi_precision_helpers.py b/test/test_multi_precision_helpers.py index c2414ba2e..48ecb3a6f 100644 --- a/test/test_multi_precision_helpers.py +++ b/test/test_multi_precision_helpers.py @@ -8,7 +8,7 @@ import uxarray as ux -from uxarray.constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ +from uxarray.utils.constants import INT_DTYPE, INT_FILL_VALUE, FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ from uxarray.multi_precision_helpers import convert_to_multiprecision, unique_coordinates_multiprecision, precision_bits_to_decimal_digits, decimal_digits_to_precision_bits import math diff --git a/uxarray/core/grid.py b/uxarray/core/grid.py index 6bc38b202..a28428677 100644 --- a/uxarray/core/grid.py +++ b/uxarray/core/grid.py @@ -348,7 +348,7 @@ def __from_vert__(self, dataset): # Create connectivity array using indices of unique vertices connectivity = indices.reshape(dataset.shape[:-1]) if self._multi_precision: - self.ds["Mesh2_face_nodes"] = xr.DataArray( + self._ds["Mesh2_face_nodes"] = xr.DataArray( data=xr.DataArray(connectivity).astype(INT_DTYPE), dims=["nMesh2_face", "nMaxMesh2_face_nodes"], attrs={ @@ -357,7 +357,7 @@ def __from_vert__(self, dataset): "start_index": 0 }) else: - self.ds["Mesh2_face_nodes"] = xr.DataArray( + self._ds["Mesh2_face_nodes"] = xr.DataArray( data=xr.DataArray(connectivity).astype(INT_DTYPE), dims=["nMesh2_face", "nMaxMesh2_face_nodes"], attrs={ @@ -1141,6 +1141,3 @@ def _build_nNodes_per_face(self): data=nNodes_per_face, dims=["nMesh2_face"], attrs={"long_name": "number of non-fill value nodes for each face"}) - - # standardized attribute - setattr(self, "nNodes_per_face", self.ds["nNodes_per_face"]) diff --git a/uxarray/multi_precision_helpers.py b/uxarray/multi_precision_helpers.py index 5b75835f3..66af712c6 100644 --- a/uxarray/multi_precision_helpers.py +++ b/uxarray/multi_precision_helpers.py @@ -2,7 +2,7 @@ from gmpy2 import mpfr, mpz import numpy as np import math -from .constants import FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ +from uxarray.utils.constants import FLOAT_PRECISION_BITS, INT_FILL_VALUE_MPZ def set_global_precision(global_precision=FLOAT_PRECISION_BITS): From f2a489d23570353436e09b60f0e6db3c43e945cf Mon Sep 17 00:00:00 2001 From: aazedwick Date: Thu, 13 Jul 2023 12:07:19 -0500 Subject: [PATCH 56/59] Fixed pre-commit --- docs/examples/004-multiprecision-usage.ipynb | 262 ------------------- docs/user_api/index.rst | 1 - uxarray/core/grid.py | 2 +- 3 files changed, 1 insertion(+), 264 deletions(-) delete mode 100644 docs/examples/004-multiprecision-usage.ipynb diff --git a/docs/examples/004-multiprecision-usage.ipynb b/docs/examples/004-multiprecision-usage.ipynb deleted file mode 100644 index e412cd9a7..000000000 --- a/docs/examples/004-multiprecision-usage.ipynb +++ /dev/null @@ -1,262 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "# Multiprecision Arithmetic UXArray\n", - "In this notebook we will explore the use of the UXArray which supports multiprecision arithmetic." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "import numpy as np\n", - "import uxarray as ux\n", - "import gmpy2\n", - "from gmpy2 import mpfr, mpz" - ] - }, - { - "cell_type": "markdown", - "source": [ - "# Using GMPY2.mpfr type for Multiprecision Arithmetic\n", - "The distance between any two nodes is less than 1e-17 (the precision of the nodes is 60 bits, which is larger than the the python `float` type precision of 53 bits)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "# Generate the nodes on the unit sphere, and the nodes are extremely close to each other: the edge length\n", - "# between any two nodes is less than 1e-17 (the precision of the nodes is 60 bits but we will use 65 bits for\n", - "# better demonstration)\n", - "nodes = []\n", - "for i in range(4):\n", - " theta = gmpy2.div(i * 2 * gmpy2.const_pi(precision=65),\n", - " 4) # Angle in radians\n", - " x = gmpy2.cos(theta)\n", - " y = gmpy2.sin(theta)\n", - " z = mpfr(\n", - " '0') # All nodes will have z-coordinate as 0 on the unit sphere\n", - " nodes.append([x, y, z])\n", - "\n", - "# Generate the face nodes connectivity\n", - "dumb_nodes = [\n", - " ux.INT_FILL_VALUE_MPZ, ux.INT_FILL_VALUE_MPZ, ux.INT_FILL_VALUE_MPZ\n", - "]\n", - "\n", - "face_nodes_connectivity = np.array([\n", - " np.array([nodes[0], nodes[1], nodes[2], dumb_nodes], dtype=object),\n", - " np.array([nodes[0], nodes[2], nodes[3], dumb_nodes], dtype=object),\n", - " np.array([nodes[0], nodes[3], nodes[1], dumb_nodes], dtype=object),\n", - " np.array([nodes[1], nodes[2], nodes[3], nodes[0]], dtype=object)\n", - "], dtype=object)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "Now we can see this topology has 4 faces and 4 nodes. Three of the facess have 3 nodes and one face has 4 nodes. So we also use the\n", - "fill value `ux.INT_FILL_VALUE_MPZ` to fill the fourth node of the first three faces." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "face_nodes_connectivity" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## Construct the Grid object with the multiprecision mode on" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "# Create the grid\n", - "vgrid = ux.Grid(face_nodes_connectivity,\n", - " multi_precision=True,\n", - " precision=65,\n", - " vertices=True,\n", - " islatlon=False,\n", - " concave=False)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## Check the Grid Information\n", - "We can see that even though the input nodes are extremely close to each other, the grid is still valid and not degenerated:\n", - "The face number is still 4 and the node number is still 4." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "print(vgrid.nMesh2_face)\n", - "print(vgrid.nMesh2_node)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# Using python string type for Multiprecision Arithmetic\n", - "You can also input the coordinates as string. The string will be converted to the mpfr type." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "# Provide nodes that are extremely closed to each other\n", - "face_nodes_connectivity = [\n", - " ['1.000000000000000000', '1.000000000000000000001', '1.0000000000000000000012'],\n", - " ['1.000000000000000001', '1.000000000000000000002', '1.0000000000000000000013'],\n", - " ['1.00000000000000000001', '1.00000000000000000000', '1.0000000000000000000013']]\n", - "\n", - "uxgrid = ux.Grid(face_nodes_connectivity, multi_precision=True, precision= 100,\n", - " vertices=True,\n", - " islatlon=False,\n", - " concave=False)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## Check the Grid Information\n", - "Although the input nodes are extremely close to each other, the grid is still valid and not degenerated:\n", - "The face number is still 1 and the node number is still 3." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "print(uxgrid.nMesh2_face)\n", - "print(uxgrid.nMesh2_node)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3.9.13 ('uxarray-docs')", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.13" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "5df6a94ef43b22c97c277f6a9dce3b546a324b9de41c6d8e82aecf8eafd14442" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/user_api/index.rst b/docs/user_api/index.rst index 19d9d98bb..e06856e38 100644 --- a/docs/user_api/index.rst +++ b/docs/user_api/index.rst @@ -190,4 +190,3 @@ Multi-Precision multi_precision_helpers.unique_coordinates_multiprecision multi_precision_helpers.decimal_digits_to_precision_bits multi_precision_helpers.precision_bits_to_decimal_digits - \ No newline at end of file diff --git a/uxarray/core/grid.py b/uxarray/core/grid.py index d69672ae0..4120658ad 100644 --- a/uxarray/core/grid.py +++ b/uxarray/core/grid.py @@ -353,7 +353,7 @@ def __from_vert__(self, # Create connectivity array using indices of unique vertices connectivity = indices.reshape(dataset.shape[:-1]) - + if self._multi_precision: self._ds["Mesh2_face_nodes"] = xr.DataArray( data=xr.DataArray(connectivity).astype(INT_DTYPE), From 638f3045df057dc3fff74711252c942d1b8815c0 Mon Sep 17 00:00:00 2001 From: aazedwick Date: Thu, 13 Jul 2023 13:28:20 -0500 Subject: [PATCH 57/59] Notebook edits --- docs/examples/001-read-grid-data.ipynb | 1511 +++++++++++++++++++++- docs/examples/002-access-grid-info.ipynb | 222 ++-- 2 files changed, 1562 insertions(+), 171 deletions(-) diff --git a/docs/examples/001-read-grid-data.ipynb b/docs/examples/001-read-grid-data.ipynb index c7b94875c..343698ab3 100644 --- a/docs/examples/001-read-grid-data.ipynb +++ b/docs/examples/001-read-grid-data.ipynb @@ -17,6 +17,12 @@ }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + }, "source": [ "## Overview\n", "\n", @@ -35,29 +41,23 @@ "│ v2.nc\n", "│ v3.nc\n", "```" - ], + ] + }, + { + "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } - } - }, - { - "cell_type": "markdown", + }, "source": [ "## Opening The Grid and Data Files with Xarray\n", "\n", "First, read in the grid definition and data variable netCCDF files by using\n", "`xarray.open_dataset`. In addition, `xr.open_mf_dataset` can be used for\n", "combining multiple data variables into a single `xarray.Dataset`." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } + ] }, { "cell_type": "code", @@ -124,11 +124,404 @@ { "cell_type": "code", "execution_count": 7, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [ { "data": { - "text/plain": "\nDimensions: (nMeshFaces: 3840, nFaceNodes: 4, nMeshNodes: 6000,\n meshLayers: 20)\nCoordinates:\n mesh_node_x (nMeshNodes) float64 ...\n mesh_node_y (nMeshNodes) float64 ...\nDimensions without coordinates: nMeshFaces, nFaceNodes, nMeshNodes, meshLayers\nData variables:\n mesh int32 ...\n mesh_face_nodes (nMeshFaces, nFaceNodes) float64 ...\n mesh_depth (meshLayers, nMeshNodes) float64 ...", - "text/html": "
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
<xarray.Dataset>\nDimensions:          (nMeshFaces: 3840, nFaceNodes: 4, nMeshNodes: 6000,\n                      meshLayers: 20)\nCoordinates:\n    mesh_node_x      (nMeshNodes) float64 ...\n    mesh_node_y      (nMeshNodes) float64 ...\nDimensions without coordinates: nMeshFaces, nFaceNodes, nMeshNodes, meshLayers\nData variables:\n    mesh             int32 ...\n    mesh_face_nodes  (nMeshFaces, nFaceNodes) float64 ...\n    mesh_depth       (meshLayers, nMeshNodes) float64 ...
" + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset>\n",
+       "Dimensions:          (nMeshFaces: 3840, nFaceNodes: 4, nMeshNodes: 6000,\n",
+       "                      meshLayers: 20)\n",
+       "Coordinates:\n",
+       "    mesh_node_x      (nMeshNodes) float64 ...\n",
+       "    mesh_node_y      (nMeshNodes) float64 ...\n",
+       "Dimensions without coordinates: nMeshFaces, nFaceNodes, nMeshNodes, meshLayers\n",
+       "Data variables:\n",
+       "    mesh             int32 ...\n",
+       "    mesh_face_nodes  (nMeshFaces, nFaceNodes) float64 ...\n",
+       "    mesh_depth       (meshLayers, nMeshNodes) float64 ...
" + ], + "text/plain": [ + "\n", + "Dimensions: (nMeshFaces: 3840, nFaceNodes: 4, nMeshNodes: 6000,\n", + " meshLayers: 20)\n", + "Coordinates:\n", + " mesh_node_x (nMeshNodes) float64 ...\n", + " mesh_node_y (nMeshNodes) float64 ...\n", + "Dimensions without coordinates: nMeshFaces, nFaceNodes, nMeshNodes, meshLayers\n", + "Data variables:\n", + " mesh int32 ...\n", + " mesh_face_nodes (nMeshFaces, nFaceNodes) float64 ...\n", + " mesh_depth (meshLayers, nMeshNodes) float64 ..." + ] }, "execution_count": 7, "metadata": {}, @@ -137,22 +530,639 @@ ], "source": [ "grid_ds" - ], + ] + }, + { + "cell_type": "code", + "execution_count": 8, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } - } - }, - { - "cell_type": "code", - "execution_count": 8, + }, "outputs": [ { "data": { - "text/plain": "\nDimensions: (time: 1, meshLayers: 20, nMeshNodes: 6000)\nCoordinates:\n * time (time) float64 13.0\nDimensions without coordinates: meshLayers, nMeshNodes\nData variables:\n v1 (time, meshLayers, nMeshNodes) float64 dask.array\n v2 (time, meshLayers, nMeshNodes) float64 dask.array\n v3 (time, meshLayers, nMeshNodes) float64 dask.array", - "text/html": "
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
<xarray.Dataset>\nDimensions:  (time: 1, meshLayers: 20, nMeshNodes: 6000)\nCoordinates:\n  * time     (time) float64 13.0\nDimensions without coordinates: meshLayers, nMeshNodes\nData variables:\n    v1       (time, meshLayers, nMeshNodes) float64 dask.array<chunksize=(1, 20, 6000), meta=np.ndarray>\n    v2       (time, meshLayers, nMeshNodes) float64 dask.array<chunksize=(1, 20, 6000), meta=np.ndarray>\n    v3       (time, meshLayers, nMeshNodes) float64 dask.array<chunksize=(1, 20, 6000), meta=np.ndarray>
" + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset>\n",
+       "Dimensions:  (time: 1, meshLayers: 20, nMeshNodes: 6000)\n",
+       "Coordinates:\n",
+       "  * time     (time) float64 13.0\n",
+       "Dimensions without coordinates: meshLayers, nMeshNodes\n",
+       "Data variables:\n",
+       "    v1       (time, meshLayers, nMeshNodes) float64 dask.array<chunksize=(1, 20, 6000), meta=np.ndarray>\n",
+       "    v2       (time, meshLayers, nMeshNodes) float64 dask.array<chunksize=(1, 20, 6000), meta=np.ndarray>\n",
+       "    v3       (time, meshLayers, nMeshNodes) float64 dask.array<chunksize=(1, 20, 6000), meta=np.ndarray>
" + ], + "text/plain": [ + "\n", + "Dimensions: (time: 1, meshLayers: 20, nMeshNodes: 6000)\n", + "Coordinates:\n", + " * time (time) float64 13.0\n", + "Dimensions without coordinates: meshLayers, nMeshNodes\n", + "Data variables:\n", + " v1 (time, meshLayers, nMeshNodes) float64 dask.array\n", + " v2 (time, meshLayers, nMeshNodes) float64 dask.array\n", + " v3 (time, meshLayers, nMeshNodes) float64 dask.array" + ] }, "execution_count": 8, "metadata": {}, @@ -161,63 +1171,450 @@ ], "source": [ "multi_data_ds" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } + ] }, { "cell_type": "markdown", - "source": [ - "## Representing The Unstructured Grid with UXarray" - ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } - } + }, + "source": [ + "## Representing The Unstructured Grid with UXarray" + ] }, { "cell_type": "code", "execution_count": 9, - "outputs": [], - "source": [ - "import uxarray as ux" - ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } - } + }, + "outputs": [], + "source": [ + "import uxarray as ux" + ] }, { "cell_type": "code", "execution_count": 10, - "outputs": [], - "source": [ - "# Construct a UXarray Grid object from `grid_ds`\n", - "grid = ux.Grid(grid_ds)" - ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } - } + }, + "outputs": [], + "source": [ + "# Construct a UXarray Grid object from `grid_ds`\n", + "grid = ux.Grid(grid_ds)" + ] }, { "cell_type": "code", "execution_count": 11, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [ { "data": { - "text/plain": "\nDimensions: (nMeshFaces: 3840, nFaceNodes: 4, nMeshNodes: 6000,\n meshLayers: 20)\nCoordinates:\n mesh_node_x (nMeshNodes) float64 ...\n mesh_node_y (nMeshNodes) float64 ...\nDimensions without coordinates: nMeshFaces, nFaceNodes, nMeshNodes, meshLayers\nData variables:\n mesh int32 ...\n mesh_face_nodes (nMeshFaces, nFaceNodes) float64 ...\n mesh_depth (meshLayers, nMeshNodes) float64 ...", - "text/html": "
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
<xarray.Dataset>\nDimensions:          (nMeshFaces: 3840, nFaceNodes: 4, nMeshNodes: 6000,\n                      meshLayers: 20)\nCoordinates:\n    mesh_node_x      (nMeshNodes) float64 ...\n    mesh_node_y      (nMeshNodes) float64 ...\nDimensions without coordinates: nMeshFaces, nFaceNodes, nMeshNodes, meshLayers\nData variables:\n    mesh             int32 ...\n    mesh_face_nodes  (nMeshFaces, nFaceNodes) float64 ...\n    mesh_depth       (meshLayers, nMeshNodes) float64 ...
" + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset>\n",
+       "Dimensions:          (nMeshFaces: 3840, nFaceNodes: 4, nMeshNodes: 6000,\n",
+       "                      meshLayers: 20)\n",
+       "Coordinates:\n",
+       "    mesh_node_x      (nMeshNodes) float64 ...\n",
+       "    mesh_node_y      (nMeshNodes) float64 ...\n",
+       "Dimensions without coordinates: nMeshFaces, nFaceNodes, nMeshNodes, meshLayers\n",
+       "Data variables:\n",
+       "    mesh             int32 ...\n",
+       "    mesh_face_nodes  (nMeshFaces, nFaceNodes) float64 ...\n",
+       "    mesh_depth       (meshLayers, nMeshNodes) float64 ...
" + ], + "text/plain": [ + "\n", + "Dimensions: (nMeshFaces: 3840, nFaceNodes: 4, nMeshNodes: 6000,\n", + " meshLayers: 20)\n", + "Coordinates:\n", + " mesh_node_x (nMeshNodes) float64 ...\n", + " mesh_node_y (nMeshNodes) float64 ...\n", + "Dimensions without coordinates: nMeshFaces, nFaceNodes, nMeshNodes, meshLayers\n", + "Data variables:\n", + " mesh int32 ...\n", + " mesh_face_nodes (nMeshFaces, nFaceNodes) float64 ...\n", + " mesh_depth (meshLayers, nMeshNodes) float64 ..." + ] }, "execution_count": 11, "metadata": {}, @@ -225,29 +1622,23 @@ } ], "source": [ - "grid.ds" - ], + "grid._ds" + ] + }, + { + "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n" + "name": "#%% md\n" } - } - }, - { - "cell_type": "markdown", + }, "source": [ "As can be seen, the Grid object has its own `grid.ds` of type `xarray.Dataset`\n", "to define the grid topology. However, the Grid object has further attributes,\n", "variables, and functions to specify the unstructured grid and be executed on\n", "it, which can be explored in the next notebooks." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } + ] } ], "metadata": { diff --git a/docs/examples/002-access-grid-info.ipynb b/docs/examples/002-access-grid-info.ipynb index ed274d352..d8103a173 100644 --- a/docs/examples/002-access-grid-info.ipynb +++ b/docs/examples/002-access-grid-info.ipynb @@ -2,6 +2,12 @@ "cells": [ { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + }, "source": [ "# Accessing Grid Information\n", "\n", @@ -33,16 +39,16 @@ "1. Indexing with Original Variable Names\n", "2. Indexing with UXarray Variable Dictionary\n", "3. UXarray's Standardized UGRID Names (Most convenient)" - ], + ] + }, + { + "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } - } - }, - { - "cell_type": "markdown", + }, "source": [ "## Data\n", "\n", @@ -51,32 +57,32 @@ "attributes and variables.\n", "\n", "Let us first read in the data:" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } + ] }, { "cell_type": "code", "execution_count": 4, - "outputs": [], - "source": [ - "import uxarray as ux\n", - "import xarray as xr" - ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } - } + }, + "outputs": [], + "source": [ + "import uxarray as ux\n", + "import xarray as xr" + ] }, { "cell_type": "code", "execution_count": 5, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [], "source": [ "# Base data path\n", @@ -92,30 +98,30 @@ "\n", "ugrid_01 = ux.Grid(ugrid_01_ds)\n", "ugrid_02 = ux.Grid(ugrid_02_ds)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } + ] }, { "cell_type": "markdown", - "source": [ - "The output of the bottom cell showcases the slight differences\n", - "in variable names:" - ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } - } + }, + "source": [ + "The output of the bottom cell showcases the slight differences\n", + "in variable names:" + ] }, { "cell_type": "code", "execution_count": 16, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [ { "name": "stdout", @@ -130,69 +136,69 @@ ], "source": [ "# Extract variable names\n", - "ugrid_01_names = list(ugrid_01.ds.keys()) + \\\n", - " list(ugrid_01.ds.coords) + \\\n", - " list(ugrid_01.ds.dims)\n", - "ugrid_02_names = list(ugrid_02.ds.keys()) + \\\n", - " list(ugrid_02.ds.coords) + \\\n", - " list(ugrid_02.ds.dims)\n", + "ugrid_01_names = list(ugrid_01._ds.keys()) + \\\n", + " list(ugrid_01._ds.coords) + \\\n", + " list(ugrid_01._ds.dims)\n", + "ugrid_02_names = list(ugrid_02._ds.keys()) + \\\n", + " list(ugrid_02._ds.coords) + \\\n", + " list(ugrid_02._ds.dims)\n", "\n", "print(\"\\nAttribute and variable names for each grid:\")\n", "print(\"ugrid_01 variable names:\", ugrid_01_names)\n", "print(\"ugrid_02 variable names:\", ugrid_02_names)" - ], + ] + }, + { + "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n" + "name": "#%% md\n" } - } - }, - { - "cell_type": "markdown", + }, "source": [ "## 1. Indexing with Original Variable Names\n", "\n", "The simplest approach is to use the original variable name as an index\n", - "into the grid dataset, `Grid.ds`. Since `ugrid_01` and `ugrid_02` have\n", + "into the grid dataset, `Grid._ds`. Since `ugrid_01` and `ugrid_02` have\n", "different names for most of their topology attributes and variables, the\n", "index for accessing them will be different for both grids." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } + ] }, { "cell_type": "code", "execution_count": 20, - "outputs": [], - "source": [ - "x_1 = ugrid_01.ds['Mesh2_node_x']\n", - "y_1 = ugrid_01.ds['Mesh2_node_y']\n", - "face_nodes_1 = ugrid_01.ds['Mesh2_face_nodes']\n", - "n_face_nodes_1 = ugrid_01.ds['nMaxMesh2_face_nodes']\n", - "\n", - "x_2 = ugrid_02.ds['mesh_node_x']\n", - "y_2 = ugrid_02.ds['mesh_node_y']\n", - "face_nodes_2 = ugrid_02.ds['mesh_face_nodes']\n", - "n_face_nodes_2 = ugrid_02.ds['nFaceNodes']" - ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } - } + }, + "outputs": [], + "source": [ + "x_1 = ugrid_01._ds['Mesh2_node_x']\n", + "y_1 = ugrid_01._ds['Mesh2_node_y']\n", + "face_nodes_1 = ugrid_01._ds['Mesh2_face_nodes']\n", + "n_face_nodes_1 = ugrid_01._ds['nMaxMesh2_face_nodes']\n", + "\n", + "x_2 = ugrid_02._ds['mesh_node_x']\n", + "y_2 = ugrid_02._ds['mesh_node_y']\n", + "face_nodes_2 = ugrid_02._ds['mesh_face_nodes']\n", + "n_face_nodes_2 = ugrid_02._ds['nFaceNodes']" + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + }, "source": [ "## 2. Indexing with UXarray Variable Dictionary\n", "\n", - "UXarray provides a dictionary, `Grid.ds_var_names`, to map the original\n", + "UXarray provides a dictionary, `Grid._ds_var_names`, to map the original\n", "topology attribute and variable names that come from the grid file into\n", "a standardized set of names. In other words, the dictionary uses a\n", "standardized set of UGRID attribute and variable names as keys, and the\n", @@ -200,76 +206,76 @@ "\n", "This allows us to use the same index into either dataset. However, this\n", "makes the indexing code much longer than the previous method." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } + ] }, { "cell_type": "code", "execution_count": 24, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [], "source": [ "var_names_dict = ugrid_01.ds_var_names\n", - "x_1 = ugrid_01.ds[var_names_dict['Mesh2_node_x']]\n", - "y_1 = ugrid_01.ds[var_names_dict['Mesh2_node_y']]\n", - "face_nodes_1 = ugrid_01.ds[var_names_dict['Mesh2_face_nodes']]\n", - "n_face_nodes_1 = ugrid_01.ds[var_names_dict['nMesh2_node']]\n", + "x_1 = ugrid_01._ds[var_names_dict['Mesh2_node_x']]\n", + "y_1 = ugrid_01._ds[var_names_dict['Mesh2_node_y']]\n", + "face_nodes_1 = ugrid_01._ds[var_names_dict['Mesh2_face_nodes']]\n", + "n_face_nodes_1 = ugrid_01._ds[var_names_dict['nMesh2_node']]\n", "\n", "var_names_dict = ugrid_02.ds_var_names\n", - "x_2 = ugrid_02.ds[var_names_dict['Mesh2_node_x']]\n", - "y_2 = ugrid_02.ds[var_names_dict['Mesh2_node_y']]\n", - "face_nodes_2 = ugrid_02.ds[var_names_dict['Mesh2_face_nodes']]\n", - "n_face_nodes_2 = ugrid_02.ds[var_names_dict['nMesh2_node']]" - ], + "x_2 = ugrid_02._ds[var_names_dict['Mesh2_node_x']]\n", + "y_2 = ugrid_02._ds[var_names_dict['Mesh2_node_y']]\n", + "face_nodes_2 = ugrid_02._ds[var_names_dict['Mesh2_face_nodes']]\n", + "n_face_nodes_2 = ugrid_02._ds[var_names_dict['nMesh2_node']]" + ] + }, + { + "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n" + "name": "#%% md\n" } - } - }, - { - "cell_type": "markdown", + }, "source": [ "Please note, for instance, we accessed the actual variable `mesh_node_x`\n", "of `ugrid_02` via indexing the dictionary with the standardized name\n", "`Mesh2_node_x`, likewise in `ugrid_01`." - ], + ] + }, + { + "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } - } - }, - { - "cell_type": "markdown", + }, "source": [ "## 3. UXarray's Standardized UGRID Names\n", "The last way of accessing grid topology attributes and variables is to use\n", "the standardized UGRID namings provided by UXarray. This method still\n", "utilizes the dictionary, `ds_var_names`, under the hood to return a\n", "reference to the variable or attribute that is stored withing\n", - "`UXarray.Grid.ds`.\n", + "`UXarray.Grid._ds`.\n", "\n", "This eliminates the need to remember the original variable names and\n", "needing to index through the `ds_var_names` dictionary. Because of this,\n", "we find this as the most convenient approach." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } + ] }, { "cell_type": "code", "execution_count": 22, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [], "source": [ "x_1 = ugrid_01.Mesh2_node_x\n", @@ -281,28 +287,22 @@ "y_2 = ugrid_02.Mesh2_node_y\n", "face_nodes_2 = ugrid_02.Mesh2_face_nodes\n", "n_face_nodes_2 = ugrid_02.nMesh2_node" - ], + ] + }, + { + "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n" + "name": "#%% md\n" } - } - }, - { - "cell_type": "markdown", + }, "source": [ "In conclusion, there are three ways of accessing the grid attributes and\n", "variables. Even though the UXarray developers recommend using the\n", "standardized UGRID names method, users can find each various pros/cons with\n", "each of these access ways." - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } + ] } ], "metadata": { From c9cf23800b489e9f824e7e68cfa0c5bf691ae03b Mon Sep 17 00:00:00 2001 From: aazedwick Date: Fri, 14 Jul 2023 10:13:48 -0500 Subject: [PATCH 58/59] Fixed corrupted notebook --- docs/examples/002-access-grid-info.ipynb | 4 +- docs/examples/003-area-calc.ipynb | 210 ----------------------- test/test_grid.py | 4 +- 3 files changed, 3 insertions(+), 215 deletions(-) diff --git a/docs/examples/002-access-grid-info.ipynb b/docs/examples/002-access-grid-info.ipynb index d8103a173..66277e5fc 100644 --- a/docs/examples/002-access-grid-info.ipynb +++ b/docs/examples/002-access-grid-info.ipynb @@ -219,13 +219,13 @@ }, "outputs": [], "source": [ - "var_names_dict = ugrid_01.ds_var_names\n", + "var_names_dict = ugrid_01.grid_var_names\n", "x_1 = ugrid_01._ds[var_names_dict['Mesh2_node_x']]\n", "y_1 = ugrid_01._ds[var_names_dict['Mesh2_node_y']]\n", "face_nodes_1 = ugrid_01._ds[var_names_dict['Mesh2_face_nodes']]\n", "n_face_nodes_1 = ugrid_01._ds[var_names_dict['nMesh2_node']]\n", "\n", - "var_names_dict = ugrid_02.ds_var_names\n", + "var_names_dict = ugrid_02.grid_var_names\n", "x_2 = ugrid_02._ds[var_names_dict['Mesh2_node_x']]\n", "y_2 = ugrid_02._ds[var_names_dict['Mesh2_node_y']]\n", "face_nodes_2 = ugrid_02._ds[var_names_dict['Mesh2_face_nodes']]\n", diff --git a/docs/examples/003-area-calc.ipynb b/docs/examples/003-area-calc.ipynb index 6f4171ae4..275043bc3 100644 --- a/docs/examples/003-area-calc.ipynb +++ b/docs/examples/003-area-calc.ipynb @@ -2,16 +2,12 @@ "cells": [ { "cell_type": "markdown", -<<<<<<< HEAD "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, -======= - "metadata": {}, ->>>>>>> main "source": [ "# Face Area Calculations\n", "\n", @@ -32,16 +28,12 @@ }, { "cell_type": "markdown", -<<<<<<< HEAD "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, -======= - "metadata": {}, ->>>>>>> main "source": [ "We will be using the `outCSne30.ug` grid file, which is encoded in the UGRID convention." ] @@ -55,13 +47,8 @@ "start_time": "2023-06-09T10:04:28.705400Z" }, "collapsed": false, -<<<<<<< HEAD "pycharm": { "name": "#%%\n" -======= - "jupyter": { - "outputs_hidden": false ->>>>>>> main } }, "outputs": [], @@ -80,13 +67,8 @@ "start_time": "2023-06-09T10:04:28.718401Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [ @@ -128,16 +110,7 @@ }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [ "## 1. Calculate Total Face Area\n", "We can calculate the total face area by calling the function `Grid.calculate_total_face_area()`. Since our dataset lies on the unit sphere, our expected area is 4*pi, which is approximately 12.56" @@ -152,13 +125,8 @@ "start_time": "2023-06-09T10:04:28.732401Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [ @@ -180,16 +148,7 @@ }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [ "## 2. Options for `Grid.calculate_total_face_area` Function\n", "\n", @@ -208,16 +167,8 @@ "execution_count": 4, "metadata": { "ExecuteTime": { -<<<<<<< HEAD - "end_time": "2023-04-19T09:57:03.690062Z", - "start_time": "2023-04-19T09:57:03.687612Z" - }, - "pycharm": { - "name": "#%%\n" -======= "end_time": "2023-06-09T10:04:28.821401Z", "start_time": "2023-06-09T10:04:28.777401Z" ->>>>>>> main } }, "outputs": [ @@ -256,16 +207,7 @@ }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [ "## 3. Getting Area of Individual Faces\n", "\n", @@ -282,13 +224,8 @@ "start_time": "2023-06-09T10:04:28.810401Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [ @@ -310,16 +247,7 @@ }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [ "Now calculate the area again with the `Grid.compute_face_areas` function using arguments: quadrature_rule \"gaussian\" and order 4" ] @@ -329,16 +257,8 @@ "execution_count": 6, "metadata": { "ExecuteTime": { -<<<<<<< HEAD - "end_time": "2023-04-19T09:57:03.696254Z", - "start_time": "2023-04-19T09:57:03.693676Z" - }, - "pycharm": { - "name": "#%%\n" -======= "end_time": "2023-06-09T10:04:28.904401Z", "start_time": "2023-06-09T10:04:28.896401Z" ->>>>>>> main } }, "outputs": [ @@ -372,15 +292,6 @@ }, { "cell_type": "code", -<<<<<<< HEAD - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], -======= "execution_count": 7, "metadata": { "ExecuteTime": { @@ -400,7 +311,6 @@ "output_type": "execute_result" } ], ->>>>>>> main "source": [ "actual_area = 4 * np.pi\n", "diff_t4_area = np.abs(t4_area - actual_area)\n", @@ -415,30 +325,11 @@ "metadata": {}, "source": [ "As we can see, it is clear that the Gaussian Quadrature Rule with Order 4 is the most accurate, and the Triangular Quadrature Rule with Order 1 is the least accurate.\n" -<<<<<<< HEAD - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } -======= ] ->>>>>>> main }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [ "## 4. Calculate Area of a Single Triangle in Cartesian Coordinates\n", "\n", @@ -456,13 +347,8 @@ "start_time": "2023-06-09T10:04:28.921401Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [ @@ -511,13 +397,8 @@ "start_time": "2023-06-09T10:04:28.951402Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [ @@ -582,44 +463,16 @@ "area_gaussian = vgrid.calculate_total_face_area(\n", " quadrature_rule=\"gaussian\", order=5)\n", "area_gaussian" -<<<<<<< HEAD - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } -======= ] ->>>>>>> main }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [] }, { "cell_type": "markdown", -<<<<<<< HEAD - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - }, -======= "metadata": {}, ->>>>>>> main "source": [ "## 5. Calculate Area from Multiple Faces in Spherical Coordinates\n", "\n", @@ -635,13 +488,8 @@ "start_time": "2023-06-09T10:04:28.984401Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [], @@ -661,17 +509,7 @@ "metadata": {}, "source": [ "We want our units to be spherical, so we pass through `islatlon=True`. Additionally, if `islatlon` is not passed through, it will default to spherical coordinates." -<<<<<<< HEAD - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } -======= ] ->>>>>>> main }, { "cell_type": "code", @@ -682,13 +520,8 @@ "start_time": "2023-06-09T10:04:29.001401Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [ @@ -731,13 +564,8 @@ "start_time": "2023-06-09T10:04:29.034401Z" }, "collapsed": false, -<<<<<<< HEAD - "pycharm": { - "name": "#%%\n" -======= "jupyter": { "outputs_hidden": false ->>>>>>> main } }, "outputs": [ @@ -784,41 +612,14 @@ }, "outputs": [], "source": [ -<<<<<<< HEAD - "from uxarray.helpers import calculate_face_area" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } -======= "from uxarray.utils.helpers import calculate_face_area" ] ->>>>>>> main }, { "cell_type": "markdown", "metadata": {}, "source": [ "`Grid.calculate_face_area` takes in three coordinate variables (x, y, z) in the form of numpy arrays and the coordinate type (either spherical or artesian) and computes the face area from the set of points" -<<<<<<< HEAD - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" -======= ] }, { @@ -828,7 +629,6 @@ "ExecuteTime": { "end_time": "2023-06-09T10:04:29.086401Z", "start_time": "2023-06-09T10:04:29.064401Z" ->>>>>>> main } }, "outputs": [], @@ -849,15 +649,6 @@ }, { "cell_type": "code", -<<<<<<< HEAD - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], -======= "execution_count": 20, "metadata": { "ExecuteTime": { @@ -877,7 +668,6 @@ "output_type": "execute_result" } ], ->>>>>>> main "source": [ "calculate_face_area(cart_x, cart_y, cart_z, coords_type=\"cartesian\")" ] diff --git a/test/test_grid.py b/test/test_grid.py index 48cf88827..39ff52aac 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -13,8 +13,6 @@ import uxarray.multi_precision_helpers as mph import numpy.testing as nt -from uxarray.utils.constants import INT_FILL_VALUE_MPZ - try: import constants except ImportError: @@ -230,7 +228,7 @@ def test_init_verts_multi_precision_fill_values(self): # Generate the face nodes connectivity dumb_nodes = [ - INT_FILL_VALUE_MPZ, INT_FILL_VALUE_MPZ, INT_FILL_VALUE_MPZ + ux.INT_FILL_VALUE_MPZ, ux.INT_FILL_VALUE_MPZ, ux.INT_FILL_VALUE_MPZ ] face_nodes_connectivity = np.array([ np.array([nodes[0], nodes[1], nodes[2], dumb_nodes], dtype=object), From 3f674bfccd9ec4cc8c2172eaf3230e1921963297 Mon Sep 17 00:00:00 2001 From: Philip Chmielowiec <67855069+philipc2@users.noreply.github.com> Date: Tue, 18 Jul 2023 13:09:35 -0500 Subject: [PATCH 59/59] Update index.rst --- docs/user_api/index.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/user_api/index.rst b/docs/user_api/index.rst index e06856e38..19c4d4b3b 100644 --- a/docs/user_api/index.rst +++ b/docs/user_api/index.rst @@ -61,6 +61,8 @@ Methods helpers.node_lonlat_rad_to_xyz helpers.normalize_in_place helpers.close_face_nodes + UxDataset.info + UxDataset.integrate Multi-Precision Helper Functions ------------------------------- @@ -71,9 +73,8 @@ Multi-Precision Helper Functions multi_precision_helpers.unique_coordinates_multiprecision multi_precision_helpers.decimal_digits_to_precision_bits multi_precision_helpers.precision_bits_to_decimal_digits -======= - UxDataset.info - UxDataset.integrate + + UxDataArray @@ -178,7 +179,6 @@ Helpers node_lonlat_rad_to_xyz normalize_in_place parse_grid_type -======= Multi-Precision