def generate_ocean_nudging_factors(mesh): """ Set ocean nudging factor. This function is specific for the current Bay or Bay-Delta grid. The method and numbers are borrowed from Joseph's 'gen_nudge.f90' Parameters ---------- mesh: SchismMesh Return ------ numpy array nudging factors """ ocean_polygon = Polygon( np.array([[526062.827808, 4226489.867824], [566004.141418, 4136786.330387], [497563.036325, 4137201.624542], [499019.230051, 4222752.220430]])) nodes = mesh.nodes center = np.array((542699., 4183642.)).reshape((1, 2)) r = (32.3e3, 32.3e3) r2 = r[0] * r[0] rat = 41.e3 / r[0] rmax = 1. / 2. / 86400. # Square of distance over Square of radius rr = np.sum(np.square(np.subtract(nodes[:, :2], center)), axis=1) / r2 tnu = rmax * (rr - 1.) / (rat * rat - 1) cut = np.empty(tnu.shape) cut[:] = rmax tnu = np.minimum(tnu, rmax) cut[:] = 0. tnu = np.maximum(tnu, cut) for i, node in enumerate(nodes): # if ocean_polygon.check_point_inside_polygon(node[:2]): if not ocean_polygon.contains(Point(node[:2])): tnu[i] = 0. return tnu
def _partition_nodes_with_polygons(self, polygons, default): """ Partition the grid with the given polygons. Each node (not element) will be assigned with an integer ID which is the index of the polygons. If some polygons overlap, the latter one will trump the former one. The area that are not covered by any of the given polygons will have a default negative one attribute. Parameters ---------- polygons: list a list of polygon dict (from YAML most of time) Returns ------- numpy.ndarray attributes """ mesh = self.mesh if default is None: # Use depth attr = np.copy(mesh.nodes[:, 2]) else: # Fill default values attr = np.empty(mesh.n_nodes()) attr.fill(float(default)) for polygon in polygons: name = polygon.get('name') vertices = np.array(polygon.get('vertices')) if vertices.shape[1] != 2: raise ValueError( 'The number of coordinates in vertices are wrong.') vertices = np.array(vertices) poly_type = polygon['type'].lower() \ if 'type' in polygon else "none" attribute = polygon['attribute'] prop = {'name': name, 'type': poly_type, 'attribute': attribute} poly = SchismPolygon(shell=vertices, prop=prop) if isinstance(attribute, str): is_eqn = True expr = self._parse_attribute(attribute) else: expr = None is_eqn = False box = np.array(poly.bounds)[[0, 2, 1, 3]] nodes = mesh.find_nodes_in_box(box) empty = True for node_i in nodes: node = mesh.nodes[node_i] flag = poly.contains(Point(node[:2])) if flag: if is_eqn: try: value = eval(expr) except Exception as e: msg = "Egn: %s" % attribute self._logger.error(msg) raise ValueError( "The polygon equation does not seem to be well-formed for polygon: {} " .format(name)) else: value = attribute empty = False if poly.type == "none": attr[node_i] = value elif poly.type == "min": if attr[node_i] < value: attr[node_i] = value elif poly.type == "max": if attr[node_i] > value: attr[node_i] = value else: raise Exception( "Not supported polygon type ({}) for polygon ({})". format(poly.type, name)) if empty: msg = "This polygon contains no nodes: %s" % poly.name self._logger.error(poly) self._logger.error(msg) n_missed = sum(1 for i, a in enumerate(attr) if a == default) if n_missed > 0: msg = "There are %d nodes that do not belong " \ "to any polygon." % n_missed self._logger.warning(msg) if default is not None: msg = "Default value of %.f is used for them." % default self._logger.warning(msg) return attr
def main(): """ Just a main function """ log = setup_logger() # Read mesh fpath = "hgrid.gr3" mesh = SchismMeshIoFactory().get_reader('gr3').read(fpath) nodes_as_point = [Point(node) for node in mesh.nodes] # Create salts and nudging_factors n_nodes = mesh.n_nodes() nvrt = 23 nudging_factors = np.zeros((n_nodes, )) # Read station db fpath = "stations_utm.csv" stations_db = StationDB(fpath) # Read salt time series # obs_dir = "../../selfe/BayDeltaSELFE/Data/CDEC_realtime/salt" time_basis = datetime(2015, 8, 26) time_start = time_basis time_window = (time_start, datetime(2015, 9, 2)) # padding = timedelta(days=1) # time_window_padded = (time_window[0] - padding, time_window[1] + padding) fpath_csv_15min = '../Data/ec_15min.csv' ec_15min = read_csv_from_dss(fpath_csv_15min) fpath_csv_1hr = '../Data/ec_1hour.csv' ec_1hr = read_csv_from_dss(fpath_csv_1hr) ec_1hr.extend([ rts(ts.data[::4], ts.times[0], timedelta(hours=1), props=deepcopy(ts.props)) for ts in ec_15min ]) # Read RKI to CDEC mapping fpath_rki = '../Data/cdec_stas_nearterm.list' rki_to_cdec = read_rki_to_cdec(fpath_rki) for ts in ec_1hr: if rki_to_cdec.get(ts.props['name']) is not None: ts.props['name'] = rki_to_cdec[ts.props['name']] ec_for_nudging = select_ec_in_stations_db(ec_1hr, stations_db) # Cut out time windows ec_for_nudging = [ts.window(*time_window) for ts in ec_for_nudging] max_gap = 8 # 8 hours ec_for_nudging = [ interpolate_ts_nan(ts, max_gap=max_gap) for ts in ec_for_nudging ] ec_for_nudging = [ ts for ts in ec_for_nudging if not np.any(np.isnan(ts.data)) ] log.info("Total %d stations to nudge", len(ec_for_nudging)) if len(ec_for_nudging) < 1: log.warning("No station to nudge...") # Convert to PSU log.info("Convert EC to PSU...") list(map(ec_psu_25c, ec_for_nudging)) # Filtering, not doing it now # ts_filt, filt = med_outliers(ts, level=6., range=[100., None]) # Collect masks nodes in the mesh for nudging of CDEC stations log.info("Creating nudging masks...") radius_of_nudging = 500. radius_padding = 0. nudging_pos = [ts.props['pos'] for ts in ec_for_nudging] nudging_areas = [ Point(p).buffer(radius_of_nudging + radius_padding) for p in nudging_pos ] nudging_mask = np.full((n_nodes, ), -1., dtype=np.int32) for node_idx, node in enumerate(nodes_as_point): for nudging_idx, ball in enumerate(nudging_areas): if ball.contains(node): nudging_mask[node_idx] = nudging_idx # Nudging factor log.info("Creating nudging factors...") station_nudging_factor = 1. / 86400. station_nudging = np.zeros((n_nodes, )) for node_idx, mask in enumerate(nudging_mask): if mask >= 0: center = Point(ec_for_nudging[mask].props['pos']) dist = center.distance(nodes_as_point[node_idx]) station_nudging[node_idx] = np.max(1. - dist / radius_of_nudging, 0.) * station_nudging_factor nudging_factors += station_nudging log.info("Add ocean boundary...") # Ocean nudging # Nudging factor ocean_nudging = generate_ocean_nudging_factors(mesh) nudging_factors += ocean_nudging # Add ocean nudging time series ec_for_nudging.append(create_ocean_salt_ts(ec_for_nudging[0])) # Add the ocean nudging mask for node_idx in range(n_nodes): if ocean_nudging[node_idx] > 0.: nudging_mask[node_idx] = len(ec_for_nudging) - 1 # Write SAL_nudge.gr3 fpath_nudge_out = "SAL_nudge.gr3" log.info("Creating %s", fpath_nudge_out) SchismMeshIoFactory().get_writer('gr3').write(mesh=mesh, fpath=fpath_nudge_out, node_attr=nudging_factors) # Write nu file fpath_salt_nu = 'SAL_nu.in' log.info("Creating %s", fpath_salt_nu) if os.path.exists(fpath_salt_nu): os.remove(fpath_salt_nu) times = ec_for_nudging[0].times print(times[0], times[-1]) times = [(t - time_basis).total_seconds() for t in times] salt_background = 0.1 data = np.full((n_nodes, nvrt), salt_background) for ts_idx, t in enumerate(times): for node_idx, mask in enumerate(nudging_mask): if mask >= 0: data[node_idx, :] = ec_for_nudging[mask].data[ts_idx] with open(fpath_salt_nu, 'ab') as f: write_fortran_binary(f, np.array([t])) for i in range(data.shape[0]): write_fortran_binary(f, data[i]) # Quick copy and paste: Need to make these a function to reuse # Write TEM_nudge.gr3 fpath_nudge_out = "TEM_nudge.gr3" log.info("Creating %s", fpath_nudge_out) SchismMeshIoFactory().get_writer('gr3').write( mesh=mesh, fpath=fpath_nudge_out, node_attr=np.zeros_like(nudging_factors)) # Write nu file fpath_temp_nu = 'TEM_nu.in' log.info("Creating %s", fpath_temp_nu) if os.path.exists(fpath_temp_nu): os.remove(fpath_temp_nu) # No temperature nudging here. 20 deg C everywhere temp_background = 20. data = np.full((n_nodes, nvrt), temp_background) for ts_idx, t in enumerate(times): # for node_idx, mask in enumerate(nudging_mask): # if mask >= 0: # data[node_idx, :] = ec_for_nudging[mask].data[ts_idx] with open(fpath_temp_nu, 'ab') as f: write_fortran_binary(f, np.array([t])) for i in range(data.shape[0]): write_fortran_binary(f, data[i])