Beispiel #1
0
    def stationary_test(self):
        """Processed the stationary moving-bed tests."""

        # Assign data from transect to local variables
        trans_data = self.transect
        in_transect_idx = self.transect.in_transect_idx
        bt_valid = trans_data.boat_vel.bt_vel.valid_data[0, in_transect_idx]

        # Check to see that there is valid bottom track data
        self.messages = []
        if np.nansum(bt_valid) > 0:
            # Assign data to local variables
            wt_u = trans_data.w_vel.u_processed_mps[:, in_transect_idx]
            wt_v = trans_data.w_vel.v_processed_mps[:, in_transect_idx]
            ens_duration = trans_data.date_time.ens_duration_sec[in_transect_idx]
            bt_u = trans_data.boat_vel.bt_vel.u_processed_mps[in_transect_idx]
            bt_v = trans_data.boat_vel.bt_vel.v_processed_mps[in_transect_idx]
            
            bin_depth = trans_data.depths.bt_depths.depth_cell_depth_m[:, in_transect_idx]
            trans_select = getattr(trans_data.depths, trans_data.depths.selected)
            depth_ens = trans_select.depth_processed_m[in_transect_idx]

            nb_u, nb_v, unit_nbu, unit_nbv = self.near_bed_velocity(wt_u, wt_v, depth_ens, bin_depth)
            
            # Compute bottom track parallel to water velocity
            unit_nb_vel = np.vstack([unit_nbu, unit_nbv])
            bt_vel = np.vstack([bt_u, bt_v])
            bt_vel_up_strm = -1 * np.sum(bt_vel * unit_nb_vel, 0)
            bt_up_strm_dist = bt_vel_up_strm * ens_duration
            bt_up_strm_dist_cum = np.nancumsum(bt_up_strm_dist)
            
            # Compute bottom track perpendicular to water velocity
            nb_vel_ang, _ = cart2pol(unit_nbu, unit_nbv)
            nb_vel_unit_cs1, nb_vel_unit_cs2 = pol2cart(nb_vel_ang + np.pi / 2, np.ones(nb_vel_ang.shape))
            nb_vel_unit_cs = np.vstack([nb_vel_unit_cs1, nb_vel_unit_cs2])
            bt_vel_cs = np.sum(bt_vel * nb_vel_unit_cs, 0)
            bt_cs_strm_dist = bt_vel_cs * ens_duration
            bt_cs_strm_dist_cum = np.nancumsum(bt_cs_strm_dist)
            
            # Compute cumulative mean moving bed velocity
            valid_bt_vel_up_strm = np.isnan(bt_vel_up_strm) == False
            mb_vel = np.nancumsum(bt_vel_up_strm) / np.nancumsum(valid_bt_vel_up_strm)
            
            # Compute the average ensemble velocities corrected for moving bed
            if mb_vel[-1] > 0:
                u_corrected = np.add(wt_u, (unit_nb_vel[0, :]) * bt_vel_up_strm)
                v_corrected = np.add(wt_v, (unit_nb_vel[1, :]) * bt_vel_up_strm)
            else:
                u_corrected = wt_u
                v_corrected = wt_v
                
            # Compute the mean of the ensemble magnitudes

            # Mean is computed using magnitudes because if a Streampro with no compass is the data source the change
            # in direction could be either real change in water direction or an uncompensated turn of the floating
            # platform. This approach is the best compromise when there is no compass or the compass is unreliable,
            # which is often why the stationary method is used. A weighted average is used to account for the possible
            # change in cell size within and ensemble for the RiverRay and RiverPro.

            mag = np.sqrt(u_corrected**2 + v_corrected**2)
            depth_cell_size = trans_data.depths.bt_depths.depth_cell_size_m[:, in_transect_idx]
            depth_cell_size[np.isnan(mag)] = np.nan
            mag_w = mag * depth_cell_size
            avg_vel = np.nansum(mag_w) / np.nansum(depth_cell_size)
            pot_error_per = (mb_vel[-1] / avg_vel) * 100
            if pot_error_per < 0:
                pot_error_per = 0   
                
            # Compute percent invalid bottom track
            self.percent_invalid_bt = (np.nansum(bt_valid == False) / len(bt_valid)) * 100
            # Store computed test characteristics
            self.dist_us_m = bt_up_strm_dist_cum[-1]
            self.duration_sec = np.nansum(ens_duration)
            self.compass_diff_deg = []
            self.flow_dir = []
            self.mb_dir = []
            self.flow_spd_mps = avg_vel
            self.mb_spd_mps = mb_vel[-1]
            self.percent_mb = pot_error_per
            self.near_bed_speed_mps = np.sqrt(np.nanmean(nb_u)**2 + np.nanmean(nb_v)**2)
            self.stationary_us_track = bt_up_strm_dist_cum
            self.stationary_cs_track = bt_cs_strm_dist_cum
            self.stationary_mb_vel = mb_vel
            
            # Quality check
            self.test_quality = 'Good'
            # Check duration
            if self.duration_sec < 300:
                self.messages.append('WARNING - Duration of stationary test is less than 5 minutes')
                self.test_quality = 'Warnings'
                
            # Check validity of mean moving-bed velocity
            if self.duration_sec > 60:
                mb_vel_std = np.nanstd(mb_vel[-30:])
                cov = mb_vel_std / mb_vel[-1]
                if cov > 0.25 and mb_vel_std > 0.03:
                    self.messages.append('WARNING - Moving-bed velocity may not be consistent. '
                                         + 'Average maybe inaccurate.')
                    self.test_quality = 'Warnings'
                    
            # Check percentage of invalid BT data
            if np.nansum(ens_duration[valid_bt_vel_up_strm]) <= 120:
                
                self.messages.append('ERROR - Total duration of valid BT data is insufficient for a valid test.')
                self.test_quality = 'Errors'
                self.moving_bed = 'Unknown'
            elif self.percent_invalid_bt > 10:
                self.messages.append('WARNING - Number of ensembles with invalid bottom track exceeds 10%')
                self.test_quality = 'Warnings'
                
            # Determine if the test indicates a moving bed
            if self.test_quality != 'Errors':
                if self.percent_mb > 1:
                    self.moving_bed = 'Yes'
                else:
                    self.moving_bed = 'No'

        else:
            self.messages.append('ERROR - Stationary moving-bed test has no valid bottom track data.')
            self.test_quality = 'Errors'
            self.moving_bed = 'Unknown'
