def t_vuf(ltype, ctime, ju, lat=None): """T_VUF Computes nodal modulation corrections. [V,U,F]=T_VUF(TYPE,DATE,JU,LAT) returns the astronomical phase V, the nodal phase modulation U, and the nodal amplitude correction F at a decimal date DATE for the components specified by index JU at a latitude LAT. TYPE is either 'full' for the 18.6 year set of constitunets, or 'nodal' for the 1-year set with satellite modulations. If LAT is not specified, then the Greenwich phase V is computed with U=0 and F=1. Note that V and U are in 'cycles', not degrees or radians (i.e., multiply by 360 to get degrees). If LAT is set to NaN, then the nodal corrections are computed for all satellites that do *not* have a "latitude-dependent" correction factor. This is for compatibility with the ways things are done in the xtide package. (The latitude-dependent corrections were zeroed out there partly because it was convenient, but this was rationalized by saying that since the forcing of tides can occur at latitudes other than where they are observed, the idea that observations have the equilibrium latitude-dependence is possibly bogus anyway). R. Pawlowicz 11/8/99 1/5/00 - Changed to allow for no LAT setting. 11/8/00 - Added the LAT=NaN option. 10/02/03 - Suuport for 18-year (full) constituent set. Version 1.2 Get all the info about constituents. Calculate astronomical arguments at mid-point of data time series. """ astro, ader = t_astron(ctime) if ltype == 'full': const = t_get18consts(ctime) # Phase relative to Greenwich (in units of cycles). v = rem(np.dot(const.doodson, astro) + const.semi, 1) v = v[(ju-1)] u = np.zeros(shape=(v.shape, v.shape), dtype='float64') f = np.ones(shape=(v.shape, v.shape), dtype='float64') else: const, sat, shallow = t_getconsts(ctime) # Phase relative to Greenwich (in units of cycles). # (This only returns values when we have doodson#s, # i.e., not for the shallow water components, # but these will be computed later.) v = np.fmod(np.dot(const['doodson'], astro) + const['semi'], 1) if lat is not None: # If we have a latitude, get nodal corrections. # Apparently the second-order terms in the tidal potential # go to zero at the equator, but the third-order terms # do not. Hence when trying to infer the third-order terms # from the second-order terms, the nodal correction factors # blow up. In order to prevent this, it is assumed that the # equatorial forcing is due to second-order forcing OFF the # equator, from about the 5 degree location. Latitudes are # hence (somewhat arbitrarily) forced to be no closer than # 5 deg to the equator, as per note in Foreman. if ((np.isfinite(lat)) & (abs(lat) < 5)): lat = sign(lat) * 5 slat = np.sin(np.pi * lat / 180) # Satellite amplitude ratio adjustment for latitude. rr = sat['amprat'] # no amplitude correction if np.isfinite(lat): j = np.flatnonzero(sat['ilatfac'] == 1) # latitude correction for diurnal constituents rr[j] = rr[j] * 0.36309 * (1.0 - 5.0 * slat * slat) / slat j = np.flatnonzero(sat['ilatfac'] == 2) # latitude correction for semi-diurnal constituents rr[j] = rr[j] * 2.59808 * slat else: rr[sat['ilatfac'] > 0] = 0 # Calculate nodal amplitude and phase corrections. uu = np.fmod(np.dot(sat['deldood'], astro.T[3:6])+sat['phcorr'], 1) # uu=uudbl-round(uudbl); <_ I think this was wrong. # The original # FORTRAN code is: IUU=UUDBL # UU=UUDBL-IUU # which is truncation. # Sum up all of the satellite factors for all satellites. nsat = np.max(sat['iconst'].shape) nfreq = np.max(const['isat'].shape) fsum = np.array(1+sp.sparse.csr_matrix( (np.squeeze(rr*np.exp(1j*2*np.pi*uu)), (np.arange(0, nsat), np.squeeze(sat['iconst']-1))), shape=(nsat, nfreq)).sum(axis=0)).flatten() f = np.absolute(fsum) u = np.angle(fsum)/(2*np.pi) # Compute amplitude and phase corrections # for shallow water constituents. for k in np.flatnonzero(np.isfinite(const['ishallow'])): ik = ((const['ishallow'][k]-1 + np.array(range(0, const['nshallow'][k]))).astype(int)) iname = shallow['iname'][ik]-1 coef = shallow['coef'][ik] f[k] = np.prod(np.power(f[iname], coef)) u[k] = np.dot(u[iname], coef) v[k] = np.dot(v[iname], coef) f = f[ju] u = u[ju] v = v[ju] else: # Astronomical arguments only, no nodal corrections. # Compute phases for shallow water constituents. for k in np.flatnonzero(np.isfinite(const['ishallow'])): ik = ((const['ishallow'][k]-1 + np.array(range(0, const['nshallow'][k]))).astype(int)) v[k] = np.dot(v[shallow['iname'][ik]-1], shallow['coef'][ik]) v = v[ju] f = np.ones(len(v)) u = np.zeros(len(v)) return v, u, f
def constituents(minres, constit, shallow, infname, infref, centraltime): """[name,freq,kmpr]=constituents(minres,infname) loads tidal constituent table (containing 146 constituents), then picks out only the ' resolvable' frequencies (i.e. those that are MINRES apart), base on the comparisons in the third column of constituents.dat. Only frequencies in the 'standard' set of 69 frequencies are actually used. Also return the indices of constituents to be inferred. If we have the mat-file, read it in, otherwise create it and read it in! R Pawlowicz 9/1/01 Version 1.0 19/1/02 - typo fixed (thanks to Zhigang Xu) Compute frequencies from astronomical considerations. """ if minres > 1 / (np.dot(np.dot(18.6, 365.25), 24)): # Choose only resolveable pairs for short const, sat, cshallow = tgets.t_getconsts(centraltime) # nargout=3 # Time series ju = np.flatnonzero(const['df'] >= minres) else: # Choose them all if > 18.6 years. const, sat, cshallow = t_get18consts(centraltime) # nargout=3 ju = np.array([range(2, (max(const['freq'].shape) +1))]).reshape(1, -1).T # Skip Z0 for ff in range(1, 3): # loop twice to make sure of neightbouring pairs jck = np.flatnonzero(diff(const['freq'][ju]) < minres) if (max(jck.shape) > 0): jrm = jck jrm = jrm + (abs(const['doodsonamp'][ju[(jck + 1 -1)]]) < abs(const['doodsonamp'][ju[(jck -1)]])) disp(' Warning! Following constituent pairs violate Rayleigh criterion') # for ick in range(1, (max(jck.shape) +1)): # disp(' ' + const.name(ju[(jck[(ick -1)] -1)], :) + ' vs ' + const.name(ju[(jck[(ick -1)] + 1 -1)], :) + ' - not using ' + const.name(ju[(jrm[(ick -1)] -1)], :)) ju[(jrm -1)] = np.array([]) if constit.size !=0: # Selected if constituents are specified in input. ju = np.array([]) for k in range(1, (constit.shape[0] +1)): j1 = strmatch(constit[(k -1), :], const['name']) if (0 in j1.shape): disp("Can't recognize name " + constit[(k -1), :] + ' for forced search') else: if j1 == 1: disp('*************************************************************************') disp("Z0 specification ignored - for non-tidal offsets see 'secular' property") disp('*************************************************************************') else: ju = np.array([ju, j1]).reshape(1, -1) dum, II = sort(const['freq'][ju]) # nargout=2 # sort in ascending order of frequency. ju = ju[(II -1)] #cout #disp([' number of standard constituents used: ',int2str(length(ju))]) if shallow.size !=0: # Add explictly selected shallow water constituents. for k in range(1, (shallow.shape[0] +1)): j1 = strmatch(shallow[(k -1), :], const['name']) if (0 in j1.shape): disp("Can't recognize name " + shallow[(k -1), :] + ' for forced search') else: if np.isnan(const['ishallow'][j1]): disp(shallow[(k -1), :] + ' Not a shallow-water constituent') disp(' Forced fit to ' + shallow[(k -1), :]) ju = np.array([ju, j1]).reshape(1, -1) nameu = const['name'][ju] fu = const['freq'][ju] # Check if neighboring chosen constituents violate Rayleigh criteria. jck = np.flatnonzero(np.diff(fu) < minres) #cout #if (length(jck)>0) #disp([' Warning! Following constituent pairs violate Rayleigh criterion']); #for ick=1:length(jck); #disp([' ',nameu(jck(ick),:),' ',nameu(jck(ick)+1,:)]); #end; #end # For inference, add in list of components to be inferred. fi = np.array([]) namei = np.array([]) jinf = np.array([]) jref = np.array([]) if infname.size !=0: fi = np.zeros(shape=(infname.shape[0], 1), dtype='float64') namei = np.zeros(shape=(infname.shape[0], 4), dtype='float64') jinf = np.zeros(shape=(infname.shape[0], 1), dtype='float64') + NaN jref = np.zeros(shape=(infname.shape[0], 1), dtype='float64') + NaN for k in range(1, (infname.shape[0] +1)): j1 = strmatch(infname[(k -1), :], const.name) if (0 in j1.shape): disp("Can't recognize name" + infname[(k -1), :] + ' for inference') else: jinf[(k -1)] = j1 fi[(k -1)] = const['freq'][j1] namei[(k -1), :] = const['name'][j1, :] j1 = strmatch(infref[(k -1), :], nameu) if (0 in j1.shape): disp("Can't recognize name " + infref[(k -1), :] + ' for as a reference for inference') else: jref[(k -1)] = j1 print(' Inference of ' + namei[(k -1), :] + ' using ' + nameu[(j1 -1), :] + '\\n') jinf[(isnan(jref) -1)] = NaN return nameu, fu, ju, namei, fi, jinf, jref
def t_predic(tim, names, freq, tidecon, **kwargs): """T_PREDIC Tidal prediction YOUT=T_PREDIC(TIM,NAMES,FREQ,TIDECON) makes a tidal prediction using the output of T_TIDE at the specified times TIM in decimal days (from DATENUM). Optional arguments can be specified using property/value pairs: YOUT=T_PREDIC(...,TIDECON,property,value,...) Available properties are: In the simplest case, the tidal analysis was done without nodal corrections, and thus neither will the prediction. If nodal corrections were used in the analysis, then it is likely we will want to use them in the prediction too and these are computed using the latitude, if given. 'latitude' decimal degrees (+north) (default: none) If the original analysis was >18.6 years satellites are not included and we force that here: 'anallength' 'nodal' (default) 'full' For >18.6 years. The tidal prediction may be restricted to only some of the available constituents: 'synthesis' 0 - Use all selected constituents. (default) scalar>0 - Use only those constituents with a SNR greater than that given (1 or 2 are good choices). It is possible to call t_predic without using property names, in which case the assumed calling sequence is YOUT=T_PREDIC(TIM,NAMES,FREQ,TIDECON,LATITUDE,SYNTHESIS); T_PREDIC can be called using the tidal structure available as an optional output from T_TIDE YOUT=T_PREDIC(TIM,TIDESTRUC,...) This is in fact the recommended calling procedure (and required when the analysis results are from series>18.6 years in length) R. Pawlowicz 11/8/99 Version 1.0 8/2/03 - Added block processing to generate prediction (to avoid memory overflows for long time series). 29/9/04 - small bug with undefined ltype fixed """ longseries = 0 ltype = 'nodal' lat = np.array([]) synth = 0 k = 1 tim = tim.reshape(-1, 1) # Use kwargs to set values other then the defaults if kwargs is not None: for key, value in kwargs.items(): if (key == 'ltype'): ltype = value if (key == 'synth'): synth = value # Do the synthesis. snr = (tidecon[:, 0] / tidecon[:, 1]) ** 2 # signal to noise ratio if synth > 0: I = snr > synth if not any(I): print('No predictions with this SNR') yout = np.nan + np.zeros(shape=(tim.shape, tim.shape), dtype='float64') return yout tidecon = tidecon[I, :] names = names[I] freq = freq[I] if tidecon.shape[1] == 4: # Real time series ap = np.multiply(tidecon[:, 0]/2.0, np.exp(-1j*tidecon[:, 2]*np.pi/180)) am = np.conj(ap) else: ap = np.multiply((tidecon[:, 0] + tidecon[:, 2]) / 2.0, np.exp(np.dot(np.dot(1j, np.pi) / 180, (tidecon[:, 4] - tidecon[:, 6])))) am = np.multiply((tidecon[:, 0] - tidecon[:, 2]) / 2.0, np.exp(np.dot(np.dot(1j, np.pi) / 180, (tidecon[:, 4] + tidecon[:, 6])))) # Mean at central point (get rid of one point at end to # take mean of odd number of points if necessary). jdmid = np.mean(tim[0:np.dot(2, np.fix((max(tim.shape) - 1) / 2)) + 1]) if longseries: const = t_get18consts ju = np.zeros(shape=(freq.shape, freq.shape), dtype='float64') for k in range(1, (names.shape[0]+1)): inam = strmatch(names[(k-1), :], const.name) if max(inam.shape) == 1: ju[(k-1)] = inam else: if max(inam.shape) > 1: minf, iminf = np.min(abs(freq[(k-1)] - const.freq(inam))) ju[(k-1)] = inam[(iminf-1)] else: const, sat, cshallow = t_getconsts(np.array([])) ju = np.zeros((len(freq),), dtype='int32') # Check to make sure names and frequencies match expected values. for k in range(0, (names.shape[0])): ju[k] = np.argwhere(const['name'] == names[(k)]) # if any(freq~=const.freq(ju)), # error('Frequencies do not match names in input'); # end; # Get the astronical argument with or without nodal corrections. if ((lat.size != 0) & (np.absolute(jdmid) > 1)): v, u, f = t_vuf(ltype, jdmid, ju, lat) else: if np.fabs(jdmid) > 1: # a real date v, u, f = t_vuf(ltype, jdmid, ju) else: v = np.zeros((len(ju),), dtype='float64') u = v f = np.ones((len(ju),), dtype='float64') ap = ap * f * np.exp(+1j*2*np.pi*(u + v)) am = am * f * np.exp(-1j*2*np.pi*(u + v)) tim = tim - jdmid n, m = tim.shape ntim = max(tim.shape) nsub = 10000 yout = np.zeros([n*m, ], dtype='complex128') # longer than one year hourly. for j1 in np.arange(0, ntim, nsub): j1 = j1.astype(int) j2 = np.min([j1 + nsub, ntim]).astype(int) tap = np.repeat(ap, j2-j1).reshape(len(ap), j2-j1) tam = np.repeat(am, j2-j1).reshape(len(am), j2-j1) touter = np.outer(24*1j*2*np.pi*freq, tim[j1:j2]) yout[j1:j2] = np.sum(np.multiply(np.exp(touter), tap), axis=0) +\ np.sum(np.multiply(np.exp(-touter), tam), axis=0) if (tidecon.shape[1] == 4): return np.real(yout) else: return yout