def test_init(self): ompi = OcgDist(size=2) self.assertEqual(len(ompi.mapping), 2) dim_x = Dimension('x', 5, dist=False) dim_y = Dimension('y', 11, dist=True) var_tas = Variable('tas', value=np.arange(0, 5 * 11).reshape(5, 11), dimensions=(dim_x, dim_y)) thing = Variable('thing', value=np.arange(11) * 10, dimensions=(dim_y,)) vc = VariableCollection(variables=[var_tas, thing]) child = VariableCollection(name='younger') vc.add_child(child) childer = VariableCollection(name='youngest') child.add_child(childer) dim_six = Dimension('six', 6) hidden = Variable('hidden', value=[6, 7, 8, 9, 0, 10], dimensions=dim_six) childer.add_variable(hidden) ompi.add_dimensions([dim_x, dim_y]) ompi.add_dimension(dim_six, group=hidden.group) ompi.add_variables([var_tas, thing]) ompi.add_variable(hidden) var = ompi.get_variable(hidden) self.assertIsInstance(var, dict)
def get_variable_collection(self, **kwargs): parent = VariableCollection(**kwargs) for n, v in list(self.metadata_source['variables'].items()): SourcedVariable(name=n, request_dataset=self.rd, parent=parent) GeometryVariable(name=DimensionName.GEOMETRY_DIMENSION, request_dataset=self.rd, parent=parent) crs = self.get_crs(self.metadata_source) if crs is not None: parent.add_variable(crs) return parent
def test_renamed_dimensions_on_variables(self): vc = VariableCollection() var1 = Variable(name='ugid', value=[1, 2, 3], dimensions='ocgis_geom') var2 = Variable(name='state', value=[20, 30, 40], dimensions='ocgis_geom') vc.add_variable(var1) vc.add_variable(var2) with renamed_dimensions_on_variables(vc, {'geom': ['ocgis_geom']}): for var in list(vc.values()): self.assertEqual(var.dimensions[0].name, 'geom') for var in list(vc.values()): self.assertEqual(var.dimensions[0].name, 'ocgis_geom')
def get_variable_collection(self, **kwargs): """ :rtype: :class:`ocgis.new_interface.variable.VariableCollection` """ if KeywordArgument.DRIVER not in kwargs: kwargs[KeywordArgument.DRIVER] = self dimension = list(self.dist.get_group(rank=vm.rank)['dimensions'].values())[0] ret = VariableCollection(**kwargs) for v in list(self.metadata_source['variables'].values()): nvar = SourcedVariable(name=v['name'], dimensions=dimension, dtype=v['dtype'], request_dataset=self.rd) ret.add_variable(nvar) return ret
def test_renamed_dimensions_on_variables(self): vc = VariableCollection() var1 = Variable(name='ugid', value=[1, 2, 3], dimensions='ocgis_geom') var2 = Variable(name='state', value=[20, 30, 40], dimensions='ocgis_geom') vc.add_variable(var1) vc.add_variable(var2) with renamed_dimensions_on_variables(vc, {'geom': ['ocgis_geom']}): for var in list(vc.values()): self.assertEqual(var.dimensions[0].name, 'geom') for var in list(vc.values()): self.assertEqual(var.dimensions[0].name, 'ocgis_geom')
def _get_field_write_target_(cls, field): """ Takes field data out of the OCGIS unstructured format (similar to UGRID) converting to the format expected by ESMF Unstructured metadata. """ # The driver for the current field must be NetCDF UGRID to ensure interpretability. assert field.dimension_map.get_driver() == DriverKey.NETCDF_UGRID grid = field.grid # Three-dimensional data is not supported. assert not grid.has_z # Number of coordinate dimension. This will be 3 for three-dimensional data. coord_dim = Dimension('coordDim', 2) # Transform ragged array to one-dimensional array. ############################################################# cindex = grid.cindex elements = cindex.get_value() num_element_conn_data = [e.shape[0] for e in elements.flat] length_connection_count = sum(num_element_conn_data) esmf_element_conn = np.zeros(length_connection_count, dtype=elements[0].dtype) start = 0 tag_start_index = MPITag.START_INDEX # Collapse the ragged element index array into a single dimensioned vector. This communication block finds the # size for the new array. ###################################################################################### if vm.size > 1: max_index = max([ii.max() for ii in elements.flat]) if vm.rank == 0: vm.comm.isend(max_index + 1, dest=1, tag=tag_start_index) adjust = 0 else: adjust = vm.comm.irecv(source=vm.rank - 1, tag=tag_start_index) adjust = adjust.wait() if vm.rank != vm.size - 1: vm.comm.isend(max_index + 1 + adjust, dest=vm.rank + 1, tag=tag_start_index) # Fill the new vector for the element connectivity. ############################################################ for ii in elements.flat: if vm.size > 1: if grid.archetype.has_multi: mbv = cindex.attrs[OcgisConvention.Name.MULTI_BREAK_VALUE] replace_breaks = np.where(ii == mbv)[0] else: replace_breaks = [] ii = ii + adjust if len(replace_breaks) > 0: ii[replace_breaks] = mbv esmf_element_conn[start:start + ii.shape[0]] = ii start += ii.shape[0] # Create the new data representation. ########################################################################## connection_count = create_distributed_dimension(esmf_element_conn.size, name='connectionCount') esmf_element_conn_var = Variable(name='elementConn', value=esmf_element_conn, dimensions=connection_count) esmf_element_conn_var.attrs[ CFName. LONG_NAME] = 'Node indices that define the element connectivity.' mbv = cindex.attrs.get(OcgisConvention.Name.MULTI_BREAK_VALUE) if mbv is not None: esmf_element_conn_var.attrs['polygon_break_value'] = mbv esmf_element_conn_var.attrs['start_index'] = grid.start_index ret = VariableCollection(variables=field.copy().values(), force=True) # Rename the element count dimension. original_name = ret[cindex.name].dimensions[0].name ret.rename_dimension(original_name, 'elementCount') # Add the element-node connectivity variable to the collection. ret.add_variable(esmf_element_conn_var) num_element_conn = Variable( name='numElementConn', value=num_element_conn_data, dimensions=cindex.dimensions[0], attrs={CFName.LONG_NAME: 'Number of nodes per element.'}) ret.add_variable(num_element_conn) node_coords = Variable(name='nodeCoords', dimensions=(grid.node_dim, coord_dim)) node_coords.units = 'degrees' node_coords.attrs[ CFName. LONG_NAME] = 'Node coordinate values indexed by element connectivity.' node_coords.attrs['coordinates'] = 'x y' fill = node_coords.get_value() fill[:, 0] = grid.x.get_value() fill[:, 1] = grid.y.get_value() ret.pop(grid.x.name) ret.pop(grid.y.name) ret.add_variable(node_coords) ret.attrs['gridType'] = 'unstructured' ret.attrs['version'] = '0.9' return ret
def write_subsets(self, src_template, dst_template, wgt_template, index_path): """ Write grid subsets to netCDF files using the provided filename templates. The template must contain the full file path with a single curly-bracer pair to insert the combination counter. ``wgt_template`` should not be a full path. This name is used when generating weight files. >>> template_example = '/path/to/data_{}.nc' :param str src_template: The template for the source subset file. :param str dst_template: The template for the destination subset file. :param str wgt_template: The template for the weight filename. >>> wgt_template = 'esmf_weights_{}.nc' :param index_path: Path to the output indexing netCDF. """ src_filenames = [] dst_filenames = [] wgt_filenames = [] dst_slices = [] # nzeros = len(str(reduce(lambda x, y: x * y, self.nsplits_dst))) for ctr, (sub_src, sub_dst, dst_slc) in enumerate(self.iter_src_grid_subsets(yield_dst=True), start=1): # padded = create_zero_padded_integer(ctr, nzeros) src_path = src_template.format(ctr) dst_path = dst_template.format(ctr) wgt_filename = wgt_template.format(ctr) src_filenames.append(os.path.split(src_path)[1]) dst_filenames.append(os.path.split(dst_path)[1]) wgt_filenames.append(wgt_filename) dst_slices.append(dst_slc) for target, path in zip([sub_src, sub_dst], [src_path, dst_path]): if target.is_empty: is_empty = True target = None else: is_empty = False field = Field(grid=target, is_empty=is_empty) ocgis_lh(msg='writing: {}'.format(path), level=logging.DEBUG) with vm.scoped_by_emptyable('field.write', field): if not vm.is_null: field.write(path) ocgis_lh(msg='finished writing: {}'.format(path), level=logging.DEBUG) with vm.scoped('index write', [0]): if not vm.is_null: dim = Dimension('nfiles', len(src_filenames)) vname = ['source_filename', 'destination_filename', 'weights_filename'] values = [src_filenames, dst_filenames, wgt_filenames] grid_splitter_destination = GridSplitterConstants.IndexFile.NAME_DESTINATION_VARIABLE attrs = [{'esmf_role': 'grid_splitter_source'}, {'esmf_role': grid_splitter_destination}, {'esmf_role': 'grid_splitter_weights'}] vc = VariableCollection() grid_splitter_index = GridSplitterConstants.IndexFile.NAME_INDEX_VARIABLE vidx = Variable(name=grid_splitter_index) vidx.attrs['esmf_role'] = grid_splitter_index vidx.attrs['grid_splitter_source'] = 'source_filename' vidx.attrs[GridSplitterConstants.IndexFile.NAME_DESTINATION_VARIABLE] = 'destination_filename' vidx.attrs['grid_splitter_weights'] = 'weights_filename' x_bounds = GridSplitterConstants.IndexFile.NAME_X_BOUNDS_VARIABLE vidx.attrs[x_bounds] = x_bounds y_bounds = GridSplitterConstants.IndexFile.NAME_Y_BOUNDS_VARIABLE vidx.attrs[y_bounds] = y_bounds vc.add_variable(vidx) for idx in range(len(vname)): v = Variable(name=vname[idx], dimensions=dim, dtype=str, value=values[idx], attrs=attrs[idx]) vc.add_variable(v) bounds_dimension = Dimension(name='bounds', size=2) xb = Variable(name=x_bounds, dimensions=[dim, bounds_dimension], attrs={'esmf_role': 'x_split_bounds'}, dtype=int) yb = Variable(name=y_bounds, dimensions=[dim, bounds_dimension], attrs={'esmf_role': 'y_split_bounds'}, dtype=int) x_name = self.dst_grid.x.dimensions[0].name y_name = self.dst_grid.y.dimensions[0].name for idx, slc in enumerate(dst_slices): xb.get_value()[idx, :] = slc[x_name].start, slc[x_name].stop yb.get_value()[idx, :] = slc[y_name].start, slc[y_name].stop vc.add_variable(xb) vc.add_variable(yb) vc.write(index_path) vm.barrier()
def write_chunks(self): """ Write grid subsets to netCDF files using the provided filename templates. This will also generate ESMF regridding weights for each subset if requested. """ src_filenames = [] dst_filenames = [] wgt_filenames = [] dst_slices = [] src_slices = [] index_path = self.create_full_path_from_template('index_file') # nzeros = len(str(reduce(lambda x, y: x * y, self.nchunks_dst))) ctr = 1 ocgis_lh(logger=_LOCAL_LOGGER, msg='starting self.iter_src_grid_subsets', level=logging.DEBUG) for sub_src, src_slc, sub_dst, dst_slc in self.iter_src_grid_subsets( yield_dst=True): ocgis_lh( logger=_LOCAL_LOGGER, msg='finished iteration {} for self.iter_src_grid_subsets'. format(ctr), level=logging.DEBUG) src_path = self.create_full_path_from_template('src_template', index=ctr) dst_path = self.create_full_path_from_template('dst_template', index=ctr) wgt_path = self.create_full_path_from_template('wgt_template', index=ctr) src_filenames.append(os.path.split(src_path)[1]) dst_filenames.append(os.path.split(dst_path)[1]) wgt_filenames.append(wgt_path) dst_slices.append(dst_slc) src_slices.append(src_slc) # Only write destinations if an iterator is not provided. if self.iter_dst is None: zip_args = [[sub_src, sub_dst], [src_path, dst_path]] else: zip_args = [[sub_src], [src_path]] cc = 1 for target, path in zip(*zip_args): with vm.scoped_by_emptyable('field.write' + str(cc), target): if not vm.is_null: ocgis_lh(logger=_LOCAL_LOGGER, msg='write_chunks:writing: {}'.format(path), level=logging.DEBUG) field = Field(grid=target) field.write(path) ocgis_lh( logger=_LOCAL_LOGGER, msg='write_chunks:finished writing: {}'.format( path), level=logging.DEBUG) cc += 1 # Increment the counter outside of the loop to avoid counting empty subsets. ctr += 1 # Generate an ESMF weights file if requested and at least one rank has data on it. if self.genweights and len( vm.get_live_ranks_from_object(sub_src)) > 0: vm.barrier() ocgis_lh(logger=_LOCAL_LOGGER, msg='write_chunks:writing esmf weights: {}'.format( wgt_path), level=logging.DEBUG) self.write_esmf_weights(src_path, dst_path, wgt_path, src_grid=sub_src, dst_grid=sub_dst) vm.barrier() # Global shapes require a VM global scope to collect. src_global_shape = global_grid_shape(self.src_grid) dst_global_shape = global_grid_shape(self.dst_grid) # Gather and collapse source slices as some may be empty and we write on rank 0. gathered_src_grid_slice = vm.gather(src_slices) if vm.rank == 0: len_src_slices = len(src_slices) new_src_grid_slice = [None] * len_src_slices for idx in range(len_src_slices): for rank_src_grid_slice in gathered_src_grid_slice: if rank_src_grid_slice[idx] is not None: new_src_grid_slice[idx] = rank_src_grid_slice[idx] break src_slices = new_src_grid_slice with vm.scoped('index write', [0]): if not vm.is_null: dim = Dimension('nfiles', len(src_filenames)) vname = [ 'source_filename', 'destination_filename', 'weights_filename' ] values = [src_filenames, dst_filenames, wgt_filenames] grid_chunker_destination = GridChunkerConstants.IndexFile.NAME_DESTINATION_VARIABLE attrs = [{ 'esmf_role': 'grid_chunker_source' }, { 'esmf_role': grid_chunker_destination }, { 'esmf_role': 'grid_chunker_weights' }] vc = VariableCollection() grid_chunker_index = GridChunkerConstants.IndexFile.NAME_INDEX_VARIABLE vidx = Variable(name=grid_chunker_index) vidx.attrs['esmf_role'] = grid_chunker_index vidx.attrs['grid_chunker_source'] = 'source_filename' vidx.attrs[GridChunkerConstants.IndexFile. NAME_DESTINATION_VARIABLE] = 'destination_filename' vidx.attrs['grid_chunker_weights'] = 'weights_filename' vidx.attrs[GridChunkerConstants.IndexFile. NAME_SRC_GRID_SHAPE] = src_global_shape vidx.attrs[GridChunkerConstants.IndexFile. NAME_DST_GRID_SHAPE] = dst_global_shape vc.add_variable(vidx) for idx in range(len(vname)): v = Variable(name=vname[idx], dimensions=dim, dtype=str, value=values[idx], attrs=attrs[idx]) vc.add_variable(v) bounds_dimension = Dimension(name='bounds', size=2) # TODO: This needs to work with four dimensions. # Source ----------------------------------------------------------------------------------------------- self.src_grid._gc_create_index_bounds_(RegriddingRole.SOURCE, vidx, vc, src_slices, dim, bounds_dimension) # Destination ------------------------------------------------------------------------------------------ self.dst_grid._gc_create_index_bounds_( RegriddingRole.DESTINATION, vidx, vc, dst_slices, dim, bounds_dimension) vc.write(index_path) vm.barrier()
def create_merged_weight_file(self, merged_weight_filename, strict=False): """ Merge weight file chunks to a single, global weight file. :param str merged_weight_filename: Path to the merged weight file. :param bool strict: If ``False``, allow "missing" files where the iterator index cannot create a found file. It is best to leave these ``False`` as not all source and destinations are mapped. If ``True``, raise an """ if vm.size > 1: raise ValueError( "'create_merged_weight_file' does not work in parallel") index_filename = self.create_full_path_from_template('index_file') ifile = RequestDataset(uri=index_filename).get() ifile.load() ifc = GridChunkerConstants.IndexFile gidx = ifile[ifc.NAME_INDEX_VARIABLE].attrs src_global_shape = gidx[ifc.NAME_SRC_GRID_SHAPE] dst_global_shape = gidx[ifc.NAME_DST_GRID_SHAPE] # Get the global weight dimension size. n_s_size = 0 weight_filename = ifile[gidx[ifc.NAME_WEIGHTS_VARIABLE]] wv = weight_filename.join_string_value() split_weight_file_directory = self.paths['wd'] for wfn in map( lambda x: os.path.join(split_weight_file_directory, os.path.split(x)[1]), wv): ocgis_lh(msg="current merge weight file target: {}".format(wfn), level=logging.DEBUG, logger=_LOCAL_LOGGER) if not os.path.exists(wfn): if strict: raise IOError(wfn) else: continue curr_dimsize = RequestDataset(wfn).get().dimensions['n_s'].size # ESMF writes the weight file, but it may be empty if there are no generated weights. if curr_dimsize is not None: n_s_size += curr_dimsize # Create output weight file. wf_varnames = ['row', 'col', 'S'] wf_dtypes = [np.int32, np.int32, np.float64] vc = VariableCollection() dim = Dimension('n_s', n_s_size) for w, wd in zip(wf_varnames, wf_dtypes): var = Variable(name=w, dimensions=dim, dtype=wd) vc.add_variable(var) vc.write(merged_weight_filename) # Transfer weights to the merged file. sidx = 0 src_indices = self.src_grid._gc_create_global_indices_( src_global_shape) dst_indices = self.dst_grid._gc_create_global_indices_( dst_global_shape) out_wds = nc.Dataset(merged_weight_filename, 'a') for ii, wfn in enumerate( map(lambda x: os.path.join(split_weight_file_directory, x), wv)): if not os.path.exists(wfn): if strict: raise IOError(wfn) else: continue wdata = RequestDataset(wfn).get() for wvn in wf_varnames: odata = wdata[wvn].get_value() try: split_grids_directory = self.paths['wd'] odata = self._gc_remap_weight_variable_( ii, wvn, odata, src_indices, dst_indices, ifile, gidx, split_grids_directory=split_grids_directory) except IndexError as e: msg = "Weight filename: '{}'; Weight Variable Name: '{}'. {}".format( wfn, wvn, str(e)) raise IndexError(msg) out_wds[wvn][sidx:sidx + odata.size] = odata out_wds.sync() sidx += odata.size out_wds.close()
def _convert_to_ugrid_(field): """ Takes field data out of the OCGIS unstructured format (similar to UGRID) converting to the format expected by ESMF Unstructured metadata. """ # The driver for the current field must be NetCDF UGRID to ensure interpretability. assert field.dimension_map.get_driver() == DriverKey.NETCDF_UGRID grid = field.grid # Three-dimensional data is not supported. assert not grid.has_z # Number of coordinate dimension. This will be 3 for three-dimensional data. coord_dim = Dimension('coordDim', 2) # Transform ragged array to one-dimensional array. ############################################################# cindex = grid.cindex elements = cindex.get_value() num_element_conn_data = [e.shape[0] for e in elements.flat] length_connection_count = sum(num_element_conn_data) esmf_element_conn = np.zeros(length_connection_count, dtype=elements[0].dtype) start = 0 tag_start_index = MPITag.START_INDEX # Collapse the ragged element index array into a single dimensioned vector. This communication block finds the # size for the new array. ###################################################################################### if vm.size > 1: max_index = max([ii.max() for ii in elements.flat]) if vm.rank == 0: vm.comm.isend(max_index + 1, dest=1, tag=tag_start_index) adjust = 0 else: adjust = vm.comm.irecv(source=vm.rank - 1, tag=tag_start_index) adjust = adjust.wait() if vm.rank != vm.size - 1: vm.comm.isend(max_index + 1 + adjust, dest=vm.rank + 1, tag=tag_start_index) # Fill the new vector for the element connectivity. ############################################################ for ii in elements.flat: if vm.size > 1: if grid.archetype.has_multi: mbv = cindex.attrs[OcgisConvention.Name.MULTI_BREAK_VALUE] replace_breaks = np.where(ii == mbv)[0] else: replace_breaks = [] ii = ii + adjust if len(replace_breaks) > 0: ii[replace_breaks] = mbv esmf_element_conn[start: start + ii.shape[0]] = ii start += ii.shape[0] # Create the new data representation. ########################################################################## connection_count = create_distributed_dimension(esmf_element_conn.size, name='connectionCount') esmf_element_conn_var = Variable(name='elementConn', value=esmf_element_conn, dimensions=connection_count, dtype=np.int32) esmf_element_conn_var.attrs[CFName.LONG_NAME] = 'Node indices that define the element connectivity.' mbv = cindex.attrs.get(OcgisConvention.Name.MULTI_BREAK_VALUE) if mbv is not None: esmf_element_conn_var.attrs['polygon_break_value'] = mbv esmf_element_conn_var.attrs['start_index'] = grid.start_index ret = VariableCollection(variables=field.copy().values(), force=True) # Rename the element count dimension. original_name = ret[cindex.name].dimensions[0].name ret.rename_dimension(original_name, 'elementCount') # Add the element-node connectivity variable to the collection. ret.add_variable(esmf_element_conn_var) num_element_conn = Variable(name='numElementConn', value=num_element_conn_data, dimensions=cindex.dimensions[0], attrs={CFName.LONG_NAME: 'Number of nodes per element.'}, dtype=np.int32) ret.add_variable(num_element_conn) # Check that the node count dimension is appropriately named. gn_name = grid.node_dim.name if gn_name != 'nodeCount': ret.dimensions[gn_name] = ret.dimensions[gn_name].copy() ret.rename_dimension(gn_name, 'nodeCount') node_coords = Variable(name='nodeCoords', dimensions=(ret.dimensions['nodeCount'], coord_dim)) node_coords.units = 'degrees' node_coords.attrs[CFName.LONG_NAME] = 'Node coordinate values indexed by element connectivity.' node_coords.attrs['coordinates'] = 'x y' fill = node_coords.get_value() fill[:, 0] = grid.x.get_value() fill[:, 1] = grid.y.get_value() ret.pop(grid.x.name) ret.pop(grid.y.name) ret.add_variable(node_coords) ret.attrs['gridType'] = 'unstructured' ret.attrs['version'] = '0.9' # Remove the coordinate index, this does not matter. if field.grid.cindex is not None: ret.remove_variable(field.grid.cindex.name) return ret
def write_chunks(self): """ Write grid subsets to netCDF files using the provided filename templates. This will also generate ESMF regridding weights for each subset if requested. """ src_filenames = [] dst_filenames = [] wgt_filenames = [] dst_slices = [] src_slices = [] index_path = self.create_full_path_from_template('index_file') # nzeros = len(str(reduce(lambda x, y: x * y, self.nchunks_dst))) ctr = 1 ocgis_lh(logger='grid_chunker', msg='starting self.iter_src_grid_subsets', level=logging.DEBUG) for sub_src, src_slc, sub_dst, dst_slc in self.iter_src_grid_subsets(yield_dst=True): ocgis_lh(logger='grid_chunker', msg='finished iteration {} for self.iter_src_grid_subsets'.format(ctr), level=logging.DEBUG) src_path = self.create_full_path_from_template('src_template', index=ctr) dst_path = self.create_full_path_from_template('dst_template', index=ctr) wgt_path = self.create_full_path_from_template('wgt_template', index=ctr) src_filenames.append(os.path.split(src_path)[1]) dst_filenames.append(os.path.split(dst_path)[1]) wgt_filenames.append(wgt_path) dst_slices.append(dst_slc) src_slices.append(src_slc) # Only write destinations if an iterator is not provided. if self.iter_dst is None: zip_args = [[sub_src, sub_dst], [src_path, dst_path]] else: zip_args = [[sub_src], [src_path]] cc = 1 for target, path in zip(*zip_args): with vm.scoped_by_emptyable('field.write' + str(cc), target): if not vm.is_null: ocgis_lh(logger='grid_chunker', msg='write_chunks:writing: {}'.format(path), level=logging.DEBUG) field = Field(grid=target) field.write(path) ocgis_lh(logger='grid_chunker', msg='write_chunks:finished writing: {}'.format(path), level=logging.DEBUG) cc += 1 # Increment the counter outside of the loop to avoid counting empty subsets. ctr += 1 # Generate an ESMF weights file if requested and at least one rank has data on it. if self.genweights and len(vm.get_live_ranks_from_object(sub_src)) > 0: vm.barrier() self.write_esmf_weights(src_path, dst_path, wgt_path, src_grid=sub_src, dst_grid=sub_dst) vm.barrier() # Global shapes require a VM global scope to collect. src_global_shape = global_grid_shape(self.src_grid) dst_global_shape = global_grid_shape(self.dst_grid) # Gather and collapse source slices as some may be empty and we write on rank 0. gathered_src_grid_slice = vm.gather(src_slices) if vm.rank == 0: len_src_slices = len(src_slices) new_src_grid_slice = [None] * len_src_slices for idx in range(len_src_slices): for rank_src_grid_slice in gathered_src_grid_slice: if rank_src_grid_slice[idx] is not None: new_src_grid_slice[idx] = rank_src_grid_slice[idx] break src_slices = new_src_grid_slice with vm.scoped('index write', [0]): if not vm.is_null: dim = Dimension('nfiles', len(src_filenames)) vname = ['source_filename', 'destination_filename', 'weights_filename'] values = [src_filenames, dst_filenames, wgt_filenames] grid_chunker_destination = GridChunkerConstants.IndexFile.NAME_DESTINATION_VARIABLE attrs = [{'esmf_role': 'grid_chunker_source'}, {'esmf_role': grid_chunker_destination}, {'esmf_role': 'grid_chunker_weights'}] vc = VariableCollection() grid_chunker_index = GridChunkerConstants.IndexFile.NAME_INDEX_VARIABLE vidx = Variable(name=grid_chunker_index) vidx.attrs['esmf_role'] = grid_chunker_index vidx.attrs['grid_chunker_source'] = 'source_filename' vidx.attrs[GridChunkerConstants.IndexFile.NAME_DESTINATION_VARIABLE] = 'destination_filename' vidx.attrs['grid_chunker_weights'] = 'weights_filename' vidx.attrs[GridChunkerConstants.IndexFile.NAME_SRC_GRID_SHAPE] = src_global_shape vidx.attrs[GridChunkerConstants.IndexFile.NAME_DST_GRID_SHAPE] = dst_global_shape vc.add_variable(vidx) for idx in range(len(vname)): v = Variable(name=vname[idx], dimensions=dim, dtype=str, value=values[idx], attrs=attrs[idx]) vc.add_variable(v) bounds_dimension = Dimension(name='bounds', size=2) # TODO: This needs to work with four dimensions. # Source ----------------------------------------------------------------------------------------------- self.src_grid._gc_create_index_bounds_(RegriddingRole.SOURCE, vidx, vc, src_slices, dim, bounds_dimension) # Destination ------------------------------------------------------------------------------------------ self.dst_grid._gc_create_index_bounds_(RegriddingRole.DESTINATION, vidx, vc, dst_slices, dim, bounds_dimension) vc.write(index_path) vm.barrier()
def create_merged_weight_file(self, merged_weight_filename, strict=False): """ Merge weight file chunks to a single, global weight file. :param str merged_weight_filename: Path to the merged weight file. :param bool strict: If ``False``, allow "missing" files where the iterator index cannot create a found file. It is best to leave these ``False`` as not all source and destinations are mapped. If ``True``, raise an """ if vm.size > 1: raise ValueError("'create_merged_weight_file' does not work in parallel") index_filename = self.create_full_path_from_template('index_file') ifile = RequestDataset(uri=index_filename).get() ifile.load() ifc = GridChunkerConstants.IndexFile gidx = ifile[ifc.NAME_INDEX_VARIABLE].attrs src_global_shape = gidx[ifc.NAME_SRC_GRID_SHAPE] dst_global_shape = gidx[ifc.NAME_DST_GRID_SHAPE] # Get the global weight dimension size. n_s_size = 0 weight_filename = ifile[gidx[ifc.NAME_WEIGHTS_VARIABLE]] wv = weight_filename.join_string_value() split_weight_file_directory = self.paths['wd'] for wfn in map(lambda x: os.path.join(split_weight_file_directory, os.path.split(x)[1]), wv): if not os.path.exists(wfn): if strict: raise IOError(wfn) else: continue n_s_size += RequestDataset(wfn).get().dimensions['n_s'].size # Create output weight file. wf_varnames = ['row', 'col', 'S'] wf_dtypes = [np.int32, np.int32, np.float64] vc = VariableCollection() dim = Dimension('n_s', n_s_size) for w, wd in zip(wf_varnames, wf_dtypes): var = Variable(name=w, dimensions=dim, dtype=wd) vc.add_variable(var) vc.write(merged_weight_filename) # Transfer weights to the merged file. sidx = 0 src_indices = self.src_grid._gc_create_global_indices_(src_global_shape) dst_indices = self.dst_grid._gc_create_global_indices_(dst_global_shape) out_wds = nc.Dataset(merged_weight_filename, 'a') for ii, wfn in enumerate(map(lambda x: os.path.join(split_weight_file_directory, x), wv)): if not os.path.exists(wfn): if strict: raise IOError(wfn) else: continue wdata = RequestDataset(wfn).get() for wvn in wf_varnames: odata = wdata[wvn].get_value() try: split_grids_directory = self.paths['wd'] odata = self._gc_remap_weight_variable_(ii, wvn, odata, src_indices, dst_indices, ifile, gidx, split_grids_directory=split_grids_directory) except IndexError as e: msg = "Weight filename: '{}'; Weight Variable Name: '{}'. {}".format(wfn, wvn, str(e)) raise IndexError(msg) out_wds[wvn][sidx:sidx + odata.size] = odata out_wds.sync() sidx += odata.size out_wds.close()