diff --git a/pyfms/__init__.py b/pyfms/__init__.py index 7a47c02..8f12b9d 100644 --- a/pyfms/__init__.py +++ b/pyfms/__init__.py @@ -1,4 +1,5 @@ from .py_data_override.py_data_override import pyDataOverride +from .py_diag_manager.pyfms_diag_manager import DiagManager from .py_field_manager.py_field_manager import FieldTable from .py_horiz_interp.py_horiz_interp import HorizInterp from .py_mpp.py_mpp import pyFMS_mpp diff --git a/pyfms/py_diag_manager/pyfms_diag_manager.py b/pyfms/py_diag_manager/pyfms_diag_manager.py new file mode 100644 index 0000000..16449ac --- /dev/null +++ b/pyfms/py_diag_manager/pyfms_diag_manager.py @@ -0,0 +1,596 @@ +import ctypes + +import numpy as np +from numpy.typing import NDArray + +from pyfms.pyfms_utils.data_handling import ( + set_Cchar, + setarray_Cdouble, + setarray_Cfloat, + setarray_Cint32, + setscalar_Cbool, + setscalar_Cdouble, + setscalar_Cfloat, + setscalar_Cint32, +) + + +class DiagManager: + + # To be class var after refactor, accessed directly from cFMS + DIAG_ALL = 2 + + def __init__(self, clibFMS: ctypes.CDLL = None): + self.clibFMS = clibFMS + + def end(self): + _cfms_diag_end = self.clibFMS.cFMS_diag_end + + _cfms_diag_end.restype = None + + _cfms_diag_end() + + def init( + self, + diag_model_subset: int = None, + time_init: NDArray = None, + ) -> str: + err_msg = " " + + _cfms_diag_init = self.clibFMS.cFMS_diag_init + + diag_model_subset_c, diag_model_subset_t = setscalar_Cint32(diag_model_subset) + time_init_p, time_init_t = setarray_Cint32(time_init) + err_msg_c, err_msg_t = set_Cchar(err_msg) + + _cfms_diag_init.argtypes = [ + diag_model_subset_t, + time_init_t, + err_msg_t, + ] + _cfms_diag_init.restype = None + + _cfms_diag_init(diag_model_subset_c, time_init_p, err_msg_c) + + return err_msg_c.value.decode("utf-8") + + def send_complete( + self, + diag_field_id: int, + ) -> str: + + err_msg = " " + + _cfms_diag_send_complete = self.clibFMS.cFMS_diag_send_complete + + diag_field_id_c, diag_field_id_t = setscalar_Cint32(diag_field_id) + err_msg_c, err_msg_t = set_Cchar(err_msg) + + _cfms_diag_send_complete.argtypes = [diag_field_id_t, err_msg_t] + _cfms_diag_send_complete.restype = None + + _cfms_diag_send_complete(diag_field_id_c, err_msg_c) + + return err_msg_c.value.decode("utf-8") + + def set_field_init_time( + self, + year: int, + month: int, + day: int, + hour: int, + minute: int, + second: int = None, + tick: int = None, + ) -> str: + + err_msg = " " + + _cfms_diag_set_field_init_time = self.clibFMS.cFMS_diag_set_field_init_time + + year_c, year_t = setscalar_Cint32(year) + month_c, month_t = setscalar_Cint32(month) + day_c, day_t = setscalar_Cint32(day) + hour_c, hour_t = setscalar_Cint32(hour) + minute_c, minute_t = setscalar_Cint32(minute) + second_c, second_t = setscalar_Cint32(second) + tick_c, tick_t = setscalar_Cint32(tick) + err_msg_c, err_msg_t = set_Cchar(err_msg) + + _cfms_diag_set_field_init_time.argtypes = [ + year_t, + month_t, + day_t, + hour_t, + minute_t, + second_t, + tick_t, + err_msg_t, + ] + _cfms_diag_set_field_init_time.restype = None + + _cfms_diag_set_field_init_time( + year_c, month_c, day_c, hour_c, minute_c, second_c, tick_c, err_msg_c + ) + + return err_msg_c.value.decode("utf-8") + + def set_field_timestep( + self, + diag_field_id: int, + dseconds: int, + ddays: int = None, + dticks: int = None, + ) -> str: + + err_msg = " " + + _cfms_diag_set_field_timestep = self.clibFMS.cFMS_diag_set_field_timestep + + diag_field_id_c, diag_field_id_t = setscalar_Cint32(diag_field_id) + dseconds_c, dseconds_t = setscalar_Cint32(dseconds) + ddays_c, ddays_t = setscalar_Cint32(ddays) + dticks_c, dticks_t = setscalar_Cint32(dticks) + err_msg_c, err_msg_t = set_Cchar(err_msg) + + _cfms_diag_set_field_timestep.argtypes = [ + diag_field_id_t, + dseconds_t, + ddays_t, + dticks_t, + err_msg_t, + ] + _cfms_diag_set_field_timestep.restype = None + + _cfms_diag_set_field_timestep( + diag_field_id_c, dseconds_c, ddays_c, dticks_c, err_msg_c + ) + + return err_msg_c.value.decode("utf-8") + + def advance_field_time( + self, + diag_field_id: int, + ): + _cfms_diag_advance_field_time = self.clibFMS.cFMS_diag_advance_field_time + + diag_field_id_c, diag_field_id_t = setscalar_Cint32(diag_field_id) + + _cfms_diag_advance_field_time.argtypes = [diag_field_id_t] + _cfms_diag_advance_field_time.restype = None + + _cfms_diag_advance_field_time(diag_field_id_c) + + def set_time_end( + self, + year: int = None, + month: int = None, + day: int = None, + hour: int = None, + minute: int = None, + second: int = None, + tick: int = None, + err_msg: str = None, + ): + if err_msg is not None: + err_msg = err_msg[:128] + + _cfms_set_time_end = self.clibFMS.cFMS_diag_set_time_end + + year_c, year_t = setscalar_Cint32(year) + month_c, month_t = setscalar_Cint32(month) + day_c, day_t = setscalar_Cint32(day) + hour_c, hour_t = setscalar_Cint32(hour) + minute_c, minute_t = setscalar_Cint32(minute) + second_c, second_t = setscalar_Cint32(second) + tick_c, tick_t = setscalar_Cint32(tick) + err_msg_c, err_msg_t = set_Cchar(err_msg) + + _cfms_set_time_end.argtypes = [ + year_t, + month_t, + day_t, + hour_t, + minute_t, + second_t, + tick_t, + err_msg_t, + ] + _cfms_set_time_end.restype = None + + _cfms_set_time_end( + year_c, + month_c, + day_c, + hour_c, + minute_c, + second_c, + tick_c, + err_msg_c, + ) + + def axis_init( + self, + name: str, + axis_data: NDArray, + units: str, + cart_name: str, + long_name: str = None, + set_name: str = None, + direction: int = None, + edges: int = None, + aux: str = None, + req: str = None, + tile_count: int = None, + domain_position: int = None, + not_xy: bool = None, + ) -> int: + + long_name = long_name[:64] + set_name = set_name[:64] + + name_c, name_t = set_Cchar(name) + naxis_data_c, naxis_data_t = setscalar_Cint32(axis_data.size) + units_c, units_t = set_Cchar(units) + cart_name_c, cart_name_t = set_Cchar(cart_name) + long_name_c, long_name_t = set_Cchar(long_name) + set_name_c, set_name_t = set_Cchar(set_name) + direction_c, direction_t = setscalar_Cint32(direction) + edges_c, edges_t = setscalar_Cint32(edges) + aux_c, aux_t = set_Cchar(aux) + req_c, req_t = set_Cchar(req) + tile_count_c, tile_count_t = setscalar_Cint32(tile_count) + domain_position_c, domain_position_t = setscalar_Cint32(domain_position) + not_xy_c, not_xy_t = setscalar_Cbool(not_xy) + + if axis_data.dtype == np.float64: + _cfms_diag_axis_init_ = self.clibFMS.cFMS_diag_axis_init_cdouble + axis_data_p, axis_data_t = setarray_Cdouble(axis_data) + elif axis_data.dtype == np.float32: + _cfms_diag_axis_init_ = self.clibFMS.cFMS_diag_axis_init_cfloat + axis_data_p, axis_data_t = setarray_Cfloat(axis_data) + else: + raise RuntimeError("diag_axis_init datatype not supported") + + _cfms_diag_axis_init_.argtypes = [ + name_t, + naxis_data_t, + axis_data_t, + units_t, + cart_name_t, + long_name_t, + direction_t, + set_name_t, + edges_t, + aux_t, + req_t, + tile_count_t, + domain_position_t, + not_xy_t, + ] + _cfms_diag_axis_init_.restype = ctypes.c_int + + return _cfms_diag_axis_init_( + name_c, + naxis_data_c, + axis_data_p, + units_c, + cart_name_c, + long_name_c, + direction_c, + set_name_c, + edges_c, + aux_c, + req_c, + tile_count_c, + domain_position_c, + not_xy_c, + ) + + def register_field_array( + self, + module_name: str, + field_name: str, + datatype, + axes: list[int] = None, + long_name: str = None, + units: str = None, + missing_value: int = None, + range_data: NDArray = None, + mask_variant: bool = None, + standard_name: str = None, + verbose: bool = None, + do_not_log: bool = None, + interp_method: str = None, + tile_count: int = None, + area: int = None, + volume: int = None, + realm: str = None, + multiple_send_data: bool = None, + ) -> int: + + err_msg = " " + + module_name = module_name[:64] + field_name = field_name[:64] + if long_name is not None: + long_name = long_name[:64] + if units is not None: + units = units[:64] + if standard_name is not None: + standard_name = standard_name[:64] + if interp_method is not None: + interp_method = interp_method[:64] + if realm is not None: + realm = realm[:64] + + if axes is not None: + if len(axes) < 5: + for i in range(5 - len(axes)): + axes.append(0) + axes_arr = np.array(axes, dtype=np.int32) + else: + axes_arr = None + + module_name_c, module_name_t = set_Cchar(module_name) + field_name_c, field_name_t = set_Cchar(field_name) + axes_p, axes_t = setarray_Cint32(axes_arr) + long_name_c, long_name_t = set_Cchar(long_name) + units_c, units_t = set_Cchar(units) + mask_variant_c, mask_variant_t = setscalar_Cbool(mask_variant) + standard_name_c, standard_name_t = set_Cchar(standard_name) + verbose_c, verbose_t = setscalar_Cbool(verbose) + do_not_log_c, do_not_log_t = setscalar_Cbool(do_not_log) + err_msg_c, err_msg_t = set_Cchar(err_msg) + interp_method_c, interp_method_t = set_Cchar(interp_method) + tile_count_c, tile_count_t = setscalar_Cint32(tile_count) + area_c, area_t = setscalar_Cint32(area) + volume_c, volume_t = setscalar_Cint32(volume) + realm_c, realm_t = set_Cchar(realm) + multiple_send_data_c, multiple_send_data_t = setscalar_Cbool(multiple_send_data) + + if datatype == np.int32: + _cfms_register_diag_field_array_ = ( + self.clibFMS.cFMS_register_diag_field_array_cint + ) + range_data_p, range_data_t = setarray_Cint32(range_data) + missing_value_c, missing_value_t = setscalar_Cint32(missing_value) + elif datatype == np.float64: + _cfms_register_diag_field_array_ = ( + self.clibFMS.cFMS_register_diag_field_array_cdouble + ) + range_data_p, range_data_t = setarray_Cdouble(range_data) + missing_value_c, missing_value_t = setscalar_Cdouble(missing_value) + elif datatype == np.float32: + _cfms_register_diag_field_array_ = ( + self.clibFMS.cFMS_register_diag_field_array_cfloat + ) + range_data_p, range_data_t = setarray_Cfloat(range_data) + missing_value_c, missing_value_t = setscalar_Cfloat(missing_value) + else: + raise RuntimeError( + "register diag field array range_data datatype not supported" + ) + + _cfms_register_diag_field_array_.argtypes = [ + module_name_t, + field_name_t, + axes_t, + long_name_t, + units_t, + missing_value_t, + range_data_t, + mask_variant_t, + standard_name_t, + verbose_t, + do_not_log_t, + err_msg_t, + interp_method_t, + tile_count_t, + area_t, + volume_t, + realm_t, + multiple_send_data_t, + ] + _cfms_register_diag_field_array_.restype = ctypes.c_int + + return _cfms_register_diag_field_array_( + module_name_c, + field_name_c, + axes_p, + long_name_c, + units_c, + missing_value_c, + range_data_p, + mask_variant_c, + standard_name_c, + verbose_c, + do_not_log_c, + err_msg_c, + interp_method_c, + tile_count_c, + area_c, + volume_c, + realm_c, + multiple_send_data_c, + ) + + def register_field_scalar( + self, + module_name: str, + field_name: str, + datatype, + long_name: str = None, + units: str = None, + standard_name: str = None, + missing_value: int = None, + range_data: NDArray = None, + do_not_log: bool = None, + area: int = None, + volume: int = None, + realm: str = None, + multiple_send_data: bool = None, + ) -> int: + + err_msg = " " + + module_name = module_name[:64] + field_name = field_name[:64] + if long_name is not None: + long_name = long_name[:64] + if units is not None: + units = units[:64] + if standard_name is not None: + standard_name = standard_name[:64] + if realm is not None: + realm = realm[:64] + + module_name_c, module_name_t = set_Cchar(module_name) + field_name_c, field_name_t = set_Cchar(field_name) + long_name_c, long_name_t = set_Cchar(long_name) + units_c, units_t = set_Cchar(units) + standard_name_c, standard_name_t = set_Cchar(standard_name) + do_not_log_c, do_not_log_t = setscalar_Cbool(do_not_log) + err_msg_c, err_msg_t = set_Cchar(err_msg) + area_c, area_t = setscalar_Cint32(area) + volume_c, volume_t = setscalar_Cint32(volume) + realm_c, realm_t = set_Cchar(realm) + multiple_send_data_c, multiple_send_data_t = setscalar_Cbool(multiple_send_data) + + if datatype == np.int32: + _cfms_register_diag_field_scalar_ = ( + self.clibFMS.cFMS_register_diag_field_array_cint + ) + range_data_p, range_data_t = setarray_Cint32(range_data) + missing_value_c, missing_value_t = setscalar_Cint32(missing_value) + elif datatype == np.float64: + _cfms_register_diag_field_scalar_ = ( + self.clibFMS.cFMS_register_diag_field_array_cdouble + ) + range_data_p, range_data_t = setarray_Cdouble(range_data) + missing_value_c, missing_value_t = setscalar_Cdouble(missing_value) + elif datatype == np.float32: + _cfms_register_diag_field_scalar_ = ( + self.clibFMS.cFMS_register_diag_field_array_cfloat + ) + range_data_p, range_data_t = setarray_Cfloat(range_data) + missing_value_c, missing_value_t = setscalar_Cfloat(missing_value) + else: + raise RuntimeError( + "register diag field array range_data datatype not supported" + ) + + _cfms_register_diag_field_scalar_.argtypes = [ + module_name_t, + field_name_t, + long_name_t, + units_t, + standard_name_t, + missing_value_t, + range_data_t, + do_not_log_t, + err_msg_t, + area_t, + volume_t, + realm_t, + multiple_send_data_t, + ] + _cfms_register_diag_field_scalar_.restype = ctypes.c_int + + return _cfms_register_diag_field_scalar_( + module_name_c, + field_name_c, + long_name_c, + units_c, + standard_name_c, + missing_value_c, + range_data_p, + do_not_log_c, + err_msg_c, + area_c, + volume_c, + realm_c, + multiple_send_data_c, + ) + + def send_data( + self, + diag_field_id: int, + field_shape: list[int], + field: NDArray, + ) -> bool: + + err_msg = " " + + diag_field_id_c, diag_field_id_t = setscalar_Cint32(diag_field_id) + field_shape_arr = np.array(field_shape, dtype=np.int32) + field_shape_p, field_shape_t = setarray_Cint32(field_shape_arr) + err_msg_c, err_msg_t = set_Cchar(err_msg) + + if field_shape_arr.size == 2: + if field.dtype == np.int32: + _cfms_diag_send_data_ = self.clibFMS.cFMS_diag_send_data_2d_cint + field_p, field_t = setarray_Cint32(field) + elif field.dtype == np.float64: + _cfms_diag_send_data_ = self.clibFMS.cFMS_diag_send_data_2d_cdouble + field_p, field_t = setarray_Cdouble(field) + elif field.dtype == np.float32: + _cfms_diag_send_data_ = self.clibFMS.cFMS_diag_send_data_2d_cfloat + field_p, field_t = setarray_Cfloat(field) + else: + raise RuntimeError(f"diag_send_data {field.dtype} unsupported") + elif field_shape_arr.size == 3: + if field.dtype == np.int32: + _cfms_diag_send_data_ = self.clibFMS.cFMS_diag_send_data_3d_cint + field_p, field_t = setarray_Cint32(field) + elif field.dtype == np.float64: + _cfms_diag_send_data_ = self.clibFMS.cFMS_diag_send_data_3d_cdouble + field_p, field_t = setarray_Cdouble(field) + elif field.dtype == np.float32: + _cfms_diag_send_data_ = self.clibFMS.cFMS_diag_send_data_3d_cfloat + field_p, field_t = setarray_Cfloat(field) + else: + raise RuntimeError(f"diag_send_data {field.dtype} unsupported") + elif field_shape_arr.size == 4: + if field.dtype == np.int32: + _cfms_diag_send_data_ = self.clibFMS.cFMS_diag_send_data_4d_cint + field_p, field_t = setarray_Cint32(field) + elif field.dtype == np.float64: + _cfms_diag_send_data_ = self.clibFMS.cFMS_diag_send_data_4d_cdouble + field_p, field_t = setarray_Cdouble(field) + elif field.dtype == np.float32: + _cfms_diag_send_data_ = self.clibFMS.cFMS_diag_send_data_4d_cfloat + field_p, field_t = setarray_Cfloat(field) + else: + raise RuntimeError(f"diag_send_data {field.dtype} unsupported") + elif field_shape_arr.size == 5: + if field.dtype == np.int32: + _cfms_diag_send_data_ = self.clibFMS.cFMS_diag_send_data_5d_cint + field_p, field_t = setarray_Cint32(field) + elif field.dtype == np.float64: + _cfms_diag_send_data_ = self.clibFMS.cFMS_diag_send_data_5d_cdouble + field_p, field_t = setarray_Cdouble(field) + elif field.dtype == np.float32: + _cfms_diag_send_data_ = self.clibFMS.cFMS_diag_send_data_5d_cfloat + field_p, field_t = setarray_Cfloat(field) + else: + raise RuntimeError(f"diag_send_data {field.dtype} unsupported") + else: + raise RuntimeError( + f"diag_send_data {field_shape_arr.size} dimensions unsupported" + ) + + _cfms_diag_send_data_.argtypes = [ + diag_field_id_t, + field_shape_t, + field_t, + err_msg_t, + ] + _cfms_diag_send_data_.restype = ctypes.c_bool + + return _cfms_diag_send_data_( + diag_field_id_c, + field_shape_p, + field_p, + err_msg_c, + ) diff --git a/pyfms/py_horiz_interp/py_horiz_interp.py b/pyfms/py_horiz_interp/py_horiz_interp.py index b1ec680..92caa2b 100644 --- a/pyfms/py_horiz_interp/py_horiz_interp.py +++ b/pyfms/py_horiz_interp/py_horiz_interp.py @@ -129,9 +129,22 @@ def create_xgrid_2dx2d_order1( "xarea": xarea[:nxgrid], } - def horiz_interp_init(self): + def horiz_interp_init(self, ninterp: int = None): _cfms_horiz_interp_init = self.cfms.cFMS_horiz_interp_init + ninterp_c, ninterp_t = ctypes.c_int(ninterp), ctypes.POINTER(ctypes.c_int) + + _cfms_horiz_interp_init.argtypes = [ninterp_t] _cfms_horiz_interp_init.restype = None - _cfms_horiz_interp_init() + _cfms_horiz_interp_init(ctypes.byref(ninterp_c)) + + def set_current_interp(self, interp_id: int = None): + _cfms_set_current_interp = self.cfms.cFMS_set_current_interp + + interp_id_c, interp_id_t = ctypes.c_int(interp_id), ctypes.POINTER(ctypes.c_int) + + _cfms_set_current_interp.argtypes = [interp_id_t] + _cfms_set_current_interp.restype = None + + _cfms_set_current_interp(ctypes.byref(interp_id_c)) diff --git a/pyfms/py_mpp/py_mpp_domains.py b/pyfms/py_mpp/py_mpp_domains.py index 3841b7c..dbf7ae3 100644 --- a/pyfms/py_mpp/py_mpp_domains.py +++ b/pyfms/py_mpp/py_mpp_domains.py @@ -6,7 +6,7 @@ from ..pyfms_utils.data_handling import ( set_Cchar, - set_multipointer, + setarray_Cbool, setarray_Cint32, setscalar_Cbool, setscalar_Cint32, @@ -59,8 +59,8 @@ def __init__(self, cFMS: ctypes.CDLL = None): def define_domains( self, - global_indices: NDArray, - layout: NDArray, + global_indices: list[int], + layout: list[int], domain_id: Optional[int] = None, pelist: Optional[NDArray] = None, xflags: Optional[int] = None, @@ -87,8 +87,11 @@ def define_domains( _cfms_define_domains = self.cFMS.cFMS_define_domains - global_indices_p, global_indices_t = setarray_Cint32(global_indices) - layout_p, layout_t = setarray_Cint32(layout) + global_indices_arr = np.array(global_indices, dtype=np.int32) + layout_arr = np.array(layout, dtype=np.int32) + + global_indices_p, global_indices_t = setarray_Cint32(global_indices_arr) + layout_p, layout_t = setarray_Cint32(layout_arr) domain_id_c, domain_id_t = setscalar_Cint32(domain_id) pelist_p, pelist_t = setarray_Cint32(pelist) xflags_c, xflags_t = setscalar_Cint32(xflags) @@ -97,7 +100,7 @@ def define_domains( yhalo_c, yhalo_t = setscalar_Cint32(yhalo) xextent_p, xextent_t = setarray_Cint32(xextent) yextent_p, yextent_t = setarray_Cint32(yextent) - maskmap_p, maskmap_t = set_multipointer(arg=maskmap, num_ptr=2) + maskmap_p, maskmap_t = setarray_Cbool(maskmap) name_c, name_t = set_Cchar(name) symmetry_c, symmetry_t = setscalar_Cbool(symmetry) memory_size_p, memory_size_t = setarray_Cint32(memory_size) @@ -176,10 +179,12 @@ def define_domains( Returns: No return """ - def define_io_domain(self, io_layout: NDArray, domain_id: Optional[int] = None): + def define_io_domain(self, io_layout: list[int], domain_id: Optional[int] = None): _cfms_define_io_domain = self.cFMS.cFMS_define_io_domain - io_layout_p, io_layout_t = setarray_Cint32(io_layout) + io_layout_arr = np.array(io_layout, dtype=np.int32) + + io_layout_p, io_layout_t = setarray_Cint32(io_layout_arr) domain_id_c, domain_id_t = setscalar_Cint32(domain_id) _cfms_define_io_domain.argtypes = [io_layout_t, domain_id_t] @@ -197,15 +202,17 @@ def define_io_domain(self, io_layout: NDArray, domain_id: Optional[int] = None): def define_layout( self, - global_indices: NDArray, + global_indices: list[int], ndivs: int, - ) -> NDArray: + ) -> list: layout = np.empty(shape=2, dtype=np.int32, order="C") + global_indices_arr = np.array(global_indices, dtype=np.int32) + _cfms_define_layout = self.cFMS.cFMS_define_layout - global_indices_p, global_indices_t = setarray_Cint32(global_indices) + global_indices_p, global_indices_t = setarray_Cint32(global_indices_arr) ndivs_c, ndivs_t = setscalar_Cint32(ndivs) layout_p, layout_t = setarray_Cint32(layout) @@ -214,7 +221,7 @@ def define_layout( _cfms_define_layout(global_indices_p, ndivs_c, layout_p) - return layout + return layout.tolist() """ Subroutine: define_nest_domains @@ -907,8 +914,8 @@ class pyDomain: def __init__( self, mpp_domains_obj: pyFMS_mpp_domains, - global_indices: NDArray, - layout: NDArray, + global_indices: list[int], + layout: list[int], domain_id: Optional[int] = None, pelist: Optional[NDArray] = None, xflags: Optional[int] = None, diff --git a/pyfms/pyfms_utils/data_handling.py b/pyfms/pyfms_utils/data_handling.py index 5a6ad5d..0d9a68c 100644 --- a/pyfms/pyfms_utils/data_handling.py +++ b/pyfms/pyfms_utils/data_handling.py @@ -58,9 +58,7 @@ def wrapper_func(py_scalar_obj: int, py_array_obj: npt.NDArray[int])-> int: def set_ndpointer(arg: npt.NDArray) -> np.ctypeslib.ndpointer: c_type = np.ctypeslib.as_ctypes_type(arg.dtype) - return np.ctypeslib.ndpointer( - dtype=c_type, ndim=arg.ndim, shape=arg.shape, flags="FORTRAN" - ) + return np.ctypeslib.ndpointer(dtype=c_type, ndim=arg.ndim, shape=arg.shape) def setarray_Cbool( @@ -98,108 +96,6 @@ def setarray_Cint32( return arg, set_ndpointer(arg) -""" -set_multipointer: - For converting NumPy arrays to pointer equivalent - This method is able to convert a Numpy array of - up to 5 dimensions to quintuple pointer (*****). - - If an array of <= 1 or > 5 dimensions is passed - as an argument, the method will default to the - returning the passed array and the result of a - pass to set_ndpointer. - - This method is expected to be slow, and when - possible avoided. -""" - - -def set_multipointer(arg: npt.NDArray, num_ptr: int) -> Tuple: - if arg is not None: - c_type = np.ctypeslib.as_ctypes_type(arg.dtype) - match num_ptr: - case 2: - if arg is not None: - arg_ptr = (ctypes.POINTER(c_type) * arg.shape[0])() - for i in range(arg.shape[0]): - arg_ptr[i] = arg[i].ctypes.data_as(ctypes.POINTER(c_type)) - return arg_ptr, ctypes.POINTER(ctypes.POINTER(c_type)) - else: - return arg, ctypes.POINTER(ctypes.POINTER(ctypes.c_int)) - case 3: - if arg is not None: - arg_ptr = (ctypes.POINTER(ctypes.POINTER(c_type)) * arg.shape[0])() - for i in range(arg.shape[0]): - arg_ptr[i] = (ctypes.POINTER(c_type) * arg.shape[1])() - for j in range(arg.shape[1]): - arg_ptr[i][j] = arg[i][j].ctypes.data_as(ctypes.POINTER(c_type)) - return arg_ptr, ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(c_type))) - else: - return arg, ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(ctypes.c_int))) - case 4: - if arg is not None: - arg_ptr = ( - ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(c_type))) - * arg.shape[0] - )() - for i in range(arg.shape[0]): - arg_ptr[i] = ( - ctypes.POINTER(ctypes.POINTER(c_type)) * arg.shape[1] - )() - for j in range(arg.shape[1]): - arg_ptr[i][j] = (ctypes.POINTER(c_type) * arg.shape[2])() - for k in range(arg.shape[2]): - arg_ptr[i][j][k] = arg[i][j][k].ctypes.data_as( - ctypes.POINTER(c_type) - ) - return arg_ptr, ctypes.POINTER( - ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(c_type))) - ) - else: - return arg, ctypes.POINTER( - ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(ctypes.c_int))) - ) - case 5: - if arg is not None: - arg_ptr = ( - ctypes.POINTER( - ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(c_type))) - ) - * arg.shape[0] - )() - for i in range(arg.shape[0]): - arg_ptr[i] = ( - ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(c_type))) - * arg.shape[1] - )() - for j in range(arg.shape[1]): - arg_ptr[i][j] = ( - ctypes.POINTER(ctypes.POINTER(c_type)) * arg.shape[2] - )() - for k in range(arg.shape[2]): - arg_ptr[i][j][k] = (ctypes.POINTER(c_type) * arg.shape[3])() - for n in range(arg.shape[3]): - arg_ptr[i][j][k][n] = arg[i][j][k][n].ctypes.data_as( - ctypes.POINTER(c_type) - ) - return arg_ptr, ctypes.POINTER( - ctypes.POINTER( - ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(c_type))) - ) - ) - else: - return arg, ctypes.POINTER( - ctypes.POINTER( - ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(ctypes.c_int))) - ) - ) - case _: - if arg is not None: - return arg, set_ndpointer(arg) - else: - return arg, ctypes.POINTER(ctypes.c_int) - - """ Scalar setting methods """ diff --git a/run_tests.sh b/run_tests.sh index 53aa06e..a02a022 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -37,4 +37,8 @@ run_test "pytest tests/py_data_override/test_generate_files.py" run_test "mpirun -n 6 python -m pytest -m 'parallel' tests/py_data_override/test_data_override.py" remove_input "tests/py_data_override/test_data_override.py" +run_test "pytest tests/py_diag_manager/test_generate_files.py" +run_test "mpirun -n 1 python -m pytest tests/py_diag_manager/test_diag_manager.py" +remove_input "tests/py_diag_manager/test_diag_manager.py + rm -rf INPUT *logfile* *warnfile* diff --git a/tests/py_diag_manager/test_diag_manager.py b/tests/py_diag_manager/test_diag_manager.py new file mode 100644 index 0000000..ce65291 --- /dev/null +++ b/tests/py_diag_manager/test_diag_manager.py @@ -0,0 +1,220 @@ +import numpy as np + +from pyfms import DiagManager, pyFMS, pyFMS_mpp_domains + + +def test_send_data(): + + NX = 8 + NY = 8 + NZ = 2 + + domain_id = 0 + calendar_type = 4 + + var2_shape = [NX, NY] + var2 = np.empty(shape=(NX, NY), dtype=np.float32) + + var3_shape = [NX, NY, NZ] + var3 = np.empty(shape=(NX, NY, NZ), dtype=np.float32) + + for i in range(NX): + for j in range(NY): + for k in range(NZ): + var3[i][j][k] = i * 100 + j * 10 + k * 1 + + for i in range(NX): + for j in range(NY): + var2[i][j] = i * 10.0 + j * 1.0 + + cfms_path = "./cFMS/libcFMS/.libs/libcFMS.so" + + pyfms = pyFMS(cFMS_path=cfms_path, calendar_type=calendar_type) + mpp_domains = pyFMS_mpp_domains(cFMS=pyfms.cFMS) + + global_indices = [0, (NX - 1), 0, (NY - 1)] + layout = [1, 1] + io_layout = [1, 1] + + mpp_domains.define_domains( + domain_id=domain_id, + global_indices=global_indices, + layout=layout, + ) + mpp_domains.define_io_domain( + domain_id=domain_id, + io_layout=io_layout, + ) + + """ + diag manager init + """ + + diag_manager = DiagManager(clibFMS=pyfms.cFMS) + diag_manager.init(diag_model_subset=diag_manager.DIAG_ALL) + + mpp_domains.set_current_domain(domain_id=domain_id) + + """ + diag axis init x + """ + x = np.arange(NX, dtype=np.float64) + + id_x = diag_manager.axis_init( + name="x", + axis_data=x, + units="point_E", + cart_name="x", + long_name="point_E", + set_name="atm", + ) + + """ + diag axis init y + """ + y = np.arange(NY, dtype=np.float64) + + id_y = diag_manager.axis_init( + name="y", + axis_data=y, + units="point_N", + cart_name="y", + long_name="point_N", + set_name="atm", + ) + + """ + diag axis init z + """ + + z = np.arange(NZ, dtype=np.float64) + + id_z = diag_manager.axis_init( + name="z", + axis_data=z, + units="point_Z", + cart_name="z", + long_name="point_Z", + set_name="atm", + not_xy=True, + ) + + """ + register diag field var3 + """ + + axes_3d = [id_x, id_y, id_z] + range_3d = np.array([-1000.0, 1000.0], dtype=np.float32) + + diag_manager.set_field_init_time( + year=2, + month=1, + day=1, + hour=1, + minute=1, + second=1, + ) + + id_var3 = diag_manager.register_field_array( + module_name="atm_mod", + field_name="var_3d", + datatype=np.float32, + axes=axes_3d, + long_name="Var in a lon/lat domain", + units="muntin", + missing_value=-99.99, + range_data=range_3d, + ) + + diag_manager.set_field_timestep( + diag_field_id=id_var3, dseconds=60 * 60, ddays=0, dticks=0 + ) + + """ + register diag_field var 2 + """ + + axes_2d = [id_x, id_y] + range_2d = np.array([-1000.0, 1000.0], dtype=np.float32) + + diag_manager.set_field_init_time( + year=2, + month=1, + day=1, + hour=1, + minute=1, + second=1, + ) + + id_var2 = diag_manager.register_field_array( + module_name="atm_mod", + field_name="var_2d", + datatype=np.float32, + axes=axes_2d, + long_name="Var in a lon/lat domain", + units="muntin", + missing_value=-99.99, + range_data=range_2d, + ) + + diag_manager.set_field_timestep( + diag_field_id=id_var2, + dseconds=60 * 60, + ddays=0, + dticks=0, + ) + + """ + diag set time end + """ + + diag_manager.set_time_end( + year=2, + month=1, + day=2, + hour=1, + minute=1, + second=1, + tick=0, + ) + + """ + send data + """ + + diag_manager.send_data( + diag_field_id=id_var3, + field_shape=var3_shape, + field=var3, + ) + + ntime = 24 + for itime in range(ntime): + + var3 = -var3 + + diag_manager.advance_field_time(diag_field_id=id_var3) + diag_manager.send_data( + diag_field_id=id_var3, + field_shape=var3_shape, + field=var3, + ) + diag_manager.send_complete(diag_field_id=id_var3) + + var2 = -var2 + + diag_manager.advance_field_time(diag_field_id=id_var2) + diag_manager.send_data( + diag_field_id=id_var2, + field_shape=var2_shape, + field=var2, + ) + diag_manager.send_complete(diag_field_id=id_var2) + + diag_manager.end() + + pyfms.pyfms_end() + + +if __name__ == "__main__": + test_send_data() diff --git a/tests/py_diag_manager/test_generate_files.py b/tests/py_diag_manager/test_generate_files.py new file mode 100644 index 0000000..a525605 --- /dev/null +++ b/tests/py_diag_manager/test_generate_files.py @@ -0,0 +1,40 @@ +import yaml + + +def test_write_input_files(): + diag_table = yaml.load( + """ +title: test_diag_manager +base_date: 2 1 1 1 1 1 + +diag_files: +- file_name: test_send_data + freq: 1 hours + time_units: hours + unlimdim: time + varlist: + - module: atm_mod + var_name: var_3d + reduction: average + kind: r4 + output_name: var3_avg + - module: atm_mod + var_name: var_2d + reduction: average + kind: r4 + output_name: var2_avg +""", + Loader=yaml.Loader, + ) + + diag_table_yaml_file = open("diag_table.yaml", "w") + yaml.dump(diag_table, diag_table_yaml_file, sort_keys=False) + diag_table_yaml_file.close() + + input_nml = """ +&diag_manager_nml + use_modern_diag = .true. +/""" + input_nml_file = open("input.nml", "w") + input_nml_file.write(input_nml) + input_nml_file.close() diff --git a/tests/py_mpp/test_define_domains.py b/tests/py_mpp/test_define_domains.py index e1f87e7..08c429f 100644 --- a/tests/py_mpp/test_define_domains.py +++ b/tests/py_mpp/test_define_domains.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from pyfms import pyDomain, pyFMS, pyFMS_mpp, pyFMS_mpp_domains, pyNestDomain +from pyfms import pyFMS, pyFMS_mpp, pyFMS_mpp_domains @pytest.mark.create @@ -30,7 +30,7 @@ def test_define_domains(): domain_id = 1 nest_domain_id = 1 - coarse_global_indices = np.array([0, NX - 1, 0, NY - 1], dtype=np.int32, order="C") + coarse_global_indices = [0, NX - 1, 0, NY - 1] coarse_npes = COARSE_NPES coarse_pelist = np.empty(shape=COARSE_NPES, dtype=np.int32, order="C") coarse_tile_id = 0 @@ -43,9 +43,7 @@ def test_define_domains(): is_mosaic = False symmetry = False - fine_global_indices = np.array( - [0, NX_FINE - 1, 0, NY_FINE - 1], dtype=np.int32, order="C" - ) + fine_global_indices = [0, NX_FINE - 1, 0, NY_FINE - 1] fine_npes = FINE_NPES fine_pelist = np.empty(shape=FINE_NPES, dtype=np.int32, order="C") fine_tile_id = 1 @@ -54,7 +52,12 @@ def test_define_domains(): fine_shalo = 2 fine_nhalo = 2 + cfms_path = "./cFMS/libcFMS/.libs/libcFMS.so" + + assert os.path.exists(cfms_path) + pyfms = pyFMS( + cFMS_path=cfms_path, ndomain=ndomain, nnest_domain=nnest_domain, ) @@ -63,13 +66,13 @@ def test_define_domains(): assert isinstance(pyfms, pyFMS) - # get global pelist + """get global pelist""" npes = mpp.npes() pyfms.set_pelist_npes(npes_in=npes) global_pelist = mpp.get_current_pelist() - # set coarse domain as tile=0 + """set coarse domain as tile=0""" for i in range(coarse_npes): coarse_pelist[i] = global_pelist[i] @@ -81,7 +84,7 @@ def test_define_domains(): pyfms.set_pelist_npes(coarse_npes) mpp.set_current_pelist(coarse_pelist) name = "test coarse domain" - maskmap = np.full(shape=(2, 4), fill_value=True, dtype=np.bool_, order="C") + maskmap = np.full(shape=(2, 4), fill_value=True, dtype=np.bool_) xextent = np.zeros(shape=2, dtype=np.int32, order="C") yextent = np.zeros(shape=2, dtype=np.int32, order="C") @@ -94,10 +97,9 @@ def test_define_domains(): ndivs=ndivs, ) - domain = pyDomain( + mpp_domains.define_domains( global_indices=coarse_global_indices, layout=layout, - mpp_domains_obj=mpp_domains, domain_id=domain_id, pelist=coarse_pelist, xflags=coarse_xflags, @@ -117,7 +119,7 @@ def test_define_domains(): mpp.set_current_pelist() - # set fine domain as tile=1 + """set fine domain as tile=1""" name_fine = "test fine pelist" for i in range(fine_npes): @@ -137,10 +139,9 @@ def test_define_domains(): ndivs=ndivs, ) - domain = pyDomain( + mpp_domains.define_domains( global_indices=fine_global_indices, layout=layout, - mpp_domains_obj=mpp_domains, domain_id=domain_id, pelist=fine_pelist, name=name, @@ -157,7 +158,7 @@ def test_define_domains(): assert mpp_domains.domain_is_initialized(domain_id) - # set nest domain + """set nest domain""" name = "test nest domain" num_nest = 1 @@ -175,8 +176,7 @@ def test_define_domains(): nest_domain_id = nest_domain_id domain_id = domain_id - nest_domain = pyNestDomain( - mpp_domains_obj=mpp_domains, + mpp_domains.define_nest_domains( num_nest=num_nest, ntiles=ntiles, nest_level=nest_level, diff --git a/tests/py_mpp/test_getset_domains.py b/tests/py_mpp/test_getset_domains.py index 3954576..8438371 100644 --- a/tests/py_mpp/test_getset_domains.py +++ b/tests/py_mpp/test_getset_domains.py @@ -28,7 +28,7 @@ def test_getset_domains(): """ domain_id = 0 ndiv = 4 - global_indices = np.array([0, 3, 0, 3], dtype=np.int32, order="C") + global_indices = [0, 3, 0, 3] whalo = 2 ehalo = 2 shalo = 2 @@ -130,15 +130,6 @@ def test_getset_domains(): # get domain - assert domain.data_domain.xbegin.value == isd[pe] - assert domain.data_domain.xend.value == ied[pe] - assert domain.data_domain.ybegin.value == jsd[pe] - assert domain.data_domain.yend.value == jed[pe] - assert domain.data_domain.xsize.value == 6 - assert domain.data_domain.ysize.value == 6 - assert domain.data_domain.xmax_size.value == 6 - assert domain.data_domain.ymax_size.value == 6 - assert domain.compute_domain.xbegin.value == isc[pe] assert domain.compute_domain.xend.value == iec[pe] assert domain.compute_domain.ybegin.value == jsc[pe] @@ -150,6 +141,15 @@ def test_getset_domains(): assert domain.compute_domain.x_is_global.value is False assert domain.compute_domain.y_is_global.value is False + assert domain.data_domain.xbegin.value == isd[pe] + assert domain.data_domain.xend.value == ied[pe] + assert domain.data_domain.ybegin.value == jsd[pe] + assert domain.data_domain.yend.value == jed[pe] + assert domain.data_domain.xsize.value == 6 + assert domain.data_domain.ysize.value == 6 + assert domain.data_domain.xmax_size.value == 6 + assert domain.data_domain.ymax_size.value == 6 + pyfms.pyfms_end()