Beispiel #2
0
    def populate_data(self, transect, data_type, threshold, data_extent=None):
        """Computes the normalized values for a single transect.

        Parameters
        ----------
        transect: TransectData
            Object of TransectData
        data_type: str
            Type of data (v, q, V, or Q)
        threshold: int
            Number of data points in an increment for the increment to be valid.
        data_extent: list
            Defines percent of data from start of transect to use, default [0, 100]
        """

        # If the data extent is not defined set data_extent to zero to trigger all data to be used
        if data_extent is None:
            data_extent = [0, 100]
            
        # Determine number of transects to be processed
        # n_cells = np.nan
        # n_ens = np.nan
        
        filename = transect.file_name
        in_transect_idx = transect.in_transect_idx

        depths_selected = getattr(transect.depths, transect.depths.selected)
        cell_depth = np.copy(depths_selected.depth_cell_depth_m[:, in_transect_idx])
        cells_above_sl = transect.w_vel.cells_above_sl[:, in_transect_idx]
        cell_depth[cells_above_sl == False] = np.nan
        depth_ens = np.copy(depths_selected.depth_processed_m[in_transect_idx])

        w_vel_x = np.copy(transect.w_vel.u_processed_mps[:, in_transect_idx])
        w_vel_y = np.copy(transect.w_vel.v_processed_mps[:, in_transect_idx])

        invalid_data = np.logical_not(transect.w_vel.valid_data[0, :, in_transect_idx]).T
        w_vel_x[invalid_data] = np.nan
        w_vel_y[invalid_data] = np.nan
        
        boat_select = getattr(transect.boat_vel, transect.boat_vel.selected)
        if boat_select is not None:
            bt_vel_x = np.copy(boat_select.u_processed_mps[in_transect_idx])
            bt_vel_y = np.copy(boat_select.v_processed_mps[in_transect_idx])
        else:
            bt_vel_x = np.tile([np.nan], transect.boat_vel.bt_vel.u_processed_mps[in_transect_idx].shape)
            bt_vel_y = np.tile([np.nan], transect.boat_vel.bt_vel.u_processed_mps[in_transect_idx].shape)
            
        # Compute normalized cell depth by average depth in each ensemble
        norm_cell_depth = np.divide(cell_depth, depth_ens)
        norm_cell_depth[norm_cell_depth < 0] = np.nan
        # If data type is discharge compute unit discharge for each cell
        if data_type.lower() == 'q':
            # Compute the cross product for each cell
            unit = np.multiply(w_vel_x, bt_vel_y) - np.multiply(w_vel_y, bt_vel_x)
        else:
            # Compute mean velocity components in each ensemble
            w_vel_mean_1 = np.nanmean(w_vel_x, 0)
            w_vel_mean_2 = np.nanmean(w_vel_y, 0)

            # Compute a unit vector
            direction, _ = cart2pol(w_vel_mean_1, w_vel_mean_2)
            unit_vec_1, unit_vec_2 = pol2cart(direction, 1)
            unit_vec = np.vstack([unit_vec_1, unit_vec_2])
            
            # Compute the velocity magnitude in the direction of the mean velocity of each
            # ensemble using the dot product and unit vector
            unit = np.tile([np.nan], w_vel_x.shape)
            for i in range(w_vel_x.shape[0]):
                unit[i, :] = np.vstack([w_vel_x[i, :], w_vel_y[i, :]]).dot(unit_vec)
                                       
        # Compute Total
        unit_total = np.nansum(np.nansum(unit), 0)
        
        # Adjust to positive value
        if unit_total < 0:
            unit *= -1
            
        # Compute normalize unit values
        unit_norm = np.divide(unit, np.abs(np.nanmean(unit, 0)))
        
        # Apply extents if they have been specified
        if data_extent[0] != 0 or data_extent[1] != 100:
            
            # Unit discharge is computed here because the unit norm could be based on velocity
            unit = np.multiply(w_vel_x, bt_vel_y) - np.multiply(w_vel_y, bt_vel_x)
            unit_ens = np.nansum(unit, 0)
            unit_total = np.nancumsum(unit_ens)
            
            # Adjust so total discharge is positive
            if unit_total[-1] < 0:
                unit_total *= -1
                
            # Apply extents
            unit_lower = unit_total[-1] * data_extent[0] / 100
            unit_upper = unit_total[-1] * data_extent[1] / 100
            idx_extent = np.where(np.logical_and(np.greater(unit_total, unit_lower),
                                                 np.less(unit_total, unit_upper)))[0]
            unit_norm = unit_norm[:, idx_extent]
            norm_cell_depth = norm_cell_depth[:, idx_extent]
            
        # If whole profile is negative make positive
        idx_neg1 = np.tile([np.nan], [unit_norm.shape[1], 1])
        idx_neg2 = np.tile([np.nan], [unit_norm.shape[1], 1])
        for c in range(unit_norm.shape[1]):
            idx_neg1[c] = len(np.where(unit_norm[:, c] < 0)[0])
            idx_neg2[c] = len(np.where(np.isnan(unit_norm[:, c]) == False)[0])
        idx_neg = np.squeeze(idx_neg1) == np.squeeze(idx_neg2)
        unit_norm[:, idx_neg] = unit_norm[:, idx_neg] * -1

        # Store results
        self.file_name = filename
        self.data_extent = data_extent
        self.data_type = data_type
        self.cell_depth_normalized = norm_cell_depth
        self.unit_normalized = unit_norm
        self.compute_stats(threshold)
