From 33a061876cfee8bc865c347b292bdffb6da2ec3b Mon Sep 17 00:00:00 2001 From: dwest77 Date: Thu, 14 Nov 2024 12:30:22 +0000 Subject: [PATCH 1/4] Added properties mixin for projects --- padocc/core/mixins.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/padocc/core/mixins.py b/padocc/core/mixins.py index d581c5a..552b75c 100644 --- a/padocc/core/mixins.py +++ b/padocc/core/mixins.py @@ -185,3 +185,17 @@ def _rerun_command(self): Setup for running this specific component interactively. """ return '' + +class PropertiesMixin: + + @property + def isparq(self) -> bool: + """ + Return True if the project is configured to use parquet. + """ + + return (self.detail_cfg['type'] == 'parq') + + @property + def cloud_format(self) -> bool: + return None \ No newline at end of file From 5e9763dd08a81d9280e1817e350a2639c7d1ea92 Mon Sep 17 00:00:00 2001 From: dwest77 Date: Thu, 14 Nov 2024 12:30:42 +0000 Subject: [PATCH 2/4] Stashed changes to validator --- padocc/phases/validate_new.py | 748 ++++++++++------------------------ 1 file changed, 224 insertions(+), 524 deletions(-) diff --git a/padocc/phases/validate_new.py b/padocc/phases/validate_new.py index 08c4fca..50aa892 100644 --- a/padocc/phases/validate_new.py +++ b/padocc/phases/validate_new.py @@ -34,7 +34,7 @@ VariableMismatchError ) from padocc.core import BypassSwitch, FalseLogger -#from padocc.core.utils import SUFFIXES, SUFFIX_LIST +from padocc.core.utils import open_kerchunk SUFFIXES = [] SUFFIX_LIST = [] @@ -290,7 +290,7 @@ def validate_shape_to_tolerance(nfiles: int, xv: str, dims: tuple, xshape: tuple else: pass -def validate_shapes(xobj, kobj, nfiles: int, xv: str, logger, bypass_shape=False, proj_dir=None, concat_dims={}) -> None: # Ingest into class structure +def _validate_shapes(xobj, kobj, nfiles: int, xv: str, logger, bypass_shape=False, proj_dir=None, concat_dims={}) -> None: # Ingest into class structure """ Ensure shapes are equivalent across Kerchunk/NetCDF per variable. Must account for the number of files opened vs how many files in total. @@ -388,40 +388,6 @@ def get_slice(shape, division): vslice.append(slice(0, math.ceil(s/division))) return vslice -def validate_selection( - test : xr.DataArray, - control : xr.DataArray, - current : int, - name : str, - recursion_limit : int = 10, - logger : logging.Logger | FalseLogger = FalseLogger(), - bypass : BypassSwitch = BypassSwitch(), - ) -> bool: - """ - General purpose validation for a specific variable from multiple sources. - Both inputs are expected to be xarray DataArray objects but the control could - instead by a NetCDF4 Dataset object. We expect both objects to be of the same size. - """ - if test.size != control.size: - raise ValueError( - 'Validation could not be completed for these objects due to differing' - f'sizes - "{test.size}" and "{control.size}"' - ) - - if current >= recursion_limit: - logger.debug('Maximum recursion depth reached') - logger.info(f'Validation for {name} not performed') - return None - - vslice = get_slice(test.shape, current) - tbox = test[vslice] - cbox = control[vslice] - - if check_for_nan(cbox): - return validate_selection(test, control, current+1, name, recursion_limit=recursion_limit, logger=logger) - else: - return compare_data(name, tbox, cbox, logger=logger, bypass=bypass) - def _count_duplicates(arr: list, source_num: int = None): """ Count the number of duplicates in a list @@ -447,6 +413,11 @@ def _count_duplicates(arr: list, source_num: int = None): class ValidateDatasets(LoggedOperation): + """ + ValidateDatasets object for performing validations between two + pseudo-identical Xarray Dataset objects + """ + def __init__( self, datasets: list, @@ -469,6 +440,11 @@ def __init__( self._identifier = identifier self._datasets = datasets + if len(self._datasets) > 2: + raise NotImplementedError( + 'Simultaneous Validation of multiple datasets is not supported.' + ) + super().__init__( logger, label=label, @@ -477,11 +453,10 @@ def __init__( verbose=verbose ) - def __str__(self): return f'' - def validate_all(self, allowances: dict = None): + def validate_metadata(self, allowances: dict = None): """ Run all validation steps on this set of datasets. """ @@ -501,16 +476,16 @@ def validate_all(self, allowances: dict = None): ignore_dims = {'ignore': allowances['ignore_dimensions']} # Validate variables/dimensions - self.validate_variables(allowances=ignore_vars) - self.validate_dimensions(allowances=ignore_dims) + self._validate_variables(allowances=ignore_vars) + self._validate_dimensions(allowances=ignore_dims) - def validate_variables(self, allowances: dict = None): + def _validate_variables(self, allowances: dict = None): """ Validate variables public method """ self._validate_selector(allowances=allowances, selector='variables') - def validate_dimensions(self, allowances: dict = None): + def _validate_dimensions(self, allowances: dict = None): """ Validate dimensions public method """ @@ -588,7 +563,6 @@ def validate_global_attrs(self, allowances: dict = None): self._validate_attrs(attrset, source='global.', ignore=ignore) - def _validate_attrs(self, attrset: list, source: str = '', ignore: list = None): """ Ensure all values across the sets of attributes are consistent @@ -613,19 +587,181 @@ def _validate_attrs(self, attrset: list, source: str = '', ignore: list = None): f'Found values: {set_of_values}' ) - def validate_shapes(self, allowances: dict = None): + def _validate_shapes(self, ignore: list = None): """ Ensure all variable shapes are consistent across all datasets. Allowances dict contains configurations for skipping some shape tests in the case for example of a virtual dimension. """ - pass + ignore = ignore or [] + + vset = self._datasets[0].variables + + for v in vset: + test = self._datasets[0][v] + control = self._datasets[1][v] + + testshape, controlshape = {}, {} + + for i in len(control.dims): + if test.dims[i] not in ignore: + testshape[test.dims[i]] = test.shape[i] + if control.dims[i] not in ignore: + controlshape[control.dims[i]] = control.shape[i] + + for dim in set(testshape.keys()) | set(controlshape.keys()): + try: + ts = testshape[dim] + except IndexError: + raise ShapeMismatchError( + f'"{dim}" dimension not present for {v} in dataset 0' + ) + + try: + cs = controlshape[dim] + except IndexError: + raise ShapeMismatchError( + f'"{dim}" dimension not present for {v} in dataset 1' + ) + + if int(ts) != int(cs): + raise ShapeMismatchError( + f'Shape mismatch for {v} with dimension {dim} ({ts} != {cs})' + ) def validate_data(self, allowances: dict = None): """ Perform data validations using the growbox method for all datasets. """ pass + # Validate selection for all DataArrays in the Dataset + + def _validate_selection( + self, + test: xr.DataArray, + control: xr.DataArray, + current : int, + name : str, + recursion_limit : int = 10, + ) -> bool: + """ + General purpose validation for a specific variable from multiple sources. + Both inputs are expected to be xarray DataArray objects but the control could + instead by a NetCDF4 Dataset object. We expect both objects to be of the same size. + """ + if test.size != control.size: + raise ValueError( + 'Validation could not be completed for these objects due to differing' + f'sizes - "{test.size}" and "{control.size}"' + ) + + if current >= recursion_limit: + logger.debug('Maximum recursion depth reached') + logger.info(f'Validation for {name} not performed') + return None + + vslice = get_slice(test.shape, current) + tbox = test[vslice] + cbox = control[vslice] + + if check_for_nan(cbox): + return self._validate_selection(test, control, current+1, name, recursion_limit=recursion_limit) + else: + return self._compare_data(name, tbox, cbox) + + def _compare_data( + self, + vname: str, + control: xr.DataArray, + test: xr.DataArray + ) -> None: + """Compare a NetCDF-derived ND array to a Kerchunk-derived one. This function takes a + netcdf selection box array of n-dimensions and an equally sized test array and + tests for elementwise equality within selection. If possible, tests max/mean/min calculations + for the selection to ensure cached values are the same. + + Expect TypeErrors later from summations which are bypassed. Other errors will exit the run. + + :param vname: (str) The name of the variable described by this box selection + + :param control: (obj) The native dataset selection + + :param test: (obj) The cloud-format (Kerchunk) dataset selection + + :param logger: (obj) Logging object for info/debug/error messages. + + :param bypass: (bool) Single value flag for bypassing numeric data errors (in the + case of values which cannot be added). + + :returns: None but will raise error if data comparison fails. + """ + self.logger.debug(f'Starting data comparison for {vname}') + + self.logger.debug('1. Flattening Arrays') + t1 = datetime.now() + + control = np.array(control).flatten() + test = np.array(test).flatten() + + self.logger.debug(f'2. Calculating Tolerance - {(datetime.now()-t1).total_seconds():.2f}s') + try: # Tolerance 0.1% of mean value for xarray set + tolerance = np.abs(np.nanmean(test))/1000 + except TypeError: # Type cannot be summed so skip all summations + tolerance = None + + self.logger.debug(f'3. Comparing with array_equal - {(datetime.now()-t1).total_seconds():.2f}s') + testpass = True + try: + equality = np.array_equal(control, test, equal_nan=True) + except TypeError as err: + equality = np.array_equal(control, test) + + if not equality: + self.logger.debug(f'3a. Comparing directly - {(datetime.now()-t1).total_seconds():.2f}s') + equality = False + for index in range(control.size): + v1 = control[index] + v2 = test[index] + if v1 != v2: + logger.error(f'X: {v1}, K: {v2}, idx: {index}') + raise ValidationError + + self.logger.debug(f'4. Comparing Max values - {(datetime.now()-t1).total_seconds():.2f}s') + try: + if np.abs(np.nanmax(test) - np.nanmax(control)) > tolerance: + self.logger.warning(f'Failed maximum comparison for {vname}') + self.logger.debug('K ' + str(np.nanmax(test)) + ' N ' + str(np.nanmax(control))) + testpass = False + except TypeError as err: + if bypass: + self.logger.warning(f'Max comparison skipped for non-summable values in {vname}') + else: + raise err + self.logger.debug(f'5. Comparing Min values - {(datetime.now()-t1).total_seconds():.2f}s') + try: + if np.abs(np.nanmin(test) - np.nanmin(control)) > tolerance: + self.logger.warning(f'Failed minimum comparison for {vname}') + self.logger.debug('K ' + str(np.nanmin(test)) + ' N ' + str(np.nanmin(control))) + testpass = False + except TypeError as err: + if bypass: + self.logger.warning(f'Min comparison skipped for non-summable values in {vname}') + else: + raise err + self.logger.debug(f'6. Comparing Mean values - {(datetime.now()-t1).total_seconds():.2f}s') + try: + if np.abs(np.nanmean(test) - np.nanmean(control)) > tolerance: + self.logger.warning(f'Failed mean comparison for {vname}') + self.logger.debug('K ' + str(np.nanmean(test)) + ' N ' + str(np.nanmean(control))) + testpass = False + except TypeError as err: + if bypass: + self.logger.warning(f'Mean comparison skipped for non-summable values in {vname}') + else: + raise err + if not testpass: + self.logger.error('Validation Error') + raise ValidationError class ValidateOperation(ProjectOperation): @@ -643,36 +779,63 @@ def __init__(self, *args, **kwargs): self.concat_dims = None - def run(self): + def _run(self): # Replaces validate timestep - + if self.detail_cfg.get('cfa'): # CFA-enabled validation - self.perform_cfa_validation() - + self._perform_cfa_validation() else: - self.perform_old_validation() + self._perform_source_validation() + + def _open_cfa(self): + """ + Open the CFA dataset for this project + """ + + return xr.open_dataset(self.cfa_path, engine='CFA', cfa_options=None) - def perform_cfa_validation(self): + def _open_product(self): + """ + Configuration to open object wrappers in the appropriate way so actions + can be applied to all. Any products not usable with Xarray should have + an xarray-wrapper to allow the application of typical methods for comparison. + """ + + if self.cloud_format == 'kerchunk': + # Kerchunk opening sequence + return open_kerchunk( + self.outfile, + self.logger, + isparq = self.isparq, + retry = True, + attempt = 3 + ) + + def _open_source(self, ) + + def _perform_cfa_validation(self): """ Perform validation for the selected dataset against the CFA-netCDF version. """ - cfa_ds = xr.open_dataset(self.cfa_path, engine='CFA', cfa_options=None) # NotImplemented + Validator = ValidateDatasets( + [cfa_ds, product_ds], + f'{self.proj_code}-validator' + logger = self.logger) - product_ds = xr.open_dataset(self.outfile) + # Perform metadata validation + Validator.validate_metadata() - self._validate_global_attrs(cfa_ds, product_ds) - self._validate_global_vars(cfa_ds, product_ds) + # Perform data validation + Validator.validate_data() - # Validate data from all variables. - + def _perform_source_validation(self): + """ + Perform validation by comparison to the source material + """ - def _get_concat_dims(self, objs): - pass - def _get_file_list(self): - pass def _open_product(self): """ @@ -685,468 +848,5 @@ def _open_product(self): def _open_base(self): pass - def _migrate_product(self): # May be unnecessary - pass - - def _backtrack_product(self): # May be unnecessary - pass - def _verify_connection(self): pass - -def get_concat_dims(xobjs: list, proj_dir) -> dict: # Ingest into class structure - """ - Retrieve the sizes of the concatenation dims. - - :param xobjs: (list) A list of xarray Dataset objects for files which are currently - being assessed, from which to find the shapes of concat dimensions. - - :param proj_code: (str) The project code in string format (DOI) - - :returns: A dictionary of the concatenation dimensions and their array shapes. - """ - concat_dims = {} - details = get_proj_file(proj_dir, 'detail-cfg.json') - if details: - # Initialise concat dims - if 'concat_dims' in details: - for dim in details['concat_dims']: - concat_dims[dim] = 0 - - for ds in xobjs: - for dim in concat_dims.keys(): - concat_dims[dim] += ds[dim].shape[0] - return concat_dims - -## 2. File Selection Tools - -def get_netcdf_list(proj_dir: str, logger, thorough=False) -> tuple: # Ingest into class structure - """ - Open document containing paths to all NetCDF files, make selections and return a list of files. - - :param proj_dir: (str) The project code directory path. - - :param logger: (obj) Logging object for info/debug/error messages. - - :param thorough: (bool) If True will select all files for testing, otherwise - standard validation subsetting (0.1% or 3 files) applies. - - :returns: A tuple containing a list of all the files as well as a list of indexes to - specific files for testing. The index list should cover at least 3 files, - with a maximum of 0.1% of the files selected in the case of > 3000 files. - """ - with open(f'{proj_dir}/allfiles.txt') as f: - xfiles = [r.strip() for r in f.readlines()] - logger.debug(f'Found {len(xfiles)} files in {proj_dir}/allfiles.txt') - - # Open full set or a subset of the files for testing - if thorough: - numfiles = len(xfiles)+1 - logger.info(f'Selecting all {numfiles-1} files') - else: - numfiles = int(len(xfiles)/1000) - if numfiles < 3: - numfiles = 3 - - if numfiles >= len(xfiles): - numfiles = len(xfiles) - indexes = [i for i in range(len(xfiles))] - else: - indexes = [] - for f in range(numfiles): - testindex = random.randint(0,numfiles-1) - while testindex in indexes: - testindex = random.randint(0,numfiles-1) - indexes.append(testindex) - logger.info(f'Selecting a subset of {numfiles}/{len(xfiles)} files') - return indexes, xfiles - -def locate_kerchunk(args, logger, get_str=False, attempt=1, remote_protocol='https') -> xr.Dataset: - """ - Gets the name of the latest kerchunk file for this project code. - - :param args: (obj) Set of command line arguments supplied by argparse. - - :param logger: (obj) Logging object for info/debug/error messages. - - :param get_str: (bool) If True will return the string filename for the selected - Kerchunk file, otherwise the Kerchunk file will be opened as an - xarray Virtual Dataset. - - :param remote_protocol: (str) Default 'https' for accessing files post-compute - since these have been reconfigured for remote testing. - Override with 'file' for Kerchunk files with a local - reference. - - :returns: Xarray Virtual Dataset from a Kerchunk file, or the string filename - of the Kerchunk file if get_str is enabled. - """ - files = os.listdir(args.proj_dir) # Get filename only - kfiles = [] - - check_complete = False - - for f in files: - if 'complete' in f: - check_complete = True - elif 'kerchunk' in f and 'complete' not in f: - kfiles.append(f) - else: - pass - - if len(kfiles) > 0: - # Which kerchunk file from set of options - kf = sorted(kfiles)[0] - logger.debug(f'Selected {kf} from {len(kfiles)} available') - kfile = os.path.join(args.proj_dir, kf) - if get_str: - return kfile, False - else: - return open_kerchunk(kfile, logger, attempt=attempt, remote_protocol=remote_protocol), False - elif check_complete: - if not args.forceful: - logger.error('File already exists and no override is set') - raise NoOverwriteError - else: - logger.info('Locating complete Kerchunk file') - if args.groupID: - complete_path = f'{args.workdir}/complete/{args.groupID}/{args.proj_code}*.json' - else: - complete_path = f'{args.workdir}/complete/{args.proj_code}*.json' - complete_versions = glob.glob(complete_path) - if len(complete_versions) > 0: - kfile = complete_versions[-1] - logger.info(f'Identified version {kfile.split("_")[-1].replace(".json","")}') - - else: - logger.error(f'No complete kerchunk files located at {complete_path}') - raise MissingKerchunkError - if get_str: - return kfile, True - else: - return open_kerchunk(kfile, logger, attempt=attempt, remote_protocol='file'), True - else: - logger.error(f'No Kerchunk file located at {args.proj_dir} and no in-place validation indicated - exiting') - raise MissingKerchunkError - -def open_netcdfs(args, logger, thorough=False, concat_dims='time') -> list: # Ingest into class structure - """Returns a single xarray object with one timestep: - - 1. Select a single file and a single timestep from that file - 2. Verify that a single timestep can be selected (Yes: return this xarray object, No: select all files and select a single timestep from that) - 3. In all cases, returns a list of xarray objects. - - :param args: (obj) Set of command line arguments supplied by argparse. - - :param logger: (obj) Logging object for info/debug/error messages. - - :param thorough: (bool) If True will concatenate all selected Datasets to a single - combined dataset, rather than a list of individual separate objects. - - :returns: A list of the xarray datasets (or a single concatenated dataset), along with a list - of indexes to use for selecting a subset of those files, plus a list of filepaths to - the original files. - """ - logger.debug('Performing temporal selections') - indexes, xfiles = get_netcdf_list(args.proj_dir, logger, thorough=thorough) - - if len(indexes) == len(xfiles): - thorough = True - xobjs = [] - if not thorough: - for i in indexes: - if not os.path.isfile(xfiles[i]): - raise SourceNotFoundError(sfile=xfiles[i]) - xobjs.append(xr.open_dataset(xfiles[i])) - if len(xobjs) == 0: - logger.error('No valid timestep objects identified') - raise NoValidTimeSlicesError(message='Kerchunk', verbose=args.verbose) - return xobjs, indexes, xfiles - else: - #xobj = xr.concat([xr.open_dataset(fx) for fx in xfiles], dim=concat_dims, data_vars='minimal') - try: - xobj = xr.open_mfdataset(xfiles, combine='by_coords', data_vars='minimal') - except TypeError: - try: - xobj = xr.open_mfdataset(xfiles, combine='by_coords', data_vars='minimal', use_cftime=True) - except: - xobj = xr.concat([xr.open_dataset(fx) for fx in xfiles], dim=concat_dims, data_vars='minimal') - return xobj, None, xfiles - - -def validate_timestep(args, xobj, kobj, step: int, nfiles: int, logger, concat_dims={}, index=0): # Ingest into class structure - """Run all tests for a single file which may or may not equate to 1 timestep""" - # Note: step indexed from 0 - - # Run Variable and Shape validation - - if 'virtual' in concat_dims: - # Assume virtual dimension is first? - logger.info("Filtering out virtual dimension for testing") - virtual = {concat_dims['virtual']:index} - logger.debug(f'Kerchunk index: {index}') - kobj = kobj.isel(**virtual) - - xvars = set(xobj.variables) - kvars = set(kobj.variables) - if xvars&kvars != xvars: # Overlap of sets - all xvars should be in kvars - missing = (xvars^kvars)&xvars - raise VariableMismatchError(missing=missing) - else: - logger.info(f'Passed Variable tests - all required variables are present') - print() - for xv in xvars: - validate_shapes(xobj, kobj, nfiles, xv, logger, - bypass_shape=args.bypass.skip_xkshape, - proj_dir=args.proj_dir, - concat_dims=concat_dims) - logger.info(f'{xv} : Passed Shape test') - logger.info(f'Passed all Shape tests') - print() - nr = 0 - for xv in xvars: - nrv = validate_data(xobj, kobj, xv, step, logger, bypass=args.bypass, nfiles=nfiles) - if nrv: - nr += nrv/10 - logger.info(f'{xv} : Passed Data test') - logger.info(f'Percentage of Unreachable Chunk requests made relative to maximum: PUCRM={nr/len(xvars)} ({len(xvars)} vars tried)') - -def run_successful(args, logger): # Ingest into class structure - """Move kerchunk-1a.json file to complete directory with proper name""" - # in_progress///kerchunk_1a.json - # complete// Date: Wed, 4 Dec 2024 14:51:27 +0000 Subject: [PATCH 3/4] Initial commit, various scripts --- padocc/core/filehandlers.py | 3 + padocc/core/project.py | 82 +++-- padocc/core/utils.py | 7 + padocc/operations/group.py | 3 +- padocc/operations/mixins.py | 2 + padocc/phases/compute.py | 21 +- padocc/phases/scan.py | 19 +- padocc/phases/validate.py | 13 - padocc/phases/validate_new.py | 322 ++++++------------ padocc/tests/conftest.py | 2 +- padocc/tests/data_creator/0DAgg/file0.nc | Bin 0 -> 10373 bytes padocc/tests/data_creator/0DAgg/file1.nc | Bin 0 -> 10373 bytes padocc/tests/data_creator/0DAgg/file2.nc | Bin 0 -> 10373 bytes padocc/tests/data_creator/0DAgg/file3.nc | Bin 0 -> 10373 bytes padocc/tests/data_creator/0DAgg/file4.nc | Bin 0 -> 10238 bytes padocc/tests/data_creator/0DAgg/file5.nc | Bin 0 -> 10373 bytes padocc/tests/data_creator/0DAgg/file6.nc | Bin 0 -> 10373 bytes padocc/tests/data_creator/0DAgg/file7.nc | Bin 0 -> 10373 bytes padocc/tests/data_creator/0DAgg/rem.json | 1 + padocc/tests/data_creator/0DAgg/upd.json | 1 + padocc/tests/data_creator/1DAgg/file0.nc | Bin 0 -> 10898 bytes padocc/tests/data_creator/1DAgg/file1.nc | Bin 0 -> 10898 bytes padocc/tests/data_creator/1DAgg/file2.nc | Bin 0 -> 10898 bytes padocc/tests/data_creator/1DAgg/file3.nc | Bin 0 -> 10898 bytes padocc/tests/data_creator/1DAgg/file4.nc | Bin 0 -> 10898 bytes padocc/tests/data_creator/1DAgg/file5.nc | Bin 0 -> 10898 bytes padocc/tests/data_creator/1DAgg/file6.nc | Bin 0 -> 10898 bytes padocc/tests/data_creator/1DAgg/file7.nc | Bin 0 -> 10898 bytes padocc/tests/data_creator/3DAgg/file0.nc | Bin 0 -> 10898 bytes padocc/tests/data_creator/3DAgg/file1.nc | Bin 0 -> 10898 bytes padocc/tests/data_creator/3DAgg/file2.nc | Bin 0 -> 10691 bytes padocc/tests/data_creator/3DAgg/file3.nc | Bin 0 -> 10898 bytes padocc/tests/data_creator/3DAgg/file4.nc | Bin 0 -> 10898 bytes padocc/tests/data_creator/3DAgg/file5.nc | Bin 0 -> 10898 bytes padocc/tests/data_creator/3DAgg/file6.nc | Bin 0 -> 10898 bytes padocc/tests/data_creator/3DAgg/file7.nc | Bin 0 -> 10691 bytes padocc/tests/data_creator/Aggs.csv | 3 + padocc/tests/data_creator/create.py | 180 ++++++++++ padocc/tests/data_creator/inspectpad.py | 11 + padocc/tests/data_creator/notes.md | 28 ++ padocc/tests/test_init.py | 17 +- padocc/tests/test_scan.py | 66 +++- .../padocc-test-suite/blacklist_codes.csv | 0 .../groups/padocc-test-suite/datasets.csv | 0 .../padocc-test-suite/proj_codes/main.txt | 0 45 files changed, 487 insertions(+), 294 deletions(-) create mode 100644 padocc/tests/data_creator/0DAgg/file0.nc create mode 100644 padocc/tests/data_creator/0DAgg/file1.nc create mode 100644 padocc/tests/data_creator/0DAgg/file2.nc create mode 100644 padocc/tests/data_creator/0DAgg/file3.nc create mode 100644 padocc/tests/data_creator/0DAgg/file4.nc create mode 100644 padocc/tests/data_creator/0DAgg/file5.nc create mode 100644 padocc/tests/data_creator/0DAgg/file6.nc create mode 100644 padocc/tests/data_creator/0DAgg/file7.nc create mode 100644 padocc/tests/data_creator/0DAgg/rem.json create mode 100644 padocc/tests/data_creator/0DAgg/upd.json create mode 100644 padocc/tests/data_creator/1DAgg/file0.nc create mode 100644 padocc/tests/data_creator/1DAgg/file1.nc create mode 100644 padocc/tests/data_creator/1DAgg/file2.nc create mode 100644 padocc/tests/data_creator/1DAgg/file3.nc create mode 100644 padocc/tests/data_creator/1DAgg/file4.nc create mode 100644 padocc/tests/data_creator/1DAgg/file5.nc create mode 100644 padocc/tests/data_creator/1DAgg/file6.nc create mode 100644 padocc/tests/data_creator/1DAgg/file7.nc create mode 100644 padocc/tests/data_creator/3DAgg/file0.nc create mode 100644 padocc/tests/data_creator/3DAgg/file1.nc create mode 100644 padocc/tests/data_creator/3DAgg/file2.nc create mode 100644 padocc/tests/data_creator/3DAgg/file3.nc create mode 100644 padocc/tests/data_creator/3DAgg/file4.nc create mode 100644 padocc/tests/data_creator/3DAgg/file5.nc create mode 100644 padocc/tests/data_creator/3DAgg/file6.nc create mode 100644 padocc/tests/data_creator/3DAgg/file7.nc create mode 100644 padocc/tests/data_creator/Aggs.csv create mode 100644 padocc/tests/data_creator/create.py create mode 100644 padocc/tests/data_creator/inspectpad.py create mode 100644 padocc/tests/data_creator/notes.md delete mode 100644 tests/auto_testdata_dir/groups/padocc-test-suite/blacklist_codes.csv delete mode 100644 tests/auto_testdata_dir/groups/padocc-test-suite/datasets.csv delete mode 100644 tests/auto_testdata_dir/groups/padocc-test-suite/proj_codes/main.txt diff --git a/padocc/core/filehandlers.py b/padocc/core/filehandlers.py index 37156cf..b29f80d 100644 --- a/padocc/core/filehandlers.py +++ b/padocc/core/filehandlers.py @@ -186,6 +186,7 @@ def __str__(self) -> str: def __len__(self) -> int: """Length of value""" content = self.get() + self.logger.debug(f'content length: {len(content)}') return len(content) def __iter__(self) -> Generator[str, None, None]: @@ -238,11 +239,13 @@ def _get_content(self) -> None: Open the file to get content if it exists """ if self.file_exists(): + self.logger.debug('Opening existing file') with open(self._file) as f: content = [r.strip() for r in f.readlines()] self._value = content else: + self.logger.debug('Creating new file') self.create_file() self._value = [] diff --git a/padocc/core/project.py b/padocc/core/project.py index 5ffa226..738b620 100644 --- a/padocc/core/project.py +++ b/padocc/core/project.py @@ -7,7 +7,7 @@ import logging from .errors import error_handler -from .utils import extract_file, BypassSwitch, apply_substitutions +from .utils import extract_file, BypassSwitch, apply_substitutions, phases from .logs import reset_file_handler from .mixins import DirectoryMixin, EvaluationsMixin @@ -101,6 +101,11 @@ def __init__( fh=fh, logid=logid, verbose=verbose) + + if not os.path.isdir(self.groupdir): + raise ValueError( + f'The group "{groupID}" has not been initialised - not present in the working directory' + ) self.proj_code = proj_code @@ -149,14 +154,41 @@ def __init__( self._outfile = None def __str__(self): - return f'' + return f'' def __repr__(self): return str(self) + def info(self, fn=print): + """ + Display some info about this particular project + """ + if self.groupID is not None: + fn(f'{self.proj_code} ({self.groupID}):') + else: + fn(f'{self.proj_code}:') + fn(f' > Phase: {self._get_phase()}') + fn(f' > Files: {len(self.allfiles)}') + fn(f' > Version: {self.get_version()}') + + def help(self, fn=print): + """ + Public user functions for the project operator. + """ + fn(str(self)) + fn(' > project.info() - Get some information about this project') + fn(' > project.get_version() - Get the version number for the output product') + fn(' > project.save_files() - Save all open files related to this project') + fn('Properties:') + fn(' > project.proj_code - code for this project.') + fn(' > project.groupID - group to which this project belongs.') + fn(' > project.dir - directory containing the projects files.') + fn(' > project.cfa_path - path to the CFA file.') + fn(' > project.outfile - path to the output product (Kerchunk/Zarr)') + def run( self, - mode: str = None, + mode: str = 'kerchunk', subset_bypass: bool = False, forceful : bool = None, thorough : bool = None, @@ -181,12 +213,12 @@ def run( self.save_files() return status except Exception as err: - - return error_handler( - err, self.logger, self.phase, - jobid=self._logid, dryrun=self._dryrun, - subset_bypass=subset_bypass, - status_fh=self.status_log) + print(err) + #return error_handler( + #err, self.logger, self.phase, + #jobid=self._logid, dryrun=self._dryrun, + ##subset_bypass=subset_bypass, + #status_fh=self.status_log) def _run(self, **kwargs): # Default project operation run. @@ -209,6 +241,13 @@ def get_version(self): """ return self.detail_cfg['version_no'] or 1 + @property + def dir(self): + if self.groupID: + return f'{self.workdir}/in_progress/{self.groupID}/{self.proj_code}' + else: + return f'{self.workdir}/in_progress/general/{self.proj_code}' + @property def cfa_path(self): return f'{self.dir}/{self.proj_code}.nca' @@ -225,9 +264,6 @@ def outfile(self): def outfile(self, value : str): self._outfile = value - def __str__(self): - return self.proj_code - def dir_exists(self, checkdir : str = None): if not checkdir: checkdir = self.dir @@ -258,6 +294,21 @@ def save_files(self): self.allfiles.close() self.status_log.close() + def _get_phase(self): + """ + Gets the highest phase this project has currently undertaken successfully""" + + max_sid = 0 + for row in self.status_log: + status = row[0] + if status != 'Success': + continue + + phase = row[1] + sid = phases.index(phase) + max_sid = max(sid, max_sid) + return phases[max_sid] + def _configure_filelist(self): pattern = self.base_cfg['pattern'] @@ -303,13 +354,6 @@ def _setup_config( config['substitutions'] = substitutions self.base_cfg.set(config) - @property - def dir(self): - if self.groupID: - return f'{self.workdir}/in_progress/{self.groupID}/{self.proj_code}' - else: - return f'{self.workdir}/in_progress/general/{self.proj_code}' - def _create_dirs(self, first_time : bool = None): if not self.dir_exists(): if self._dryrun: diff --git a/padocc/core/utils.py b/padocc/core/utils.py index 65f9992..f395ffe 100644 --- a/padocc/core/utils.py +++ b/padocc/core/utils.py @@ -23,6 +23,13 @@ 'validate':'30:00' # From CMIP experiments - no reliable prediction mechanism possible } +phases = [ + 'scan', + 'compute', + 'validate', + 'catalog' +] + class BypassSwitch: """Class to represent all bypass switches throughout the pipeline. Requires a switch string which is used to enable/disable specific pipeline diff --git a/padocc/operations/group.py b/padocc/operations/group.py index 0db1239..e3eeb6a 100644 --- a/padocc/operations/group.py +++ b/padocc/operations/group.py @@ -247,6 +247,7 @@ def _compute_config( self, proj_code, mode=None, + subset_bypass=False, **kwargs ) -> None: """ @@ -306,7 +307,7 @@ def _compute_config( version_no=version, **kwargs ) - status = proj_op.run() + status = proj_op.run(subset_bypass=subset_bypass) proj_op.save_files() return status diff --git a/padocc/operations/mixins.py b/padocc/operations/mixins.py index fd2b6d3..9978393 100644 --- a/padocc/operations/mixins.py +++ b/padocc/operations/mixins.py @@ -143,6 +143,8 @@ def _open_json(file): pattern = pattern[0] if status: self.logger.warning(status) + else: + pattern = os.path.abspath(pattern) if substitutions: cfg_values['substitutions'] = substitutions diff --git a/padocc/phases/compute.py b/padocc/phases/compute.py index 78a3d52..914efc7 100644 --- a/padocc/phases/compute.py +++ b/padocc/phases/compute.py @@ -158,7 +158,7 @@ def _convert_kerchunk(self, nfile: str, ctype, **kwargs) -> None: def _hdf5_to_zarr(self, nfile: str, **kwargs) -> dict: """Wrapper for converting NetCDF4/HDF5 type files to Kerchunk""" from kerchunk.hdf import SingleHdf5ToZarr - return SingleHdf5ToZarr(nfile, **kwargs).translate() + return SingleHdf5ToZarr(nfile,**kwargs).translate() def _ncf3_to_zarr(self, nfile: str, **kwargs) -> dict: """Wrapper for converting NetCDF3 type files to Kerchunk""" @@ -193,6 +193,7 @@ def __init__( limiter : int = None, skip_concat : bool = False, new_version : bool = None, + label : str = 'compute', **kwargs ) -> None: """ @@ -233,6 +234,7 @@ def __init__( workdir, groupID=groupID, thorough=thorough, + label=label, **kwargs) self.logger.debug('Starting variable definitions') @@ -243,7 +245,8 @@ def __init__( self.skip_concat = skip_concat self.stage = stage - self._identify_mode() + self.mode = self.detail_cfg['mode'] or 'kerchunk' + self.fmt = self.detail_cfg['type'] or 'JSON' self.validate_time = None self.concat_time = None @@ -284,7 +287,7 @@ def __init__( self.temp_zattrs.set({}) self.combine_kwargs = {} # Now using concat_dims and identical dims finders. - self.create_kwargs = {'inline_threshold':1} + self.create_kwargs = {'inline_threshold':0} self.pre_kwargs = {} self.special_attrs = {} @@ -292,6 +295,12 @@ def __init__( self.logger.debug('Finished all setup steps') + def help(self, fn=print): + super().help(fn=fn) + fn('') + fn('Compute Options:') + fn(' > project.run() - Run compute for this project') + def _run(self, mode: str = 'kerchunk'): """ Default _run hook for compute operations. A user should aim to use the @@ -597,20 +606,16 @@ def _determine_dim_specs(self, objs: list) -> None: # Calculate Partial Validation Estimate here t1 = datetime.now() self.logger.info("Determining concatenation dimensions") - print() self._find_concat_dims(objs) if self.combine_kwargs['concat_dims'] == []: self.logger.info("No concatenation dimensions available - virtual dimension will be constructed.") else: self.logger.info(f"Found {self.combine_kwargs['concat_dims']} concatenation dimensions.") - print() # Identical (Variables) Dimensions self.logger.info("Determining identical variables") - print() self._find_identical_dims(objs) self.logger.info(f"Found {self.combine_kwargs['identical_dims']} identical variables.") - print() # This one only happens for two files so don't need to take a mean self.validate_time = (datetime.now()-t1).total_seconds() @@ -749,7 +754,7 @@ def _combine_and_save(self, refs: dict) -> None: ]) t1 = datetime.now() - if self.fmt == 'json': + if self.fmt == 'JSON': self.logger.info('Concatenating to JSON format Kerchunk file') self._data_to_json(refs) else: diff --git a/padocc/phases/scan.py b/padocc/phases/scan.py index c5a6d7d..016bc22 100644 --- a/padocc/phases/scan.py +++ b/padocc/phases/scan.py @@ -130,7 +130,7 @@ def __init__( proj_code : str, workdir : str, groupID : str = None, - label : str = None, + label : str = 'scan', **kwargs, ) -> None: @@ -139,7 +139,13 @@ def __init__( label = 'scan-operation' super().__init__( - proj_code, workdir, groupID=groupID, **kwargs) + proj_code, workdir, groupID=groupID, label=label,**kwargs) + + def help(self, fn=print): + super().help(fn=fn) + fn('') + fn('Scan Options:') + fn(' > project.run() - Run a scan for this project') def _run(self, mode: str = 'kerchunk') -> None: """Main process handler for scanning phase""" @@ -150,7 +156,7 @@ def _run(self, mode: str = 'kerchunk') -> None: if nfiles < 3: self.detail_cfg = {'skipped':True} - self.logger.info('Skip scanning phase >> proceed directly to compute') + self.logger.info(f'Skip scanning phase (only found {nfiles} files) >> proceed directly to compute') return None @@ -158,14 +164,17 @@ def _run(self, mode: str = 'kerchunk') -> None: limiter = min(100, max(2, int(nfiles/20))) self.logger.info(f'Determined {limiter} files to scan (out of {nfiles})') + self.logger.debug(f'Using {mode} scan operations') if mode == 'zarr': self._scan_zarr(limiter=limiter) elif mode == 'kerchunk': self._scan_kerchunk(limiter=limiter) else: - self.logger.error('Unrecognised mode - must be one of ["kerchunk","zarr","CFA"]') - return 'Failed' + self.update_status('scan','ValueError',jobid=self._logid, dryrun=self._dryrun) + raise ValueError( + f'Unrecognised mode: {mode} - must be one of ["kerchunk","zarr","CFA"]' + ) self.update_status('scan','Success',jobid=self._logid, dryrun=self._dryrun) return 'Success' diff --git a/padocc/phases/validate.py b/padocc/phases/validate.py index 60ade77..0903126 100644 --- a/padocc/phases/validate.py +++ b/padocc/phases/validate.py @@ -21,19 +21,6 @@ from ujson import JSONDecodeError from dask.distributed import LocalCluster -class CloudValidator: - """ - Encapsulate all validation testing into a single class. Instantiate for a specific project, - the object could then contain all project info (from detail-cfg) opened only once. Also a - copy of the total datasets (from native and cloud sources). Subselections can be passed - between class methods along with a variable index (class variables: variable list, dimension list etc.) - - Class logger attribute so this doesn't need to be passed between functions. - Bypass switch contained here with all switches. - """ - def __init__(self): - pass - ## 1. Array Selection Tools def find_dimensions(dimlen: int, divisions: int) -> int: diff --git a/padocc/phases/validate_new.py b/padocc/phases/validate_new.py index 50aa892..86ce2b6 100644 --- a/padocc/phases/validate_new.py +++ b/padocc/phases/validate_new.py @@ -144,201 +144,6 @@ def match_timestamp(xobject: xr.Dataset, kobject: xr.Dataset, logger) -> tuple: else: logger.debug('Skipped timestamp selection as xobject has no time') return kobject, xobject - -def compare_data(vname: str, xbox: xr.Dataset, kerchunk_box: xr.Dataset, logger, bypass=False) -> None: # Ingest into class structure - """Compare a NetCDF-derived ND array to a Kerchunk-derived one. This function takes a - netcdf selection box array of n-dimensions and an equally sized kerchunk_box array and - tests for elementwise equality within selection. If possible, tests max/mean/min calculations - for the selection to ensure cached values are the same. - - Expect TypeErrors later from summations which are bypassed. Other errors will exit the run. - - :param vname: (str) The name of the variable described by this box selection - - :param xbox: (obj) The native dataset selection - - :param kerchunk_box: (obj) The cloud-format (Kerchunk) dataset selection - - :param logger: (obj) Logging object for info/debug/error messages. - - :param bypass: (bool) Single value flag for bypassing numeric data errors (in the - case of values which cannot be added). - - :returns: None but will raise error if data comparison fails. - """ - logger.debug(f'Starting data comparison for {vname}') - - logger.debug('1. Flattening Arrays') - t1 = datetime.now() - - xbox = np.array(xbox).flatten() - kerchunk_box = np.array(kerchunk_box).flatten() - - logger.debug(f'2. Calculating Tolerance - {(datetime.now()-t1).total_seconds():.2f}s') - try: # Tolerance 0.1% of mean value for xarray set - tolerance = np.abs(np.nanmean(kerchunk_box))/1000 - except TypeError: # Type cannot be summed so skip all summations - tolerance = None - - logger.debug(f'3. Comparing with array_equal - {(datetime.now()-t1).total_seconds():.2f}s') - testpass = True - try: - equality = np.array_equal(xbox, kerchunk_box, equal_nan=True) - except TypeError as err: - equality = np.array_equal(xbox, kerchunk_box) - - if not equality: - logger.debug(f'3a. Comparing directly - {(datetime.now()-t1).total_seconds():.2f}s') - equality = False - for index in range(xbox.size): - v1 = xbox[index] - v2 = kerchunk_box[index] - if v1 != v2: - logger.error(f'X: {v1}, K: {v2}, idx: {index}') - raise ValidationError - - logger.debug(f'4. Comparing Max values - {(datetime.now()-t1).total_seconds():.2f}s') - try: - if np.abs(np.nanmax(kerchunk_box) - np.nanmax(xbox)) > tolerance: - logger.warning(f'Failed maximum comparison for {vname}') - logger.debug('K ' + str(np.nanmax(kerchunk_box)) + ' N ' + str(np.nanmax(xbox))) - testpass = False - except TypeError as err: - if bypass: - logger.warning(f'Max comparison skipped for non-summable values in {vname}') - else: - raise err - logger.debug(f'5. Comparing Min values - {(datetime.now()-t1).total_seconds():.2f}s') - try: - if np.abs(np.nanmin(kerchunk_box) - np.nanmin(xbox)) > tolerance: - logger.warning(f'Failed minimum comparison for {vname}') - logger.debug('K ' + str(np.nanmin(kerchunk_box)) + ' N ' + str(np.nanmin(xbox))) - testpass = False - except TypeError as err: - if bypass: - logger.warning(f'Min comparison skipped for non-summable values in {vname}') - else: - raise err - logger.debug(f'6. Comparing Mean values - {(datetime.now()-t1).total_seconds():.2f}s') - try: - if np.abs(np.nanmean(kerchunk_box) - np.nanmean(xbox)) > tolerance: - logger.warning(f'Failed mean comparison for {vname}') - logger.debug('K ' + str(np.nanmean(kerchunk_box)) + ' N ' + str(np.nanmean(xbox))) - testpass = False - except TypeError as err: - if bypass: - logger.warning(f'Mean comparison skipped for non-summable values in {vname}') - else: - raise err - if not testpass: - logger.error('Validation Error') - raise ValidationError - -def validate_shape_to_tolerance(nfiles: int, xv: str, dims: tuple, xshape: tuple, kshape: tuple, logger, proj_dir=None) -> None: # Ingest into class structure - """ - Special case function for validating a shaped array to some tolerance. This is an alternative to - opening N files, only works if each file has roughly the same total shape. Tolerance is based on - the number of files supplied, more files means the tolerance is lower? - - :param nfiles: (int) The number of native files across the whole dataset. - - :param xv: (str) The name of the variable within the dataset. - - :param dims: (tuple) A list of the names of the dimensions in this dataset. - - :param xshape: (tuple) The shape of the array from the original native files. - - :param kshape: (tuple) The shape of the array from the cloud formatted dataset. - - :param logger: (obj) Logging object for info/debug/error messages. - - :param proj_dir: (str) The project code directory path. - """ - concat_dims = ['time'] # Default value - does not work for all cases. - - tolerance = 1/(nfiles*5) - logger.info(f'Attempting shape bypass using concat-dim tolerance {tolerance*100}%') - detail = get_proj_file(proj_dir, 'detail-cfg.json') - if detail: - logger.debug('Finding concat dims recorded in details for this proj_code') - if 'concat_dims' in detail: - concat_dims = detail['concat_dims'] - - check_dims = [] - for cdim in concat_dims: - # Match to index in xobj - for index, dim in enumerate(dims): - if dim == cdim: - check_dims.append(index) - tolerance_error = False - general_shape_error = False - for cdim in range(len(xshape)): - if cdim in check_dims: - if abs(xshape[cdim] - kshape[cdim]) / kshape[cdim] > tolerance: - tolerance_error = XKShapeToleranceError( - tolerance=tolerance, - diff=abs(xshape[cdim] - kshape[cdim]) / kshape[cdim], - dim=dims[cdim] - ) - else: - if xshape[cdim] != kshape[cdim]: - general_shape_error = ShapeMismatchError(var=xv, first=kshape, second=xshape) - if general_shape_error: - raise general_shape_error - elif tolerance_error: - raise tolerance_error - else: - pass - -def _validate_shapes(xobj, kobj, nfiles: int, xv: str, logger, bypass_shape=False, proj_dir=None, concat_dims={}) -> None: # Ingest into class structure - """ - Ensure shapes are equivalent across Kerchunk/NetCDF per variable. Must account for the number - of files opened vs how many files in total. - - :param xobj: (obj) The native dataset selection. - - :param kobj: (obj) The cloud-format (Kerchunk) dataset selection - - :param nfiles: (int) The number of native files for this whole dataset. - - :param xv: (str) The name of the variable within the dataset. - - :param logger: (obj) Logging object for info/debug/error messages. - - :param bypass_shape: (bool) Switch for bypassing shape errors - diverts to tolerance testing as a backup. - - :param proj_dir: (str) The project code directory path. - - :param concat_dims: (dict) Dictionary of concatenation dimensions with their appropriate - sizes by index. (e.g {'time':100}) - - :returns: None but will raise error if shape validation fails. - """ - xshape = list(xobj[xv].shape) - kshape = list(kobj[xv].shape) - - # Perform dimension adjustments if necessary - logger.debug(f'{xv} - raw shapes - K: {kshape}, X: {xshape}') - if concat_dims: - for index, dim in enumerate(xobj[xv].dims): - if dim in concat_dims: - xshape[index] = concat_dims[dim] - else: - if 'time' in xobj[xv].dims: - try: - xshape[0] *= nfiles - except TypeError: - logger.warning(f'{xv} - {nfiles}*{xshape[0]} failed to assign') - except: - pass - logger.debug(f'{xv} - dimension-adjusted shapes - K: {kshape}, X: {xshape}') - if xshape != kshape: - # Incorrect dimensions on the shapes of the arrays - if xshape != kshape and bypass_shape: # Special bypass-shape testing - logger.info('Attempting special bypass using tolerance feature') - validate_shape_to_tolerance(nfiles, xv, xobj[xv].dims, xshape, kshape, logger, proj_dir=proj_dir) - else: - raise ShapeMismatchError(var=xv, first=kshape, second=xshape) def check_for_nan(box, bypass, logger, label=None): # Ingest into class structure """ @@ -411,17 +216,39 @@ def _count_duplicates(arr: list, source_num: int = None): missing.append(item) return missing +def slice_all_dims(data_arr: xr.DataArray, intval: int): + """ + Slice all dimensions for the DataArray according + to the integer value.""" + shape = tuple(data_arr.shape) + + for d in shape: + if d < 8: + continue + + mid = int(d/2) + step = int(d/(intval*2)) + data_arr = data_arr[mid-step:mid+step] + return data_arr + +def default_preslice(data_arr: xr.DataArray): + """ + Default preslice performs no operations on the + data array. + """ + return data_arr class ValidateDatasets(LoggedOperation): """ ValidateDatasets object for performing validations between two - pseudo-identical Xarray Dataset objects + pseudo-identical Xarray Dataset objects. """ def __init__( self, datasets: list, identifier: str, + preslice_fn: list = None, # Preslice each dataset's DataArrays to make equivalent. logger = None, label: str = None, fh: str = None, @@ -440,6 +267,11 @@ def __init__( self._identifier = identifier self._datasets = datasets + self.variables = None + self.dimensions = None + + self._preslice_fn = preslice_fn or [default_preslice for d in datasets] + if len(self._datasets) > 2: raise NotImplementedError( 'Simultaneous Validation of multiple datasets is not supported.' @@ -455,6 +287,26 @@ def __init__( def __str__(self): return f'' + + def test_dataset_var(self, var): + """ + Get a variable DataArray from the test dataset, + performing preslice functions. + """ + return self._dataset_var(var, 0) + + def control_dataset_var(self, var): + """ + Get a variable DataArray from the control dataset, + performing preslice functions. + """ + return self._dataset_var(var, 1) + + def _dataset_var(self, var, id): + """ + Perform preslice functions on the requested DataArray + """ + return self._preslice_fn[id](self._datasets[id][var]) def validate_metadata(self, allowances: dict = None): """ @@ -525,6 +377,8 @@ def _validate_selector(self, allowances: dict = None, selector: str = 'variables f'Datasets have {[len(c) for c in compare_vars]} {selector} ' 'respectively.' ) + + setattr(self, selector, set(total_list)) # Check all variables are present in all datasets. missing = _count_duplicates(total_list, source_num=len(self._datasets)) @@ -598,8 +452,8 @@ def _validate_shapes(self, ignore: list = None): vset = self._datasets[0].variables for v in vset: - test = self._datasets[0][v] - control = self._datasets[1][v] + test = self.test_dataset_var(v) + control = self.control_dataset_var(v) testshape, controlshape = {}, {} @@ -629,19 +483,33 @@ def _validate_shapes(self, ignore: list = None): f'Shape mismatch for {v} with dimension {dim} ({ts} != {cs})' ) - def validate_data(self, allowances: dict = None): + def validate_data(self): """ - Perform data validations using the growbox method for all datasets. + Perform data validations using the growbox method for all variable DataArrays. """ - pass - # Validate selection for all DataArrays in the Dataset + + if self.variables is None: + self.logger.error( + 'Unable to validate data, please ensure metadata has been validated first.' + 'Use `validate_metadata()` method.' + ) + return None + + for var in self.variables: + self.logger.info('Validating selection for ') + testvar = self.test_dataset_var(var) + controlvar = self.control_dataset_var(var) + + # Check access to the source data somehow here + # Initiate growbox method - recursive increasing box size. + self._validate_selection(var, testvar, controlvar) def _validate_selection( self, + var: str, test: xr.DataArray, control: xr.DataArray, - current : int, - name : str, + current : int = 1, recursion_limit : int = 10, ) -> bool: """ @@ -656,26 +524,26 @@ def _validate_selection( ) if current >= recursion_limit: - logger.debug('Maximum recursion depth reached') - logger.info(f'Validation for {name} not performed') + self.logger.debug('Maximum recursion depth reached') + self.logger.info(f'Validation for {var} not performed') return None - vslice = get_slice(test.shape, current) - tbox = test[vslice] - cbox = control[vslice] + tbox = slice_all_dims(test, current) + cbox = slice_all_dims(control, current) if check_for_nan(cbox): - return self._validate_selection(test, control, current+1, name, recursion_limit=recursion_limit) + return self._validate_selection(test, control, current+1, var, recursion_limit=recursion_limit) else: - return self._compare_data(name, tbox, cbox) + return self._compare_data(var, tbox, cbox) def _compare_data( self, vname: str, - control: xr.DataArray, - test: xr.DataArray + test: xr.DataArray, + control: xr.DataArray ) -> None: - """Compare a NetCDF-derived ND array to a Kerchunk-derived one. This function takes a + """ + Compare a NetCDF-derived ND array to a Kerchunk-derived one. This function takes a netcdf selection box array of n-dimensions and an equally sized test array and tests for elementwise equality within selection. If possible, tests max/mean/min calculations for the selection to ensure cached values are the same. @@ -684,11 +552,9 @@ def _compare_data( :param vname: (str) The name of the variable described by this box selection - :param control: (obj) The native dataset selection + :param test: (obj) The cloud-format (Kerchunk) dataset selection - :param test: (obj) The cloud-format (Kerchunk) dataset selection - - :param logger: (obj) Logging object for info/debug/error messages. + :param control: (obj) The native dataset selection :param bypass: (bool) Single value flag for bypassing numeric data errors (in the case of values which cannot be added). @@ -723,7 +589,7 @@ def _compare_data( v1 = control[index] v2 = test[index] if v1 != v2: - logger.error(f'X: {v1}, K: {v2}, idx: {index}') + self.logger.error(f'X: {v1}, K: {v2}, idx: {index}') raise ValidationError self.logger.debug(f'4. Comparing Max values - {(datetime.now()-t1).total_seconds():.2f}s') @@ -733,7 +599,7 @@ def _compare_data( self.logger.debug('K ' + str(np.nanmax(test)) + ' N ' + str(np.nanmax(control))) testpass = False except TypeError as err: - if bypass: + if self.bypass: self.logger.warning(f'Max comparison skipped for non-summable values in {vname}') else: raise err @@ -744,7 +610,7 @@ def _compare_data( self.logger.debug('K ' + str(np.nanmin(test)) + ' N ' + str(np.nanmin(control))) testpass = False except TypeError as err: - if bypass: + if self.bypass: self.logger.warning(f'Min comparison skipped for non-summable values in {vname}') else: raise err @@ -755,7 +621,7 @@ def _compare_data( self.logger.debug('K ' + str(np.nanmean(test)) + ' N ' + str(np.nanmean(control))) testpass = False except TypeError as err: - if bypass: + if self.bypass: self.logger.warning(f'Mean comparison skipped for non-summable values in {vname}') else: raise err @@ -763,7 +629,6 @@ def _compare_data( self.logger.error('Validation Error') raise ValidationError - class ValidateOperation(ProjectOperation): """ Encapsulate all validation testing into a single class. Instantiate for a specific project, @@ -782,11 +647,16 @@ def __init__(self, *args, **kwargs): def _run(self): # Replaces validate timestep + # test = open_product() + if self.detail_cfg.get('cfa'): # CFA-enabled validation - self._perform_cfa_validation() + # control = open_cfa() + pass else: - self._perform_source_validation() + # Use default + # Rethink preslice_fn - will need this to be dynamic. + pass def _open_cfa(self): """ @@ -812,8 +682,6 @@ def _open_product(self): attempt = 3 ) - def _open_source(self, ) - def _perform_cfa_validation(self): """ Perform validation for the selected dataset against the CFA-netCDF version. diff --git a/padocc/tests/conftest.py b/padocc/tests/conftest.py index ecb2962..342eaa7 100644 --- a/padocc/tests/conftest.py +++ b/padocc/tests/conftest.py @@ -7,7 +7,7 @@ def pytest_collection_modifyitems(items): "TestInit", "TestScan", "TestCompute", - "TestCleanup" + #"TestCleanup" ] sorted_items = items.copy() diff --git a/padocc/tests/data_creator/0DAgg/file0.nc b/padocc/tests/data_creator/0DAgg/file0.nc new file mode 100644 index 0000000000000000000000000000000000000000..d20b5bbf68a58098ac2e16b773155c1f53d3d245 GIT binary patch literal 10373 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 literal 0 HcmV?d00001 diff --git a/padocc/tests/data_creator/0DAgg/file1.nc b/padocc/tests/data_creator/0DAgg/file1.nc new file mode 100644 index 0000000000000000000000000000000000000000..51912e05a4fb962d557b428c0d0a461fed4279d6 GIT binary patch literal 10373 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 literal 0 HcmV?d00001 diff --git a/padocc/tests/data_creator/0DAgg/file2.nc b/padocc/tests/data_creator/0DAgg/file2.nc new file mode 100644 index 0000000000000000000000000000000000000000..d45f203e9dfd572b711b8a7a90bcec9e3dcdddec GIT binary patch literal 10373 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 literal 0 HcmV?d00001 diff --git a/padocc/tests/data_creator/0DAgg/file5.nc b/padocc/tests/data_creator/0DAgg/file5.nc new file mode 100644 index 0000000000000000000000000000000000000000..52e800ab35907fe4612371375f4a922259db95eb GIT binary patch literal 10373 zcmeHLd0bP+7QPS+h!IgNqM}Aca6v=_cR67VC`JT|Vv8C`lwe3`5^yhS#iy-nT`J|N zXw}vQuz+H#6-1?oD2ptj7Lhdzi%Y9kpU-#a-aCM{^yzE+{>u5moy?t?GiSatXYO}q zlaHr&chi2Rh9)KimRF3#52xQaG7T0%GW%IRo&koO48tMRI~JR1!b9FHO$`YH)WzH! zJzr423Bnsykl^9uPmUdW!6G<>31LJK9&%Z%Sf-N5WlF+=>1jxoo^wV!I*jEECP=+3 zq6^(wCXSL*y^P5osFzS09U&Y$j%@89rw(L?Q|5Ok3=Dds215e_15>64E?FF8u{k-C z9?T=Un4veUk*JQ8ld^_hj~i1xM8758vqq*9EMwwL9B`fEhPtwd-sCz-6BY=>s&LUF zXAiktA(F_1DzTE98fhU2E@dD&Hzl}470xJn{cHv-Kexa@3IGH%GRo6;rk5blca~s5 zpog2E7eV0Vq0yuqjNf1~%fn{|wwTqa#q>&RnU$`jhcnNnkQV_+3+mH_IAKY@#%OQ` zB3s&=BygMQMcIfJhtx@hDrzAtvX2>w2l*c3;VFrdh+ap6nG$UiBXWQ#y>h8sws?U| z7$v4OV-NWWRT5Q5G`_*j{!7E? z36QU7VNfLPr-k-20*@9P4`?4Fp|>DGbfv3p&%a6^-WA;#%?v(7ccyN}ls%Y|iemD2 zPp00BDG^FY@cQ`k{MXDotA$DCd5smDweX}{Z1By*yRhb=HZ(3J7JOU+m-}a>!8f{? znN6wX5d86+W43E_pq1{CJ72m7))vPr>j&n-dxA*?rja@rUcT@7k-#iSuoJ#NC9)oP z>A?q;O^LA0!e|PATpgqsm!~_Pz6IjvNoP*`HbbM%<3-~M4dl$2tK+X|fO+ciSwRD} z5WU;&-29MKXsK0(Xd9a0S+d1Oo7|fa&}dQJYkgORK@XGReYvM+5Ay+nReP;WqI5q-Ru()j?8?jmzNa`OuQ-HpJzO z8bZTOYx$#U;lYJ3YuqJOu)j>bVuQ&YI8*AGd2K`~oSn3E->wZsP_WYV&LpJ<&VDnX z)ul`ai+)V%mDX4Tp~`DBs}E?v@xiNKhK@;r`4fuo&efH`=B0N;+hui-bvdfbecwhH zl;~%@)=&#p>DOm|xl;qzT|W!Df1?SOXY*ceo7M{F`|UaXOI$AaY`v8QwPzu5>P|sz z{e6hwkDK%KQ9UeizF~N-?|twpEIK*dyAGU}g%S_H)40x(4>2IHi5Nv<3&jq+{)zhDE4hujL7 zYrCst^Y&`6GPpeDt6dtf=Q|xVJ9ZPkACOk?!Q)!6a$R97w<(9bJ<5kuqAtJ-zufaS zebnGNW~qDhpaLj7cig?#`W9G!+dnR4tp*+yxcocuc^TaO#`s#bDT&Wu=br^RInc6h z{`t6bnnpNWb4T95?Z3*9u7O`r_UsT^%T@LzhR+E`lWW z$idsJo592}|K_j4VhFakty}%_C*WDH%pcLG1ga%QL+3s|24>}h4@}e6!7kIfq?Ep` zpxN-i+p$suCok-5T^LaW({I}I_|9dpk-O1&Ktdrb7W_JJ)l)5$Bsckcl77&RzIt+` zYZ0uR(R>W9HiIC#XzQGcMwr{;6Yun-8gjP}O%0fOAO0!cQW`g@7A8NleBfhQ4E;W> z$X@xV2*R?ywl$y71Wm~&8jF_K!>JbU!(0B93B5O686(Omg8tk^yPqAegUQqPrunU^ zhlWaB*m?JRkmore;~&{-u*_tuYv4Ah9fYl`q@}=9aL=DM+W}SSqaH`bR>06QyRw18 zMlknnsxQq?1@69eg+mTDfT3quq+o~!G|8rGhmL53Q(tFPx_jop*;SE2en(D$T`7OX z@K+=b)vj6m_!2OhxISRcql>^RSC0>Wz6CzKwEoDUj5>HGwJviXcLq-GHcwf2x&nxh z)iIvRCGb4;MAA;bDmXE)c#+XXHOya_fBS=rEf77j_~4-t=O97UUzxq41&Y7m$!2q! zpgeG=Z1Qq){FsHYU;gVVj9uM)W&7bes17xE8EusfMB$8|Zgk%P+4*+55o>E<*~ZPs zF6&OgO#9-8tM}fA+l8@m$-P#%Jk+A`;U{H~-E;l!jbjR7d%xgdZQLdJ^gtZ%V|F#1 zWgY+CMx6##t(g}cK4=2<)2oe5#do2$YGQh?F7;3s`?T5VMm=bh&3i+V8lj(82zS~K zm9Seq5soHm;O?BNkaX@f@cK-}P9E6+@z-vRSYg`?5xoO8F3HS*>HKKNsb{Ld%xB%1 zkmruy^5DCGf>ues~kHe$ioAVMVv0&OQdE+SQ%k;xq|9|Cg=_bWH zNds{CE_Cxa^LZ|07J$jy-%2+T)%eqNGkCAKdpiw_XK01*YmT6araMD1JYH+t#4lLg zKK?L6H$G?j{8yqWm%SU&q}DwA4~Qn>V~Feck@S;dnjBk1;t-FevKOUFC=&@4qIPPE z7#|uz5>|p(O^k~B?bg8L374$ih&T2eJcvpvP&&}Zgy<7TD(OOHRV-5zihr>TaCU*| zBHkFCvwGxFl!v@^0zrG{2IIL1G@2Bo22uRCqVLqYFvFzBlTYc9^jqz&-N7(Rx3+fl zgFOg64U8}{9N{vFa)B&N9wQSemosFQ&-tMaDdNm~EjMgSJ9wlDq#!6lqU4f)LN3YT zJv_a+A_+;|5wYin#i+P4xvIS>R;Z8&!=z#dE*2+3TfES<&3P1oM_T_lj@$S=<1hh3 z{NHq#VW(*rXDT@fiF7w6arBl*rNKgJjQF)w6(zK2^CH0|o*P98%9$CLhpU!H(^(VV zn#&X-VkABGZJ(j4>m}{ZbAOK>H{-S8?7paKV1%YY!j2G27DuSYGdacXqmo^1aN3{_ zhU6-6VuFv!H|tDnaDhpYc!YB|!ub_BN5^a(X(I@09*&b>26i5kXW=+ucMdz5I=^GS zjyEYTkpkokI)X}=4bFShJ*eA=_m}DroNOwSHejqJ=O1a%G2lu2x*PeOLDvlJyQSyvK=ymP22FyXL-{rx;hh|8$PTnrWCq6`Vbw`; zj=~gm^jC<(CD9TUnWM{PIGty1eZo83gLJ(0p|=d@L;8^IL71aewcm00`343NXqzDz z5I!ib(5kQxaEU;Vw;~^;pm!bXXn!7kb}$;@hd0c8X4Cqk0i>EJ~tUX^Yq- zpyL29)ZpeZ^eW6AN=N9Gw8;IB1 zA1VI>={>A4uw%gDaV+vPKcBf&VGl+M42`CiHm`CXtEAyDp-$!vcpBF3|!B;sK`Z zJdPjDo!&lBF>oDA9H=NE(phrNDfF1!o0(u1W2``w;jK@4TuX literal 0 HcmV?d00001 diff --git a/padocc/tests/data_creator/0DAgg/file6.nc b/padocc/tests/data_creator/0DAgg/file6.nc new file mode 100644 index 0000000000000000000000000000000000000000..1beedbf5002c55bb3da41c76c2be6db112e545d3 GIT binary patch literal 10373 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 literal 0 HcmV?d00001 diff --git a/padocc/tests/data_creator/0DAgg/file7.nc b/padocc/tests/data_creator/0DAgg/file7.nc new file mode 100644 index 0000000000000000000000000000000000000000..f3aa2ab53c81b436a6601a0308539d6bbffb6e32 GIT binary patch literal 10373 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 F{{lkN1yKM1 literal 0 HcmV?d00001 diff --git a/padocc/tests/data_creator/1DAgg/file5.nc b/padocc/tests/data_creator/1DAgg/file5.nc new file mode 100644 index 0000000000000000000000000000000000000000..2469d7313ccffd1812d07503f31eb8468b985d42 GIT binary patch literal 10898 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 B1GxYI literal 0 HcmV?d00001 diff --git a/padocc/tests/data_creator/1DAgg/file6.nc b/padocc/tests/data_creator/1DAgg/file6.nc new file mode 100644 index 0000000000000000000000000000000000000000..85ebd8f2b320063745d4e12e4331a878213179d2 GIT binary patch literal 10898 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 F{{pq`_w4`x literal 0 HcmV?d00001 diff --git a/padocc/tests/data_creator/1DAgg/file7.nc b/padocc/tests/data_creator/1DAgg/file7.nc new file mode 100644 index 0000000000000000000000000000000000000000..64edcaf5684a7bd385b0e30f4fc2752d1491a8a1 GIT binary patch literal 10898 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 NKsN&22)vEJe*tRV6vF@j literal 0 HcmV?d00001 diff --git a/padocc/tests/data_creator/3DAgg/file0.nc b/padocc/tests/data_creator/3DAgg/file0.nc new file mode 100644 index 0000000000000000000000000000000000000000..39d172d2dfb46108120efc61be8b562528040b39 GIT binary patch literal 10898 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 literal 0 HcmV?d00001 diff --git a/padocc/tests/data_creator/3DAgg/file2.nc b/padocc/tests/data_creator/3DAgg/file2.nc new file mode 100644 index 0000000000000000000000000000000000000000..7b6941df96ebfb0b7cf44a44058939bb35a9bf53 GIT binary patch literal 10691 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 j1bPtYL7)eL9t3(2=s}am2Z0_0UPR!(1632o literal 0 HcmV?d00001 diff --git a/padocc/tests/data_creator/3DAgg/file5.nc b/padocc/tests/data_creator/3DAgg/file5.nc new file mode 100644 index 0000000000000000000000000000000000000000..768ccab373c426a259142b2e81b978ef937d8224 GIT binary patch literal 10898 zcmeHMc~lff6Cc4$Pk_KI@eZb)UZg;p7{3< z_L^aw@eL+Tj^SUyQ2fhd1dJBLXBc~he4$jXk}2d7%tvSdORA$khZ&mOEPrk~k-w^c>)esR0%6S5;aZfapn=E29 zbnp}22z-whN3@*V&t!6YntRIV8e-KMgv#qU(0jooEXrg0pnW100z9RvV96XSdxb(N zk;%m>X$1Rfyb8kz->M)U5sc*auSvjipXB4i0)Sy=VRH3w^z?D{@(l2?pXBbyFxq%p z8!bQe^~kS365nIcroqE3;3`#*C6k7fM4F;bF*`O+zGetnEgdjkYwi)b+bk zz`dZ&Hr>!Fn1~`wckUKJ!%+{y*|UVMdy)Izgkx26|DaE1@#(mc`F?&xD@LLRGH}c9MFh72c|GspH6R!@AC3$VsE_DX!$5-7EM z)@O`CEExCETiIya2)4h3uIh5)E-Y7=3`p@P1pgB|vzGhS!M=ngDMy8+FkyoCtIDyL z;L!$VvO#z`FbXGO+SOy=xyS9R(tWvLsI%B6^wv#?jA=e=?Op-*x}3gItJ?r6d*Tzk z18U&ldhdJtH7lX#-aD1bxewsbCilUq9_JzEqW+~TM%A$CskKY+FQ;L`%H6IWHW}dI za=?Dctp(Uuahbn?Ek*RK)u22GEhIlCI{!nzMg64k*M zFF!Mzzy3B@Zhk&7FDMUQocg-gHp^ z$xa80tgsmw{y)K%jSBDa%Nt?J?qZ2fNj=myAB_D78P@7EyY zNPvYtzYt!Q$4^hGxDSu}S7q*UsDi062SStX?1G_R|K`!4TL#RoJQIQKZn$y6t;?g6 z^>Ak1+5;EmRdDF>w>tCs6vNi|Ogp}LC1fOyFWCAr4rZ(}7yO`62jeRHg5iz}@a6DT z&1!24!M9jV7TdKLMt|2U%e1K;qMqcZPZE?v>deaI!pv-#^F$Re==lwZOB6r+esTeX zC##|Y?U6r-`g*CY`xUgl@Qtddtb^XSYgI)lrO-FoBuP^v86@)>5<}{4!lHw5r}`Ne z!}VcL^Mi)xLg+2k#0{(S;9JSM<329ckmZ(iT9jQ1g&Gcj@tx zca6Vnz>!MmGoo%-(6ZZbp)fCAPc0pGWCh8e{Zas1N;mrlCRD+O$XlysW>kQMtB1Ey z_ubHU-qwBFi*w*$%XX(i-5Lnz3*;A#FM#XW*?ZULmqFT6n}8Lv3h?Y^`tZOvB~Wxc zA<27GB_vOF`*C%15-dBxd(een4p;9rMJL@y@Ars4I%0D@RO@*g4?dg=f26N#JXK!~ z$F}=BxjnfCn}_-A@4C7Hw5MvBYSSig_Jil6Z7W@m-GO9{S7n(Q`=Du!GSJz&9^4zZ z9|@?)2Ai~67fDG4EZ>)%5N>@3mJjvWHfm%M1gZ2U^l%PR|6@%#=f9##}(xTOMO6MS=g`&Wa<0^2G23(CQ1>5tK##tqK%yO`@DS2;W{|dVl~)!_I)Vj#r9vM-T+gZd~!0H_QL-4y1Vi&)PS~i)Sl@kH$gd2 zoa4Qz0%jjOecn2{45lR{th^gn5B?i2w?x_Ag%xY-W~BG1g_~PW<~-h32B{mplX*98 z!}^9IsY_V_sHRPPxGS>(oSa{Nl3G>7p0mp)bl|=N~ysnx|F4@%n(< zE>$&-tgi^LD_X2XpfLv})lH z95fKbp99-GVVF_c4e%TB^O?lBdr8z2pa7L1NZuR2_pehpj&t8!a>q|ZA9AYvdE$Pq?L&lXs>CA%cbbcnpIqen| z3^@T00w4QF9j=7S8*=-&eqRNe8U_pIh7`h{T~8`Z&8xuJ`Sh&e*Qh zp=b@kz_?Q&cig>1VxQYe$m2woFF!m?QkjPXCxOAk*6H9h|BY%iJS(bBzX-5 zLV)s@x22&p84MMxJ}e7mH;s}RoP}z0X_7@krI?@ZdmHb zylHNls8RkQ=Kh%=ag8AHub3NWZhzU_O$$A}skIm;hGes@WFH*ktnEcITl)@Ub5ujq z+vd+F*==jl@xQFCkbju9**y>Y6Ki)x+F>DgHG(mAfpZ@LCv5EJFRKDZv~Wi*hjFmY}8u@>|3G9;nFme@RPEfy@E8Z9-k6Jnx{ z@$M22TND?i?_EuD*11b87lLOa!pq=u_Fk17hM=|3!PhLE(e^fIGwlvi&`hGkEZ1bq z5H&u#0(v6A6<~loND(QQM0`nVq_x7H<#-{%tPYKW))G{NCSquPLu3&`^iM2AMVq~Y zlTd;}jvUu2k|ch=;B33xt_=|;K;PO{L@>Eh5xX@|bW+qxU{-8d%V1`08Gu- zB@Ai*;CHItoG;K&LivY< z5_;0o$#jZ04JI1z4knYZ1|JkmD75W47t4G}=p8_ZXk!n*<85>@Ww;f#4h=OFBOXdn z=94a@rV*a0#wWpp(LrG-AY7@KCk@7D^aV-7{KxJ&i}XhAZ$1T|%ucZD;$P0BfF9+J9}5eeM6xbyy@gaY24(@=sxy}7?d z=(RrvZyZe2U;lfL0o@zrG*nOxsol7}GmLq$`2l+U|KZN|Uhb@RIO-vyn$mDe3qrc1 z*PqicPM_0nB^N;Hf9o&|U59=NO0`HuA&!JfI^hb=xe)zlbDO@yKbb5G33z}@rn_q$HCpQl(VzYFVP>Z0LIVcr>7a?9 z@CM_1tQ=7Yw;o$^dy#v}bl1bCGYFM8aiI4?8!XCWw9z_|37#$zrMHi-rJY=^@R7>I zN=YF5YOIA}^xtV99uW@V)-NDnIor6qvjAY2S(uzAJGi(zxw?3|+u1lfFpN5$)&=P5g{n%zLlJ+)9zjEG?xf5F&_7r9KrFkzBo4K3D(3()`~)}ZP)P@LuIT}$6K0?HXSj{N3u}jFAtcHHuF$FAK#JV%}vcMOf9;9e*Hf8 zf)?L&LRv5pLl_P27D2;NXTsUDgs!`g`>upzYcR2U8*$o|d&1E*MhjDZqR!2x&=UCRg4Y)D(TCu+ zEd4+6qKlBQ_pydkmlSxJ9TmIKHW@4(M$D|L&H-hK%aFb29)Z$CsFOG=19FT9tS@{0 z2tr=Cc{Yaaf|#i(jeEQg!B36+`9TB9V8f6-);^I}pdoFkN2<{&h?#VCxyGtWh_(uw z9A&)=CT6F<+H}4eA{PrXod%YJu5y@W!1F{1zb{idB-FtS&**;3?^J`TZOrfSU)00N zrIu+q`4#YJQiVxD!)eg#bE`Q2Tna>em3yQAjv9zL61G;&>@4JsJu_xgY%0vz-0`-D zW(j!AX&5Wm`2ei8>RL=!uZFZ#t)6!t*TWf?-&|e$bT2DX(Qzu8s~%CASCT4!+wMD=@;d^6MF>7tz}c|)oo z%P!nj5SRuYp|Ku~5f$)w(7Hi}-&DXr(fX`1jat~!T|L4kB@@KZdv@`U=4B@jrzVCx`rG{?2}vuMdIZl zgDcWOYnX{R!R8?Bs#{*C*}W8!Pw9tWjjjP*kj<) zV`2AQohxA9**T$cF{SXSxIAgrxhud-%9)gZYzbIbS{uC#tpWa-K08+@UjdO_{J9Hv zQz2q-$m&}zIWSRuf0Q&k4N3%xRo_c>fGgCyfpNig!833yx!zmfI!T+bSHiN{5;S;I(+r{Z`0tp z#nBDNgUdiSD0J4$G1<`hmf^)UA!y%sQ;9A(au(88811%-sDocOjLtLYTM1Koq~O!7B|VEFMYe1tzvMDRmtC4W>*qumk@)asmjiV$ZEmb}dRP(M zy>}t=P~SQowrI>>0m|E&|{27Agz0??Tkfb+gQN6~pP(E1p=5y#|L$x7yt^DFi{GXKMJ3 zYLM~ICpdh450X}fcd|@A1lt>@b*@DE)ed#sz3EylywIEeEObmJY+6t@Z_C(FaNWbJS|ci;>)@dMFCrg6RYi6BgvGgVOS3$=`}SK9t@Y|uqGtx^Zx>#$Y|23K zW`}pjZ8_l1bADudq6pj!B2qW4JP1d3tMEN?OCfa8aKnLC)o@QE+hgaq^{~<0-e`+% zF@%&q$iEQ&Gb9-$SDEc9fzpd<+v_)1!?*z%NhV*Gz%*6s6D!nfV6ji5;ibvh5K`lM z_u222@SDzwA+cSvAmp{q?HwMaaCuF&v}tP{Y?*sNHvLQ@O#D}V9&Aj2(_>Er*N8Gf zckqmTmL6qr#DCPv?FH5F)o+5VXSG!j5_Q@C`nC!fe*XHL{BaKSw3HUDWea z@3plMB9TAtD>@D_dsd`;aVZ~|nk=_j(KF#yqjizXmRi`p<7`*Oks_E|Yj-c^+;NC} zSnZ+R{Q$&_@tb6C90&e3W@(Kh>tT3e)28676_8|YV_g<|3b?O`p6EJDeF@hWjbN$2 zXe&#GB2I3A$d{#oM9!GV4I*-bi5w^WPyD`}jM6NP@{dVI(@EO!BQc$(c7JO!ic^?B zos8!Dgs#AOn1D@CRJi09Ddyt@v7Ri4OEDLbTu4Noa?ET?*5@>l>%b^TUPFNpqWtAu zX(&ww{l&_U%R<>jqhtnWq3T>(=r5Oj=_wPVd=@8Rv{G;uE2YXHACziFEOTJqb~jDb zDE}CDpCw4#BuM-t?#7whpLX}-g)UR5w-_dhWV23W9USAl?MgCRyEbFKO5=o<`SVD2 z+gx<~FK^3bALng$%_e`~?T*MgEaZ�PAicN_cVkK}v=|rC8=8R`|5+4_d0AUBfWT ze4U=Yy*02xOqBDqaXr$-pdVXd;@VJ0jnN545*^qQo2RbEg2hvzr6%5lnCQlMe~E`Z zii^?@t|mF}U@O($^}+iiES;GzywaP!XDlq4n^S2I`}KVtrJ!+1XFf_dy{? zhU++euOOwqOs;GhS}0aX#a{jrQ+-MnOx5kV*3Hgi0~gl%$LAO~DKJroDM`S4(_yOb zt;vPs@O&QAiF6SrNd5h%iT#5lZTc4P>Tb|?Fr@v1 z-}NE%a~#jH#d#e`mHAnlsRA}OtMF-z7x+n}U-~KSNGbc{ctgWxI4$u^RbESrhJF=k zxWsW7)0@x9SI2Ar!Ef0OKmJ(t5#l#&IOnik{DvKgfb6?zR6|8{n zv!w)d5$>fS654Znpc85sAV@u1c2SpiK=-cQ#Ocu!;ocp0^4Z?q+W`;187~r>Cmf0a zc+Zy;w>+?*n?m9(4Uj6)2UIS@>hZUhvx!~KG(ZZ{;Z*GJ+ zJGpx>c>Ce7g)zIN0FWgHoWyc2DSg2Pk?7dr<})}m3!bZn82SSb#aC|gWxjzo+a9_O5=|IU^m^*9n^skg(jyw(=2 zAQrii@fM8!Zj0!q^;$CIT5F54h(*z@Ec$0K!aD_%$yTpn&7lUlh1&}>lu-V0p@g2a z1TvlCO@m2?_XiUjY{5qb6AEo>&c!kh5_VF#IsR{{L`CJ6C7cIvn*7QA=q!r3E2f(VNd{ z7^lzaw~`B>^uKi&hU%f;&Nt=hMyJo|L7*uVeNOdr)-udQl7iCvcHWLaI|A(pv?I`t cKsy5M2(%;6jzBvC?Fh6Z(2hVm0&gSmUvK;g`~Uy| literal 0 HcmV?d00001 diff --git a/padocc/tests/data_creator/3DAgg/file7.nc b/padocc/tests/data_creator/3DAgg/file7.nc new file mode 100644 index 0000000000000000000000000000000000000000..a04218dcc9040a6529656951fbb46a1733439f7b GIT binary patch literal 10691 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 0: + ignore_attrs = True + counter += 1 + + if not os.path.isdir(options[option]): + os.makedirs(options[option]) + + ds = netCDF4.Dataset(dsname, format='NETCDF4', mode='w') + + ds.Conventions = 'DW-0.1' + ds.nemo = 'alpha13' + + create_dims(ds, dims, ignore_attrs) + create_vars(ds, vars, ignore_attrs) + + ds.close() + +if __name__ == '__main__': + main() + diff --git a/padocc/tests/data_creator/inspectpad.py b/padocc/tests/data_creator/inspectpad.py new file mode 100644 index 0000000..cab7c21 --- /dev/null +++ b/padocc/tests/data_creator/inspectpad.py @@ -0,0 +1,11 @@ +from padocc.phases import ScanOperation, KerchunkDS +from padocc.core.utils import BypassSwitch + +WORKDIR = '/home/users/dwest77/cedadev/padocc/padocc/tests/auto_testdata_dir' + + +sco = ScanOperation('1DAgg', workdir=WORKDIR, groupID = 'padocc-test-suite', verbose=1) +sco.run(forceful=True) + +com = KerchunkDS('1DAgg', workdir=WORKDIR, groupID = 'padocc-test-suite', verbose=1) +com.run(forceful=True) \ No newline at end of file diff --git a/padocc/tests/data_creator/notes.md b/padocc/tests/data_creator/notes.md new file mode 100644 index 0000000..8e3cac0 --- /dev/null +++ b/padocc/tests/data_creator/notes.md @@ -0,0 +1,28 @@ +# Padocc Test Data + +## Facets +- Global attributes +- Variable attributes +- Dimension attributes +- Mismatched attributes + +- Coordinate variables (height) +- Coordinate dimensions (time, lat, lon) +- Scalar Dimensions (axis_nbounds) +- Scalar variables +- Aggregated/concat variables (huss) +- Identical variables (height) + +1D aggregations: + - *time, lat, lon (coord dims) + - height (coord var) + - axis_nbounds (scalar_dim) + - id_num (scalar var) + - aggregated variables (rain) + - identical variables (lat_projection) + +3D aggregations: + - *time, *lat, *lon + +0D aggregations: + - no time \ No newline at end of file diff --git a/padocc/tests/test_init.py b/padocc/tests/test_init.py index 7d1e09e..4fdad98 100644 --- a/padocc/tests/test_init.py +++ b/padocc/tests/test_init.py @@ -2,25 +2,10 @@ WORKDIR = 'padocc/tests/auto_testdata_dir' -infile = 'padocc/tests/data/myfile.csv' -# Input CSV has Identifier, Path/To/Datasets, {updates}, {removals} - -groupID = 'padocc-test-suite' -workdir = '/home/username/padocc-workdir' - -mygroup = GroupOperation( - groupID, - workdir=workdir, - label='test_group' -) - -mygroup.init_from_file(infile) - - class TestInit: def test_init_basic(self, wd=WORKDIR): - infile = 'padocc/tests/data/myfile.csv' + infile = 'padocc/tests/data_creator/Aggs.csv' groupID = 'padocc-test-suite' workdir = wd diff --git a/padocc/tests/test_scan.py b/padocc/tests/test_scan.py index 284e116..94c2b50 100644 --- a/padocc/tests/test_scan.py +++ b/padocc/tests/test_scan.py @@ -1,18 +1,76 @@ from padocc.operations import GroupOperation +from padocc.phases import ScanOperation WORKDIR = 'padocc/tests/auto_testdata_dir' class TestScan: - def test_scan_basic(self, workdir=WORKDIR): + def test_scan_basic(self, workdir=WORKDIR, verbose=1): groupID = 'padocc-test-suite' process = GroupOperation( groupID, workdir=workdir, - label='test_scan', - verbose=1) + label='test_scan_basic', + verbose=verbose) process.run('scan') + def test_scan_0DAgg(self, workdir=WORKDIR, verbose=1): + groupID = 'padocc-test-suite' + + process = ScanOperation( + '0DAgg', + workdir=workdir, + groupID=groupID, + label='test_scan_0DAgg', + verbose=verbose) + + process.run() + + print(f'Successful scan - results {process.proj_code}:') + print(f' > Chunks: {process.detail_cfg["total_chunks"]}') + print(f' > Format: {process.detail_cfg["type"]}') + print(f' > Driver: {process.detail_cfg["driver"]}') + print(f' > Variables: {process.detail_cfg["variable_count"]}') + + def test_scan_1DAgg(self, workdir=WORKDIR, verbose=1): + groupID = 'padocc-test-suite' + + process = ScanOperation( + '1DAgg', + workdir=workdir, + groupID=groupID, + label='test_scan_1DAgg', + verbose=verbose) + + process.run() + + print(f'Successful scan - results {process.proj_code}:') + print(f' > Chunks: {process.detail_cfg["total_chunks"]}') + print(f' > Format: {process.detail_cfg["type"]}') + print(f' > Driver: {process.detail_cfg["driver"]}') + print(f' > Variables: {process.detail_cfg["variable_count"]}') + + def test_scan_3DAgg(self, workdir=WORKDIR, verbose=1): + groupID = 'padocc-test-suite' + + process = ScanOperation( + '3DAgg', + workdir=workdir, + groupID=groupID, + label='test_scan_3DAgg', + verbose=verbose) + + process.run() + + print(f'Successful scan - results {process.proj_code}:') + print(f' > Chunks: {process.detail_cfg["total_chunks"]}') + print(f' > Format: {process.detail_cfg["type"]}') + print(f' > Driver: {process.detail_cfg["driver"]}') + print(f' > Variables: {process.detail_cfg["variable_count"]}') + if __name__ == '__main__': - TestScan().test_scan_basic() \ No newline at end of file + TestScan().test_scan_basic(verbose=0) + TestScan().test_scan_0DAgg(verbose=0) + TestScan().test_scan_1DAgg(verbose=0) + TestScan().test_scan_3DAgg(verbose=0) \ No newline at end of file diff --git a/tests/auto_testdata_dir/groups/padocc-test-suite/blacklist_codes.csv b/tests/auto_testdata_dir/groups/padocc-test-suite/blacklist_codes.csv deleted file mode 100644 index e69de29..0000000 diff --git a/tests/auto_testdata_dir/groups/padocc-test-suite/datasets.csv b/tests/auto_testdata_dir/groups/padocc-test-suite/datasets.csv deleted file mode 100644 index e69de29..0000000 diff --git a/tests/auto_testdata_dir/groups/padocc-test-suite/proj_codes/main.txt b/tests/auto_testdata_dir/groups/padocc-test-suite/proj_codes/main.txt deleted file mode 100644 index e69de29..0000000 From c91d60940ff796b1e9842d9fb686acc9e9da51a8 Mon Sep 17 00:00:00 2001 From: dwest77 Date: Wed, 4 Dec 2024 14:51:39 +0000 Subject: [PATCH 4/4] Initial commit of CLI --- padocc/cli.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 padocc/cli.py diff --git a/padocc/cli.py b/padocc/cli.py new file mode 100644 index 0000000..81b385f --- /dev/null +++ b/padocc/cli.py @@ -0,0 +1,11 @@ +__author__ = "Daniel Westwood" +__contact__ = "daniel.westwood@stfc.ac.uk" +__copyright__ = "Copyright 2023 United Kingdom Research and Innovation" + +## PADOCC CLI for entrypoint scripts + +def main(): + pass + +if __name__ == '__main__': + main() \ No newline at end of file