Beispiel #3
0
    def loop_test(self, ens_duration=None):
        """Process loop moving bed test.

        Parameters
        ----------
        ens_duration: np.array(float)
            Duration of each ensemble, in sec
        """

        # Assign data from transect to local variables
        self.transect.boat_interpolations(update=False, target='BT', method='Linear')
        self.transect.boat_interpolations(update=False, target='GPS', method='Linear')
        trans_data = self.transect
        in_transect_idx = trans_data.in_transect_idx
        n_ensembles = len(in_transect_idx)
        bt_valid = trans_data.boat_vel.bt_vel.valid_data[0, in_transect_idx]

        # Set variables to defaults
        self.messages = []
        vel_criteria = 0.012

        # Check that there is some valid BT data
        if np.nansum(bt_valid) > 1:
            wt_u = trans_data.w_vel.u_processed_mps[:, in_transect_idx]
            wt_v = trans_data.w_vel.v_processed_mps[:, in_transect_idx]
            if ens_duration is None:
                ens_duration = trans_data.date_time.ens_duration_sec[in_transect_idx]
            # else:
            #     ens_duration = kargs[:]
            bt_u = trans_data.boat_vel.bt_vel.u_processed_mps[in_transect_idx]
            bt_v = trans_data.boat_vel.bt_vel.v_processed_mps[in_transect_idx]
            bin_size = trans_data.depths.bt_depths.depth_cell_size_m[:, in_transect_idx]

            # Compute discharge weighted mean velocity components for the
            # purposed of computing the mean flow direction
            xprod = QComp.cross_product(transect=trans_data)
            q = QComp.discharge_middle_cells(xprod, trans_data, ens_duration)
            wght = np.abs(q)
            se = np.nansum(np.nansum(wt_u * wght)) / np.nansum(np.nansum(wght))
            sn = np.nansum(np.nansum(wt_v * wght)) / np.nansum(np.nansum(wght))
            direct, flow_speed_q = cart2pol(se, sn)
            # Compute flow speed and direction
            self.flow_dir = rad2azdeg(direct)
            
            # Compute the area weighted mean velocity components for the
            # purposed of computing the mean flow speed. Area weighting is used for flow speed instead of
            # discharge so that the flow speed is not included in the weighting used to compute the mean flow speed.
            wght_area = np.multiply(np.multiply(np.sqrt(bt_u ** 2 + bt_v ** 2), bin_size), ens_duration)
            idx = np.where(np.isnan(wt_u) == False)
            se = np.nansum(np.nansum(wt_u[idx] * wght_area[idx])) / np.nansum(np.nansum(wght_area[idx]))
            sn = np.nansum(np.nansum(wt_v[idx] * wght_area[idx])) / np.nansum(np.nansum(wght_area[idx]))
            dir_a, self.flow_spd_mps = cart2pol(se, sn)
            # flow_dir_a = rad2azdeg(dir_a)

            # Compute closure distance and direction
            bt_x = np.nancumsum(bt_u * ens_duration)
            bt_y = np.nancumsum(bt_v * ens_duration)
            direct, self.dist_us_m = cart2pol(bt_x[-1], bt_y[-1])
            self.mb_dir = rad2azdeg(direct)
            
            # Compute duration of test
            self.duration_sec = np.nansum(ens_duration)
            
            # Compute the moving-bed velocity
            self.mb_spd_mps = self.dist_us_m / self.duration_sec
            
            # Compute potential error in BT referenced discharge
            self.percent_mb = (self.mb_spd_mps / (self.flow_spd_mps + self.mb_spd_mps)) * 100
            
            # Assess invalid bottom track
            # Compute percent invalid bottom track
            self.percent_invalid_bt = (np.nansum(bt_valid == False) / len(bt_valid)) * 100
            
            # Determine if more than 9 consecutive seconds of invalid BT occurred
            consect_bt_time = np.zeros(n_ensembles)
            for n in range(1, n_ensembles):
                if bt_valid[n]:
                    consect_bt_time[n] = consect_bt_time[n - 1] + ens_duration[n]
                else:
                    consect_bt_time[n] = 0
                    
            max_consect_bt_time = np.nanmax(consect_bt_time)
            
            # Evaluate compass calibration based on flow direction
            
            # Find apex of loop adapted from
            # http://www.mathworks.de/matlabcentral/newsreader/view_thread/164048
            loop_out = np.array([bt_x[0], bt_y[0], 0])
            loop_return = np.array([bt_x[-1], bt_y[-1], 0])
            
            distance = np.zeros(n_ensembles)
            for n in range(n_ensembles):
                p = np.array([bt_x[n], bt_y[n], 0])
                distance[n] = np.linalg.norm(np.cross(loop_return - loop_out, p - loop_out))  \
                    / np.linalg.norm(loop_return - loop_out)
                
            dmg_idx = np.where(distance == np.nanmax(distance))[0][0]
            
            # Compute flow direction on outgoing part of loop
            u_out = wt_u[:, :dmg_idx + 1]
            v_out = wt_v[:, :dmg_idx + 1]
            wght = np.abs(q[:, :dmg_idx+1])
            se = np.nansum(u_out * wght) / np.nansum(wght)
            sn = np.nansum(v_out * wght) / np.nansum(wght)  
            direct, _ = cart2pol(se, sn)
            flow_dir1 = rad2azdeg(direct)
            
            # Compute unweighted flow direction in each cell
            direct, _ = cart2pol(u_out, v_out)
            flow_dir_cell = rad2azdeg(direct)
            
            # Compute difference from mean and correct to +/- 180
            v_dir_corr = flow_dir_cell - flow_dir1
            v_dir_idx = v_dir_corr > 180
            v_dir_corr[v_dir_idx] = 360-v_dir_corr[v_dir_idx]
            v_dir_idx = v_dir_corr < -180
            v_dir_corr[v_dir_idx] = 360 + v_dir_corr[v_dir_idx]
            
            # Number of invalid weights
            idx2 = np.where(np.isnan(wght) == False)     
            nwght = len(idx2[0])             
            
            # Compute 95% uncertainty using weighted standard deviation
            uncert1 = 2. * np.sqrt(np.nansum(np.nansum(wght * v_dir_corr**2))
                                   / (((nwght - 1) * np.nansum(np.nansum(wght))) / nwght)) / np.sqrt(nwght)
            
            # Compute flow direction on returning part of loop
            u_ret = wt_u[:, dmg_idx + 1:]
            v_ret = wt_v[:, dmg_idx + 1:]
            wght = np.abs(q[:, dmg_idx+1:])
            se = np.nansum(u_ret * wght) / np.nansum(wght)
            sn = np.nansum(v_ret * wght) / np.nansum(wght)
            direct, _ = cart2pol(se, sn)
            flow_dir2 = rad2azdeg(direct)
            
            # Compute unwieghted flow direction in each cell
            direct, _ = cart2pol(u_ret, v_ret)
            flow_dir_cell = rad2azdeg(direct)
            
            # Compute difference from mean and correct to +/- 180
            v_dir_corr = flow_dir_cell - flow_dir2
            v_dir_idx = v_dir_corr > 180
            v_dir_corr[v_dir_idx] = 360 - v_dir_corr[v_dir_idx]
            v_dir_idx = v_dir_corr < -180
            v_dir_corr[v_dir_idx] = 360 + v_dir_corr[v_dir_idx]
            
            # Number of valid weights
            idx2 = np.where(np.isnan(wght) == False)
            nwght = len(idx2[0])
            
            # Compute 95% uncertainty using weighted standard deviation
            uncert2 = 2.*np.sqrt(np.nansum(np.nansum(wght * v_dir_corr**2))
                                 / (((nwght-1)*np.nansum(np.nansum(wght))) / nwght)) / np.sqrt(nwght)
            
            # Compute and report difference in flow direction
            diff_dir = np.abs(flow_dir1 - flow_dir2)
            if diff_dir > 180:
                diff_dir = diff_dir - 360
            self.compass_diff_deg = diff_dir
            uncert = uncert1 + uncert2
            
            # Compute potential compass error
            idx = np.where(np.isnan(bt_x) == False)
            if len(idx[0]) > 0:
                idx = idx[0][-1]
            width = np.sqrt((bt_x[dmg_idx] - bt_x[idx] / 2) ** 2 + (bt_y[dmg_idx] - bt_y[idx] / 2) ** 2)
            compass_error = (2 * width * sind(diff_dir / 2) * 100) / (self.duration_sec * self.flow_spd_mps)
            
            # Initialize message counter
            self.test_quality = 'Good'

            # Low water velocity
            if self.flow_spd_mps < 0.25:
                self.messages.append('WARNING: The water velocity is less than recommended minimum for '
                                     + 'this test and could cause the loop method to be inaccurate. '
                                     + 'CONSIDER USING A STATIONARY TEST TO CHECK MOVING-BED CONDITIONS')
                self.test_quality = 'Warnings'

            # Percent invalid bottom track
            if self.percent_invalid_bt > 20:
                self.messages.append('ERROR: Percent invalid bottom track exceeds 20 percent. '
                                     + 'THE LOOP IS NOT ACCURATE. TRY A STATIONARY MOVING-BED TEST.')
                self.test_quality = 'Errors'
            elif self.percent_invalid_bt > 5:
                self.messages.append('WARNING: Percent invalid bottom track exceeds 5 percent. '
                                     + 'Loop may not be accurate. PLEASE REVIEW DATA.')
                self.test_quality = 'Warnings'
                
            # More than 9 consecutive seconds of invalid BT
            if max_consect_bt_time > 9:
                self.messages.append('ERROR: Bottom track is invalid for more than 9 consecutive seconds.'
                                     + 'THE LOOP IS NOT ACCURATE. TRY A STATIONARY MOVING-BED TEST.')
                self.test_quality = 'Errors'
                
            if np.abs(compass_error) > 5 and np.abs(diff_dir) > 3 and np.abs(diff_dir) > uncert:
                self.messages.append('ERROR: Difference in flow direction between out and back sections of '
                                     + 'loop could result in a 5 percent or greater error in final discharge. '
                                     + 'REPEAT LOOP AFTER COMPASS CAL. OR USE A STATIONARY MOVING-BED TEST.')
                self.test_quality = 'Errors'
        
        else:
            self.messages.append('ERROR: Loop has no valid bottom track data. '
                                 + 'REPEAT OR USE A STATIONARY MOVING-BED TEST.')
            self.test_quality = 'Errors'
            
        # If loop is valid then evaluate moving-bed condition
        if self.test_quality != 'Errors':
            
            # Check minimum moving-bed velocity criteria
            if self.mb_spd_mps > vel_criteria:
                # Check that closure error is in upstream direction
                if 135 < np.abs(self.flow_dir - self.mb_dir) < 225:
                    # Check if moving-bed is greater than 1% of the mean flow speed
                    if self.percent_mb > 1:
                        self.messages.append('Loop Indicates a Moving Bed -- Use GPS as reference. If GPS is '
                                             + 'unavailable or invalid use the loop method to correct the '
                                             + 'final discharge.')
                        self.moving_bed = 'Yes'
                    else:
                        self.messages.append('Moving Bed Velocity < 1% of Mean Velocity -- No Correction Recommended')
                        self.moving_bed = 'No'
                else:
                    self.messages.append('ERROR: Loop closure error not in upstream direction. '
                                         + 'REPEAT LOOP or USE STATIONARY TEST')
                    self.test_quality = 'Errors'
                    self.moving_bed = 'Unknown'
            else:
                self.messages.append('Moving-bed velocity < Minimum moving-bed velocity criteria '
                                     + '-- No correction recommended')
                self.moving_bed = 'No'
        else:
            self.messages.append('ERROR: Due to ERRORS noted above this loop is NOT VALID. '
                                 + 'Please consider suggestions.')
            self.moving_bed = 'Unknown'