Commit b197c3fdf011f9972e68675ac4ca59e0cd09ad67
0 parents
Exists in
master
first commit
Showing 14 changed files with 2060 additions and 0 deletions Side-by-side Diff
allan.m
| ... | ... | @@ -0,0 +1,576 @@ |
| 1 | +function [retval, s, errorb, tau] = allan(data,tau,name,verbose) | |
| 2 | +% ALLAN Compute the Allan deviation for a set of time-domain frequency data | |
| 3 | +% [RETVAL, S, ERRORB, TAU] = ALLAN(DATA,TAU,NAME,VERBOSE) | |
| 4 | +% | |
| 5 | +% Inputs: | |
| 6 | +% DATA should be a structure and have the following fields: | |
| 7 | +% DATA.freq or DATA.phase | |
| 8 | +% A vector of fractional frequency measurements (df/f) in | |
| 9 | +% DATA.freq *or* phase offset data (seconds) in DATA.phase . | |
| 10 | +% If frequency data is not present, it will be generated by | |
| 11 | +% differentiating the phase data. | |
| 12 | +% If both fields are present, then DATA.freq will be used. | |
| 13 | +% Note: for general-purpose calculations of Allan deviation, | |
| 14 | +% (i.e. a two-sample variance) use DATA.freq . | |
| 15 | +% | |
| 16 | +% DATA.rate or DATA.time | |
| 17 | +% The sampling rate in Hertz (DATA.rate) or a vector of | |
| 18 | +% timestamps for each measurement in seconds (DATA.time). | |
| 19 | +% DATA.rate is used if both fields are present. | |
| 20 | +% If DATA.rate == 0, then the timestamps are used. | |
| 21 | +% | |
| 22 | +% DATA.units (optional) | |
| 23 | +% The units for the data. If present, the string DATA.units | |
| 24 | +% is added to the plot y-axis label. | |
| 25 | +% | |
| 26 | +% TAU is an array of tau values for computing Allan deviation. | |
| 27 | +% TAU values must be divisible by 1/DATA.rate (data points cannot be | |
| 28 | +% grouped in fractional quantities!) and invalid values are ignored. | |
| 29 | +% Leave empty to use default values. | |
| 30 | +% NAME is an optional label that is added to the plot titles. | |
| 31 | +% VERBOSE sets the level of status messages: | |
| 32 | +% 0 = silent & no data plots; | |
| 33 | +% 1 = status messages & minimum plots; | |
| 34 | +% 2 = all messages and plots (default) | |
| 35 | +% | |
| 36 | +% Outputs: | |
| 37 | +% RETVAL is the array of Allan deviation values at each TAU. | |
| 38 | +% S is an optional output of other statistical measures of the data (mean, std, etc). | |
| 39 | +% ERRORB is an optional output containing the error estimates for a 1-sigma | |
| 40 | +% confidence interval. These values are shown on the figure for each point. | |
| 41 | +% TAU is an optional output containing the array of tau values used in the | |
| 42 | +% calculation (which may be a truncated subset of the input or default values). | |
| 43 | +% | |
| 44 | +% Example: | |
| 45 | +% | |
| 46 | +% To compute the Allan deviation for the data in the variable "lt": | |
| 47 | +% >> lt | |
| 48 | +% lt = | |
| 49 | +% freq: [1x86400 double] | |
| 50 | +% rate: 0.5 | |
| 51 | +% | |
| 52 | +% Use: | |
| 53 | +% | |
| 54 | +% >> ad = allan(lt,[2 10 100],'lt data',1); | |
| 55 | +% | |
| 56 | +% The Allan deviation will be computed and plotted at tau = 2,10,100 seconds. | |
| 57 | +% 1-sigma confidence intervals will be indicated by vertical lines at each point. | |
| 58 | +% You can also use the default settings, which are usually a good starting point: | |
| 59 | +% | |
| 60 | +% >> ad = allan(lt); | |
| 61 | +% | |
| 62 | +% | |
| 63 | +% Notes: | |
| 64 | +% This function calculates the standard Allan deviation (ADEV), *not* the | |
| 65 | +% overlapping ADEV. Use "allan_overlap.m" for overlapping ADEV. | |
| 66 | +% The calculation is performed using fractional frequency data. If only | |
| 67 | +% phase data is provided, frequency data is generated by differentiating | |
| 68 | +% the phase data. | |
| 69 | +% No pre-processing of the data is performed, except to remove any | |
| 70 | +% initial offset (i.e., starting gap) in the time record. | |
| 71 | +% For rate-based data, ADEV is computed only for tau values greater than the | |
| 72 | +% minimum time between samples and less than the half the total time. For | |
| 73 | +% time-stamped data, only tau values greater than the maximum gap between | |
| 74 | +% samples and less than half the total time are used. | |
| 75 | +% The calculation for fixed sample rate data is *much* faster than for | |
| 76 | +% time-stamp data. You may wish to run the rate-based calculation first, | |
| 77 | +% then compare with time-stamp-based. Often the differences are insignificant. | |
| 78 | +% To show the "tau bins" (y_k samples) on the data plot, set the variable | |
| 79 | +% TAUBIN to 1 (search for "#TAUBIN"). | |
| 80 | +% You can choose between loglog and semilog plotting of results by | |
| 81 | +% commenting in/out the appropriate line. Search for "#PLOTLOG". | |
| 82 | +% I recommend installing "dsplot.m", which improves the performance of | |
| 83 | +% plotting large data sets. Download from File Exchange, File ID: #15850. | |
| 84 | +% allan.m will use dsplot.m if it is present on your MATLAB path. | |
| 85 | +% This function has been validated using the test data from NBS Monograph | |
| 86 | +% 140, the 1000-point test data set given by Riley [1], and the example data | |
| 87 | +% given in IEEE standard 1139-1999, Annex C. | |
| 88 | +% The author welcomes other validation results, see contact info below. | |
| 89 | +% | |
| 90 | +% For more information, see: | |
| 91 | +% [1] W. J. Riley, "The Calculation of Time Domain Frequency Stability," | |
| 92 | +% Available on the web: | |
| 93 | +% http://www.ieee-uffc.org/frequency_control/teaching.asp?name=paper1ht | |
| 94 | +% | |
| 95 | +% | |
| 96 | +% M.A. Hopcroft | |
| 97 | +% mhopeng at gmail dot com | |
| 98 | +% | |
| 99 | +% I welcome your comments and feedback! | |
| 100 | +% | |
| 101 | +% MH Mar2014 | |
| 102 | +% v2.24 fix bug related to generating freq data from phase with timestamps | |
| 103 | +% (thanks to S. David-Grignot for finding the bug) | |
| 104 | +% MH Oct2010 | |
| 105 | +% v2.22 tau truncation to integer groups; tau sort | |
| 106 | +% plotting bugfix | |
| 107 | +% v2.20 sychronize updates across allan, allan_overlap, allan_modified | |
| 108 | +% v2.16 add TAU as output, fixed unusual error with dsplot v1.1 | |
| 109 | +% v2.14 update plotting behaviour, default tau values | |
| 110 | +% | |
| 111 | + | |
| 112 | +versionstr = 'allan v2.24'; | |
| 113 | + | |
| 114 | +% MH Jun2010 | |
| 115 | +% v2.12 bugfix for rate data row/col orientation | |
| 116 | +% add DATA.units for plotting | |
| 117 | +% use dsplot.m for plotting | |
| 118 | +% | |
| 119 | +% MH MAR2010 | |
| 120 | +% v2.1 minor interface and bugfixes | |
| 121 | +% update data consistency check | |
| 122 | +% | |
| 123 | +% MH FEB2010 | |
| 124 | +% v2.0 Consistent code behaviour for all "allan_x.m" functions: | |
| 125 | +% accept phase data | |
| 126 | +% verbose levels | |
| 127 | +% | |
| 128 | +% | |
| 129 | +% MH JAN2010 | |
| 130 | +% v1.84 code cleanup | |
| 131 | +% v1.82 typos in comments and code cleanup | |
| 132 | +% tau bin plotting changed for performance improvement | |
| 133 | +% v1.8 Performance improvements: | |
| 134 | +% vectorize code for rate data | |
| 135 | +% logical indexing for irregular rate data | |
| 136 | +% MH APR2008 | |
| 137 | +% v1.62 loglog plot option | |
| 138 | +% v1.61 improve error handling, plotting | |
| 139 | +% fix bug in regular data calc for high-rate data | |
| 140 | +% fix bug in timestamp data calc for large starting gap | |
| 141 | +% (thanks to C. B. Ruiz for identifying these bugs) | |
| 142 | +% uses timestamps for DATA.rate=0 | |
| 143 | +% progress indicator for large timestamp data processing | |
| 144 | +% MH JUN2007 | |
| 145 | +% v1.54 Improve data plotting and optional bin plotting | |
| 146 | +% MH FEB2007 | |
| 147 | +% v1.5 use difference from median for plotting | |
| 148 | +% added MAD calculation for outlier detection | |
| 149 | +% MH JAN2007 | |
| 150 | +% v1.48 plotting typos fixes | |
| 151 | +% MH DEC2006 | |
| 152 | +% v1.46 hack to plot error bars | |
| 153 | +% v1.44 further validation (Riley 1000-pt) | |
| 154 | +% plot mean and std | |
| 155 | +% MH NOV2006 | |
| 156 | +% v1.42 typo fix comments | |
| 157 | +% v1.4 fix irregular rate algorithm | |
| 158 | +% irregular algorithm rejects tau less than max gap in time data | |
| 159 | +% validate both algorithms using test data from NBS Monograph 140 | |
| 160 | +% v1.3 fix time calc if data.time not present | |
| 161 | +% add error bars (not possible due to bug in MATLAB R14SP3) | |
| 162 | +% remove offset calculation | |
| 163 | +% v1.24 improve feedback | |
| 164 | +% MH SEP2006 | |
| 165 | +% v1.22 updated comments | |
| 166 | +% v1.2 errors and warnings | |
| 167 | +% v1.1 handle irregular interval data | |
| 168 | +%#ok<*AGROW> | |
| 169 | + | |
| 170 | +% defaults | |
| 171 | +if nargin < 4, verbose=2; end | |
| 172 | +if nargin < 3, name=''; end | |
| 173 | +if nargin < 2 || isempty(tau), tau=2.^(-10:10); end | |
| 174 | + | |
| 175 | +% plot "tau bins"? #TAUBIN | |
| 176 | +TAUBIN = 0; % set 0 or 1 % WARNING: this has a significant impact on performance | |
| 177 | + | |
| 178 | +% Formatting for plots | |
| 179 | +FontName = 'Arial'; | |
| 180 | +FontSize = 14; | |
| 181 | +plotlinewidth=2; | |
| 182 | + | |
| 183 | +if verbose >= 1, fprintf(1,'allan: %s\n\n',versionstr); end | |
| 184 | + | |
| 185 | +%% Data consistency checks | |
| 186 | +if ~(isfield(data,'phase') || isfield(data,'freq')) | |
| 187 | + error('Either ''phase'' or ''freq'' must be present in DATA. See help file for details. [con0]'); | |
| 188 | +end | |
| 189 | +if isfield(data,'time') | |
| 190 | + if isfield(data,'phase') && (length(data.phase) ~= length(data.time)) | |
| 191 | + if isfield(data,'freq') && (length(data.freq) ~= length(data.time)) | |
| 192 | + error('The time and freq vectors are not the same length. See help for details. [con2]'); | |
| 193 | + else | |
| 194 | + error('The time and phase vectors are not the same length. See help for details. [con1]'); | |
| 195 | + end | |
| 196 | + end | |
| 197 | + if isfield(data,'phase') && (any(isnan(data.phase)) || any(isinf(data.phase))) | |
| 198 | + error('The phase vector contains invalid elements (NaN/Inf). [con3]'); | |
| 199 | + end | |
| 200 | + if isfield(data,'freq') && (any(isnan(data.freq)) || any(isinf(data.freq))) | |
| 201 | + error('The freq vector contains invalid elements (NaN/Inf). [con4]'); | |
| 202 | + end | |
| 203 | + if isfield(data,'time') && (any(isnan(data.time)) || any(isinf(data.time))) | |
| 204 | + error('The time vector contains invalid elements (NaN/Inf). [con5]'); | |
| 205 | + end | |
| 206 | +end | |
| 207 | + | |
| 208 | +% sort tau vector | |
| 209 | +tau=sort(tau); | |
| 210 | + | |
| 211 | + | |
| 212 | +%% Basic statistical tests on the data set | |
| 213 | +if ~isfield(data,'freq') | |
| 214 | + if isfield(data,'rate') && data.rate ~= 0 | |
| 215 | + data.freq=diff(data.phase).*data.rate; | |
| 216 | + elseif isfield(data,'time') | |
| 217 | + data.freq=diff(data.phase)./diff(data.time); | |
| 218 | + end | |
| 219 | + if verbose >= 1, fprintf(1,'allan: Fractional frequency data generated from phase data (M=%g).\n',length(data.freq)); end | |
| 220 | + data.time(1)=[]; % make time stamps correspond to freq data | |
| 221 | +end | |
| 222 | +if size(data.freq,2) > size(data.freq,1), data.freq=data.freq'; end % ensure columns | |
| 223 | + | |
| 224 | +s.numpoints=length(data.freq); | |
| 225 | +s.max=max(data.freq); | |
| 226 | +s.min=min(data.freq); | |
| 227 | +s.mean=mean(data.freq); | |
| 228 | +s.median=median(data.freq); | |
| 229 | +if isfield(data,'time') | |
| 230 | + if size(data.time,2) > size(data.time,1), data.time=data.time'; end % ensure columns | |
| 231 | + s.linear=polyfit(data.time(1:length(data.freq)),data.freq,1); | |
| 232 | +elseif isfield(data,'rate') && data.rate ~= 0; | |
| 233 | + s.linear=polyfit((1/data.rate:1/data.rate:length(data.freq)/data.rate)',data.freq,1); | |
| 234 | +else | |
| 235 | + error('Either "time" or "rate" must be present in DATA. Type "help allan" for details. [err1]'); | |
| 236 | +end | |
| 237 | +s.std=std(data.freq); | |
| 238 | + | |
| 239 | +if verbose >= 2 | |
| 240 | + fprintf(1,'allan: input data statistics:\n'); | |
| 241 | + disp(s); | |
| 242 | +end | |
| 243 | + | |
| 244 | + | |
| 245 | +% center at median for plotting | |
| 246 | +medianfreq=data.freq-s.median; | |
| 247 | +sm=[]; sme=[]; | |
| 248 | + | |
| 249 | +% Screen for outliers using 5x Median Absolute Deviation (MAD) criteria | |
| 250 | +s.MAD = median(abs(medianfreq)/0.6745); | |
| 251 | +if verbose >= 2 | |
| 252 | + fprintf(1, 'allan: 5x MAD value for outlier detection: %g\n',5*s.MAD); | |
| 253 | +end | |
| 254 | +if verbose >= 1 && any(abs(medianfreq) > 5*s.MAD) | |
| 255 | + fprintf(1, 'allan: NOTE: There appear to be outliers in the frequency data. See plot.\n'); | |
| 256 | +end | |
| 257 | + | |
| 258 | + | |
| 259 | +%%%% | |
| 260 | +% There are two cases, either using timestamps or fixed sample rate: | |
| 261 | + | |
| 262 | +%% Fixed Sample Rate Data | |
| 263 | +% If there is a regular interval between measurements, calculation is much | |
| 264 | +% easier/faster | |
| 265 | +if isfield(data,'rate') && data.rate > 0 % if data rate was given | |
| 266 | + if verbose >= 1, fprintf(1, 'allan: regular data (%g data points @ %g Hz)\n',length(data.freq),data.rate); end | |
| 267 | + | |
| 268 | + % string for plot title | |
| 269 | + name=[name ' (' num2str(data.rate) ' Hz)']; | |
| 270 | + | |
| 271 | + % what is the time interval between data points? | |
| 272 | + tmstep = 1/data.rate; | |
| 273 | + | |
| 274 | + % Is there time data? Just for curiosity/plotting, does not impact calculation | |
| 275 | + if isfield(data,'time') | |
| 276 | + % adjust time data to remove any starting gap; first time step | |
| 277 | + % should not be zero for comparison with freq data | |
| 278 | + dtime=data.time-data.time(1)+mean(diff(data.time)); | |
| 279 | + if verbose >= 2 | |
| 280 | + fprintf(1,'allan: End of timestamp data: %g sec.\n',dtime(end)); | |
| 281 | + if (data.rate - 1/mean(diff(dtime))) > 1e-6 | |
| 282 | + fprintf(1,'allan: NOTE: data.rate (%f Hz) does not match average timestamped sample rate (%f Hz)\n',data.rate,1/mean(diff(dtime))); | |
| 283 | + end | |
| 284 | + end | |
| 285 | + else | |
| 286 | + % create time axis data using rate (for plotting only) | |
| 287 | + dtime=(tmstep:tmstep:length(data.freq)*tmstep)'; % column oriented | |
| 288 | + end | |
| 289 | + | |
| 290 | + % check the range of tau values and truncate if necessary | |
| 291 | + % find halfway point of time record | |
| 292 | + halftime = round(tmstep*length(data.freq)/2); | |
| 293 | + % truncate tau to appropriate values | |
| 294 | + tau = tau(tau >= tmstep & tau <= halftime); | |
| 295 | + if verbose >= 2, fprintf(1, 'allan: allowable tau range: %g to %g sec. (1/rate to total_time/2)\n',tmstep,halftime); end | |
| 296 | + | |
| 297 | + % save the freq data for the loop | |
| 298 | + dfreq=data.freq; | |
| 299 | + % find the number of data points in each tau group | |
| 300 | + m = data.rate.*tau; | |
| 301 | + % only integer values allowed (no fractional groups of points) | |
| 302 | + %tau = tau(m-round(m)<1e-8); % numerical precision issues (v2.1) | |
| 303 | + tau = tau(m==round(m)); % The round() test is only correct for values < 2^53 | |
| 304 | + %m = m(m-round(m)<1e-8); % change to round(m) for integer test v2.22 | |
| 305 | + m = m(m==round(m)); | |
| 306 | + %m=round(m); | |
| 307 | + | |
| 308 | + if verbose >= 1, fprintf(1,'allan: calculating Allan deviation...\n '); end | |
| 309 | + | |
| 310 | + % calculate the Allan deviation for each value of tau | |
| 311 | + k=0; tic; | |
| 312 | + for i = tau | |
| 313 | + if verbose >= 2, fprintf(1,'%g ',i); end | |
| 314 | + k=k+1; | |
| 315 | + | |
| 316 | + % truncate frequency set to an even multiple of this tau value | |
| 317 | + freq=dfreq(1:end-rem(length(dfreq),m(k))); | |
| 318 | + % group the data into tau-length groups or bins | |
| 319 | + f = reshape(freq,m(k),[]); % Vectorize! | |
| 320 | + % find average in each "tau group", y_k (each colummn of f) | |
| 321 | + fa=mean(f,1); | |
| 322 | + % first finite difference | |
| 323 | + fd=diff(fa); | |
| 324 | + % calculate two-sample variance for this tau | |
| 325 | + M=length(fa); | |
| 326 | + sm(k)=sqrt(0.5/(M-1)*(sum(fd.^2))); | |
| 327 | + | |
| 328 | + % estimate error bars | |
| 329 | + sme(k)=sm(k)/sqrt(M+1); | |
| 330 | + | |
| 331 | + if TAUBIN == 1 | |
| 332 | + % save the binning points for plotting | |
| 333 | + fs(k,1:length(freq)/m(k))=m(k):m(k):length(freq); fval{k}=mean(f,1); | |
| 334 | + end | |
| 335 | + | |
| 336 | + end % repeat for each value of tau | |
| 337 | + | |
| 338 | + if verbose >= 2, fprintf(1,'\n'); end | |
| 339 | + calctime=toc; if verbose >= 2, fprintf(1,'allan: Elapsed time for calculation: %e seconds\n',calctime); end | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | +%% Irregular data (timestamp) | |
| 344 | +elseif isfield(data,'time') | |
| 345 | + % the interval between measurements is irregular | |
| 346 | + % so we must group the data by time | |
| 347 | + if verbose >= 1, fprintf(1, 'allan: irregular rate data (no fixed sample rate)\n'); end | |
| 348 | + | |
| 349 | + % string for plot title | |
| 350 | + name=[name ' (timestamp)']; | |
| 351 | + | |
| 352 | + % adjust time to remove any initial offset or zero | |
| 353 | + dtime=data.time-data.time(1)+mean(diff(data.time)); | |
| 354 | + %dtime=data.time; | |
| 355 | + % where is the maximum gap in time record? | |
| 356 | + gap_pos=find(diff(dtime)==max(diff(dtime))); | |
| 357 | + % what is average data spacing? | |
| 358 | + avg_gap = mean(diff(dtime)); | |
| 359 | + | |
| 360 | + if verbose >= 2 | |
| 361 | + fprintf(1, 'allan: WARNING: irregular timestamp data (no fixed sample rate).\n'); | |
| 362 | + fprintf(1, ' Calculation time may be long and the results subject to interpretation.\n'); | |
| 363 | + fprintf(1, ' You are advised to estimate using an average sample rate (%g Hz) instead of timestamps.\n',1/avg_gap); | |
| 364 | + fprintf(1, ' Continue at your own risk! (press any key to continue)\n'); | |
| 365 | + pause; | |
| 366 | + end | |
| 367 | + | |
| 368 | + if verbose >= 1 | |
| 369 | + fprintf(1, 'allan: End of timestamp data: %g sec\n',dtime(end)); | |
| 370 | + fprintf(1, ' Average rate: %g Hz (%g sec/measurement)\n',1/avg_gap,avg_gap); | |
| 371 | + if max(diff(dtime)) ~= 1/mean(diff(dtime)) | |
| 372 | + fprintf(1, ' Max. gap: %g sec at position %d\n',max(diff(dtime)),gap_pos(1)); | |
| 373 | + end | |
| 374 | + if max(diff(dtime)) > 5*avg_gap | |
| 375 | + fprintf(1, ' WARNING: Max. gap in time record is suspiciously large (>5x the average interval).\n'); | |
| 376 | + end | |
| 377 | + end | |
| 378 | + | |
| 379 | + | |
| 380 | + % find halfway point | |
| 381 | + halftime = fix(dtime(end)/2); | |
| 382 | + % truncate tau to appropriate values | |
| 383 | + tau = tau(tau >= max(diff(dtime)) & tau <= halftime); | |
| 384 | + if isempty(tau) | |
| 385 | + error('allan: ERROR: no appropriate tau values (> %g s, < %g s)\n',max(diff(dtime)),halftime); | |
| 386 | + end | |
| 387 | + | |
| 388 | + % save the freq data for the loop | |
| 389 | + dfreq=data.freq; | |
| 390 | + dtime=dtime(1:length(dfreq)); | |
| 391 | + | |
| 392 | + if verbose >= 1, fprintf(1,'allan: calculating Allan deviation...\n'); end | |
| 393 | + | |
| 394 | + k=0; tic; | |
| 395 | + for i = tau | |
| 396 | + if verbose >= 2, fprintf(1,'%d ',i); end | |
| 397 | + | |
| 398 | + k=k+1; fa=[]; %f=[]; | |
| 399 | + km=0; | |
| 400 | + | |
| 401 | + % truncate data set to an even multiple of this tau value | |
| 402 | + freq=dfreq(dtime <= dtime(end)-rem(dtime(end),i)); | |
| 403 | + time=dtime(dtime <= dtime(end)-rem(dtime(end),i)); | |
| 404 | + %freq=dfreq; | |
| 405 | + %time=dtime; | |
| 406 | + | |
| 407 | + % break up the data into groups of tau length in sec | |
| 408 | + while i*km < time(end) | |
| 409 | + km=km+1; | |
| 410 | + | |
| 411 | + % progress bar | |
| 412 | + if verbose >= 2 | |
| 413 | + if rem(km,100)==0, fprintf(1,'.'); end | |
| 414 | + if rem(km,1000)==0, fprintf(1,'%g/%g\n',km,round(time(end)/i)); end | |
| 415 | + end | |
| 416 | + | |
| 417 | + f = freq(i*(km-1) < time & time <= i*km); | |
| 418 | + f = f(~isnan(f)); % make sure values are valid | |
| 419 | + | |
| 420 | + if ~isempty(f) | |
| 421 | + fa(km)=mean(f); | |
| 422 | + else | |
| 423 | + fa(km)=0; | |
| 424 | + end | |
| 425 | + | |
| 426 | + if TAUBIN == 1 % WARNING: this has a significant impact on performance | |
| 427 | + % save the binning points for plotting | |
| 428 | + %if find(time <= i*km) > 0 | |
| 429 | + fs(k,km)=max(time(time <= i*km)); | |
| 430 | + %else | |
| 431 | + if isempty(fs(k,km)) | |
| 432 | + fs(k,km)=0; | |
| 433 | + end | |
| 434 | + fval{k}=fa; | |
| 435 | + end % save tau bin plot points | |
| 436 | + | |
| 437 | + end | |
| 438 | + | |
| 439 | + if verbose >= 2, fprintf(1,'\n'); end | |
| 440 | + | |
| 441 | + % first finite difference of the averaged results | |
| 442 | + fd=diff(fa); | |
| 443 | + % calculate Allan deviation for this tau | |
| 444 | + M=length(fa); | |
| 445 | + sm(k)=sqrt(0.5/(M-1)*(sum(fd.^2))); | |
| 446 | + | |
| 447 | + % estimate error bars | |
| 448 | + sme(k)=sm(k)/sqrt(M+1); | |
| 449 | + | |
| 450 | + | |
| 451 | + end | |
| 452 | + | |
| 453 | + if verbose == 2, fprintf(1,'\n'); end | |
| 454 | + calctime=toc; if verbose >= 2, fprintf(1,'allan: Elapsed time for calculation: %e seconds\n',calctime); end | |
| 455 | + | |
| 456 | + | |
| 457 | +else | |
| 458 | + error('allan: WARNING: no DATA.rate or DATA.time! Type "help allan" for more information. [err2]'); | |
| 459 | +end | |
| 460 | + | |
| 461 | + | |
| 462 | +%%%%%%%% | |
| 463 | +%% Plotting | |
| 464 | + | |
| 465 | +if verbose >= 2 % show all data | |
| 466 | + | |
| 467 | + % plot the frequency data, centered on median | |
| 468 | + if size(dtime,2) > size(dtime,1), dtime=dtime'; end % this should not be necessary, but dsplot 1.1 is a little bit brittle | |
| 469 | + try | |
| 470 | + % dsplot makes a new figure | |
| 471 | + hd=dsplot(dtime,medianfreq); | |
| 472 | + catch ME | |
| 473 | + figure; | |
| 474 | + if length(dtime) ~= length(medianfreq) | |
| 475 | + fprintf(1,'allan: Warning: length of time axis (%d) is not equal to data array (%d)\n',length(dtime),length(medianfreq)); | |
| 476 | + end | |
| 477 | + hd=plot(dtime,medianfreq); | |
| 478 | + if verbose >= 1, fprintf(1,'allan: Note: Install dsplot.m for improved plotting of large data sets (File Exchange File ID: #15850).\n'); end | |
| 479 | + if verbose >= 2, fprintf(1,' (Message: %s)\n',ME.message); end | |
| 480 | + end | |
| 481 | + set(hd,'Marker','.','LineStyle','none','Color','b'); % equivalent to '.-' | |
| 482 | + hold on; | |
| 483 | + | |
| 484 | + % show center (0) | |
| 485 | + plot(xlim,[0 0],':k'); | |
| 486 | + % show 5x Median Absolute Deviation (MAD) values | |
| 487 | + hm=plot(xlim,[5*s.MAD 5*s.MAD],'-r'); | |
| 488 | + plot(xlim,[-5*s.MAD -5*s.MAD],'-r'); | |
| 489 | + % show linear fit line | |
| 490 | + hf=plot(xlim,polyval(s.linear,xlim)-s.median,'-g'); | |
| 491 | + title(['Data: ' name],'FontSize',FontSize+2,'FontName',FontName); | |
| 492 | + %set(get(gca,'Title'),'Interpreter','none'); | |
| 493 | + xlabel('Time [sec]','FontSize',FontSize,'FontName',FontName); | |
| 494 | + if isfield(data,'units') | |
| 495 | + ylabel(['data - median(data) [' data.units ']'],'FontSize',FontSize,'FontName',FontName); | |
| 496 | + else | |
| 497 | + ylabel('freq - median(freq)','FontSize',FontSize,'FontName',FontName); | |
| 498 | + end | |
| 499 | + set(gca,'FontSize',FontSize,'FontName',FontName); | |
| 500 | + legend([hd hm hf],{'data (centered on median)','5x MAD outliers',['Linear Fit (' num2str(s.linear(1),'%g') ')']},'FontSize',max(10,FontSize-2)); | |
| 501 | + % tighten up | |
| 502 | + xlim([dtime(1) dtime(end)]); | |
| 503 | + | |
| 504 | + | |
| 505 | + % Optional tau bin (y_k samples) plot | |
| 506 | + if TAUBIN == 1 | |
| 507 | + % plot the tau divisions on the data plot | |
| 508 | + rfs=size(fs,1); | |
| 509 | + colororder=get(gca,'ColorOrder'); | |
| 510 | + axis tight; kc=2; | |
| 511 | + %ap=axis; | |
| 512 | + for j=1:rfs | |
| 513 | + kc=kc+1; if rem(kc,length(colororder))==1, kc=2; end | |
| 514 | + %for b=1:max(find(fs(j,:))); % new form of "find" in r2009a | |
| 515 | + for b=1:find(fs(j,:), 1, 'last' ); | |
| 516 | + % plot the tau division boundaries | |
| 517 | + %plot([fs(j,b) fs(j,b)],[ap(3)*1.1 ap(4)*1.1],'-','Color',colororder(kc,:)); | |
| 518 | + % plot tau group y values | |
| 519 | + if b == 1 | |
| 520 | + plot([dtime(1) fs(j,b)],[fval{j}(b)-s.median fval{j}(b)-s.median],'-','Color',colororder(kc,:),'LineWidth',4); | |
| 521 | + else | |
| 522 | + plot([fs(j,b-1) fs(j,b)],[fval{j}(b)-s.median fval{j}(b)-s.median],'-','Color',colororder(kc,:),'LineWidth',4); | |
| 523 | + end | |
| 524 | + end | |
| 525 | + end | |
| 526 | + axis auto | |
| 527 | + end % End optional bin plot | |
| 528 | + | |
| 529 | +end % end plot raw data | |
| 530 | + | |
| 531 | + | |
| 532 | +if verbose >= 1 % show ADEV results | |
| 533 | + | |
| 534 | + % plot Allan deviation results | |
| 535 | + if ~isempty(sm) | |
| 536 | + figure | |
| 537 | + | |
| 538 | + % Choose loglog or semilogx plot here #PLOTLOG | |
| 539 | + %semilogx(tau,sm,'.-b','LineWidth',plotlinewidth,'MarkerSize',24); | |
| 540 | + loglog(tau,sm,'.-b','LineWidth',plotlinewidth,'MarkerSize',24); | |
| 541 | + | |
| 542 | + % in R14SP3, there is a bug that screws up the error bars on a semilog plot. | |
| 543 | + % When this is fixed in a future release, uncomment below to use normal errorbars | |
| 544 | + %errorbar(tau,sm,sme,'.-b'); set(gca,'XScale','log'); | |
| 545 | + % this is a hack to approximate the error bars | |
| 546 | + hold on; plot([tau; tau],[sm+sme; sm-sme],'-k','LineWidth',max(plotlinewidth-1,2)); | |
| 547 | + | |
| 548 | + grid on; | |
| 549 | + title(['Allan Deviation: ' name],'FontSize',FontSize+2,'FontName',FontName); | |
| 550 | + %set(get(gca,'Title'),'Interpreter','none'); | |
| 551 | + xlabel('\tau [sec]','FontSize',FontSize,'FontName',FontName); | |
| 552 | + if isfield(data,'units') | |
| 553 | + ylabel(['\sigma_y(\tau) [' data.units ']'],'FontSize',FontSize,'FontName',FontName); | |
| 554 | + else | |
| 555 | + ylabel('\sigma_y(\tau)','FontSize',FontSize,'FontName',FontName); | |
| 556 | + end | |
| 557 | + set(gca,'FontSize',FontSize,'FontName',FontName); | |
| 558 | + % expand the x axis a little bit so that the errors bars look nice | |
| 559 | + adax = axis; | |
| 560 | + axis([adax(1)*0.9 adax(2)*1.1 adax(3) adax(4)]); | |
| 561 | + | |
| 562 | + % display the minimum value | |
| 563 | + fprintf(1,'allan: Minimum ADEV value: %g at tau = %g seconds\n',min(sm),tau(sm==min(sm))); | |
| 564 | + | |
| 565 | + elseif verbose >= 1 | |
| 566 | + fprintf(1,'allan: WARNING: no values calculated.\n'); | |
| 567 | + fprintf(1,' Check that TAU > 1/DATA.rate and TAU values are divisible by 1/DATA.rate\n'); | |
| 568 | + fprintf(1,'Type "help allan" for more information.\n\n'); | |
| 569 | + end | |
| 570 | + | |
| 571 | +end % end plot ADEV data | |
| 572 | + | |
| 573 | +retval = sm; | |
| 574 | +errorb = sme; | |
| 575 | + | |
| 576 | +return |
allan_modified.m
| ... | ... | @@ -0,0 +1,561 @@ |
| 1 | +function [retval, s, errorb, tau] = allan_modified(data,tau,name,verbose) | |
| 2 | +% ALLAN_MODIFIED Compute the modified Allan deviation for a set of | |
| 3 | +% time-domain frequency data | |
| 4 | +% [RETVAL, S, ERRORB, TAU] = ALLAN_MODIFIED(DATA,TAU,NAME,VERBOSE) | |
| 5 | +% | |
| 6 | +% Inputs: | |
| 7 | +% DATA should be a struct and have the following fields: | |
| 8 | +% DATA.freq or DATA.phase | |
| 9 | +% A vector of fractional frequency measurements (df/f) in | |
| 10 | +% DATA.freq *or* phase offset data (seconds) in DATA.phase | |
| 11 | +% If phase data is not present, it will be generated by | |
| 12 | +% integrating the fractional frequency data. | |
| 13 | +% If both fields are present, then DATA.phase will be used. | |
| 14 | +% | |
| 15 | +% DATA.rate or DATA.time | |
| 16 | +% The sampling rate in Hertz (DATA.rate) or a vector of | |
| 17 | +% timestamps for each measurement in seconds (DATA.time). | |
| 18 | +% DATA.rate is used if both fields are present. | |
| 19 | +% If DATA.rate == 0, then the timestamps are used. | |
| 20 | +% | |
| 21 | +% TAU is an array of tau values for computing Allan deviation. | |
| 22 | +% TAU values must be divisible by 1/DATA.rate (data points cannot be | |
| 23 | +% grouped in fractional quantities!). Invalid values are ignored. | |
| 24 | +% NAME is an optional label that is added to the plot titles. | |
| 25 | +% VERBOSE sets the level of status messages: | |
| 26 | +% 0 = silent & no data plots; 1 = status messages; 2 = all messages | |
| 27 | +% | |
| 28 | +% Outputs: | |
| 29 | +% RETVAL is the array of modified Allan deviation values at each TAU. | |
| 30 | +% S is an optional output of other statistical measures of the data (mean, std, etc). | |
| 31 | +% ERRORB is an optional output containing the error estimates for a | |
| 32 | +% 1-sigma confidence interval. These values are shown on the figure for each point. | |
| 33 | +% TAU is an optional output containing the array of tau values used in the | |
| 34 | +% calculation (which may be a truncated subset of the input or default values). | |
| 35 | +% | |
| 36 | +% Example: | |
| 37 | +% | |
| 38 | +% To compute the modified Allan deviation for the data in the variable "lt": | |
| 39 | +% >> lt | |
| 40 | +% lt = | |
| 41 | +% freq: [1x86400 double] | |
| 42 | +% rate: 0.5 | |
| 43 | +% | |
| 44 | +% Use: | |
| 45 | +% | |
| 46 | +% >> adm = allan_modified(lt,[2 10 100],'lt data',1); | |
| 47 | +% | |
| 48 | +% The modified Allan deviation will be computed and plotted at tau = 2,10,100 seconds. | |
| 49 | +% 1-sigma confidence intervals will be indicated by vertical lines at each point. | |
| 50 | +% You can also use the default settings, which are usually a good starting point: | |
| 51 | +% | |
| 52 | +% >> adm = allan_modified(lt); | |
| 53 | +% | |
| 54 | +% | |
| 55 | +% Notes: | |
| 56 | +% This function calculates the modifed Allan deviation (MDEV). | |
| 57 | +% The calculation is performed using phase data. If only frequency data is | |
| 58 | +% provided, phase data is generated by integrating the frequency data. | |
| 59 | +% No pre-processing of the data is performed. | |
| 60 | +% For rate-based data, MDEV is computed only for tau values greater than the | |
| 61 | +% minimum time between samples and less than the half the total time. For | |
| 62 | +% time-stamped data, only tau values greater than the maximum gap between | |
| 63 | +% samples and less than half the total time are used. | |
| 64 | +% The calculation for fixed sample rate data is *much* faster than for | |
| 65 | +% time-stamp data. You may wish to run the rate-based calculation first, | |
| 66 | +% then compare with time-stamp-based. Often the differences are insignificant. | |
| 67 | +% When phase data is generated by integrating time-stamped frequency data, | |
| 68 | +% the final data point is dropped, because there is no timestamp for it. | |
| 69 | +% This will create a [usually small] difference between the results from | |
| 70 | +% analyzing the same data set with timestamp data and analyzing with a | |
| 71 | +% fixed sample rate. See note in the code near line 350. | |
| 72 | +% You can choose between loglog and semilog plotting of results by | |
| 73 | +% commenting in/out the appropriate line. Search for "#PLOTLOG". | |
| 74 | +% This function has been validated using the test data from NBS Monograph | |
| 75 | +% 140, the 1000-point test data set given by Riley [1], and the example data | |
| 76 | +% given in IEEE standard 1139-1999, Annex C. | |
| 77 | +% The author welcomes other validation results, see contact info below. | |
| 78 | +% | |
| 79 | +% For more information, see: | |
| 80 | +% [1] W. J. Riley, "The Calculation of Time Domain Frequency Stability," | |
| 81 | +% Available on the web: | |
| 82 | +% http://www.ieee-uffc.org/frequency_control/teaching.asp?name=paper1ht | |
| 83 | +% | |
| 84 | +% | |
| 85 | +% M.A. Hopcroft | |
| 86 | +% mhopeng at gmail dot com | |
| 87 | +% | |
| 88 | +% I welcome your comments and feedback! | |
| 89 | +% | |
| 90 | +% MH Mar2014 | |
| 91 | +% v1.24 fix bug related to generating freq data from phase with timestamps | |
| 92 | +% (thanks to S. David-Grignot for finding the bug) | |
| 93 | +% MH Oct2010 | |
| 94 | +% v1.22 tau truncation to integer groups; tau sort | |
| 95 | +% plotting bugfix | |
| 96 | +% v1.20 update to match allan.m (dsplot.m, columns) | |
| 97 | +% discard tau values with timestamp irregularities | |
| 98 | +% | |
| 99 | + | |
| 100 | +versionstr = 'allan_modified v1.24'; | |
| 101 | + | |
| 102 | +% MH MAR2010 | |
| 103 | +% v1.1 bugfixes for irregular sample rates | |
| 104 | +% update consistency check | |
| 105 | +% | |
| 106 | +% MH FEB2010 | |
| 107 | +% v1.0 based on allan_overlap v2.0 | |
| 108 | +% | |
| 109 | + | |
| 110 | +%#ok<*AGROW> | |
| 111 | + | |
| 112 | + | |
| 113 | +% defaults | |
| 114 | +if nargin < 4, verbose = 2; end | |
| 115 | +if nargin < 3, name=''; end | |
| 116 | +if nargin < 2 || isempty(tau), tau=2.^(-10:10); end | |
| 117 | +if isfield(data,'rate') && isempty(data.rate), data.rate=0; end % v1.1 | |
| 118 | + | |
| 119 | +% Formatting for plots | |
| 120 | +FontName = 'Arial'; | |
| 121 | +FontSize = 14; | |
| 122 | +plotlinewidth=2; | |
| 123 | + | |
| 124 | +if verbose >= 1, fprintf(1,'allan_modified: %s\n\n',versionstr); end | |
| 125 | + | |
| 126 | +%% Data consistency checks | |
| 127 | +if ~(isfield(data,'phase') || isfield(data,'freq')) | |
| 128 | + error('Either ''phase'' or ''freq'' must be present in DATA. See help file for details. [con0]'); | |
| 129 | +end | |
| 130 | +if isfield(data,'time') | |
| 131 | + if isfield(data,'phase') && (length(data.phase) ~= length(data.time)) | |
| 132 | + if isfield(data,'freq') && (length(data.freq) ~= length(data.time)) | |
| 133 | + error('The time and freq vectors are not the same length. See help for details. [con2]'); | |
| 134 | + else | |
| 135 | + error('The time and phase vectors are not the same length. See help for details. [con1]'); | |
| 136 | + end | |
| 137 | + end | |
| 138 | + if isfield(data,'phase') && (any(isnan(data.phase)) || any(isinf(data.phase))) | |
| 139 | + error('The phase vector contains invalid elements (NaN/Inf). [con3]'); | |
| 140 | + end | |
| 141 | + if isfield(data,'freq') && (any(isnan(data.freq)) || any(isinf(data.freq))) | |
| 142 | + error('The freq vector contains invalid elements (NaN/Inf). [con4]'); | |
| 143 | + end | |
| 144 | + if isfield(data,'time') && (any(isnan(data.time)) || any(isinf(data.time))) | |
| 145 | + error('The time vector contains invalid elements (NaN/Inf). [con5]'); | |
| 146 | + end | |
| 147 | +end | |
| 148 | + | |
| 149 | +% sort tau vector | |
| 150 | +tau=sort(tau); | |
| 151 | + | |
| 152 | + | |
| 153 | +%% Basic statistical tests on the data set | |
| 154 | +if ~isfield(data,'freq') | |
| 155 | + if isfield(data,'rate') && data.rate ~= 0 | |
| 156 | + data.freq=diff(data.phase).*data.rate; | |
| 157 | + elseif isfield(data,'time') | |
| 158 | + data.freq=diff(data.phase)./diff(data.time); | |
| 159 | + end | |
| 160 | + if verbose >= 1, fprintf(1,'allan_modified: Fractional frequency data generated from phase data (M=%g).\n',length(data.freq)); end | |
| 161 | +end | |
| 162 | +if size(data.freq,2) > size(data.freq,1), data.freq=data.freq'; end % ensure columns | |
| 163 | + | |
| 164 | +s.numpoints=length(data.freq); | |
| 165 | +s.max=max(data.freq); | |
| 166 | +s.min=min(data.freq); | |
| 167 | +s.mean=mean(data.freq); | |
| 168 | +s.median=median(data.freq); | |
| 169 | +if isfield(data,'time') | |
| 170 | + if size(data.time,2) > size(data.time,1), data.time=data.time'; end % ensure columns | |
| 171 | + s.linear=polyfit(data.time(1:length(data.freq)),data.freq,1); | |
| 172 | +elseif isfield(data,'rate') && data.rate ~= 0; | |
| 173 | + s.linear=polyfit((1/data.rate:1/data.rate:length(data.freq)/data.rate)',data.freq,1); | |
| 174 | +else | |
| 175 | + error('Either "time" or "rate" must be present in DATA. Type "help allan_modified" for details. [err1]'); | |
| 176 | +end | |
| 177 | +s.std=std(data.freq); | |
| 178 | + | |
| 179 | +if verbose >= 2 | |
| 180 | + fprintf(1,'allan_modified: fractional frequency data statistics:\n'); | |
| 181 | + disp(s); | |
| 182 | +end | |
| 183 | + | |
| 184 | +% scale to median for plotting | |
| 185 | +medianfreq=data.freq-s.median; | |
| 186 | +sm=[]; sme=[]; | |
| 187 | + | |
| 188 | +% Screen for outliers using 5x Median Absolute Deviation (MAD) criteria | |
| 189 | +MAD = median(abs(medianfreq)/0.6745); | |
| 190 | +if verbose >= 1 && any(abs(medianfreq) > 5*MAD) | |
| 191 | + fprintf(1, 'allan_modified: NOTE: There appear to be outliers in the frequency data. See plot.\n'); | |
| 192 | +end | |
| 193 | + | |
| 194 | +%%%% | |
| 195 | +% There are two cases, either using timestamps or rate: | |
| 196 | + | |
| 197 | +%% Fixed Sample Rate Data | |
| 198 | +% If there is a regular interval between measurements, calculation is much | |
| 199 | +% easier/faster | |
| 200 | +if isfield(data,'rate') && data.rate > 0 % if data rate was given | |
| 201 | + if verbose >= 1 | |
| 202 | + fprintf(1, 'allan_modified: regular data '); | |
| 203 | + if isfield(data,'freq') | |
| 204 | + fprintf(1, '(%g freq data points @ %g Hz)\n',length(data.freq),data.rate); | |
| 205 | + elseif isfield(data,'phase') | |
| 206 | + fprintf(1, '(%g phase data points @ %g Hz)\n',length(data.phase),data.rate); | |
| 207 | + else | |
| 208 | + error('\n phase or freq data missing [err10]'); | |
| 209 | + end | |
| 210 | + end | |
| 211 | + | |
| 212 | + % string for plot title | |
| 213 | + name=[name ' (' num2str(data.rate) ' Hz)']; | |
| 214 | + | |
| 215 | + % what is the time interval between data points? | |
| 216 | + tmstep = 1/data.rate; | |
| 217 | + | |
| 218 | + % Is there time data? Just for curiosity/plotting, does not impact calculation | |
| 219 | + if isfield(data,'time') | |
| 220 | + % adjust time data to remove any starting gap; first time step | |
| 221 | + % should not be zero for comparison with freq data | |
| 222 | + dtime=data.time-data.time(1)+mean(diff(data.time)); | |
| 223 | + dtime=dtime(1:length(medianfreq)); % equalize the data vector lengths for plotting (v1.1) | |
| 224 | + if verbose >= 2 | |
| 225 | + fprintf(1,'allan_modified: End of timestamp data: %g sec.\n',dtime(end)); | |
| 226 | + if (data.rate - 1/mean(diff(dtime))) > 1e-6 | |
| 227 | + fprintf(1,'allan_modified: NOTE: data.rate (%f Hz) does not match average timestamped sample rate (%f Hz)\n',data.rate,1/mean(diff(dtime))); | |
| 228 | + end | |
| 229 | + end | |
| 230 | + else | |
| 231 | + % create time axis data using rate (for plotting only) | |
| 232 | + dtime=(tmstep:tmstep:length(data.freq)*tmstep); | |
| 233 | + end | |
| 234 | + | |
| 235 | + | |
| 236 | + % is phase data present? If not, generate it | |
| 237 | + if ~isfield(data,'phase') | |
| 238 | + nfreq=data.freq-s.mean; | |
| 239 | + dphase=zeros(1,length(nfreq)+1); | |
| 240 | + dphase(2:end) = cumsum(nfreq).*tmstep; | |
| 241 | + if verbose >= 1, fprintf(1,'allan_modified: phase data generated from fractional frequency data (N=%g).\n',length(dphase)); end | |
| 242 | + else | |
| 243 | + dphase=data.phase; | |
| 244 | + end | |
| 245 | + | |
| 246 | + | |
| 247 | + % check the range of tau values and truncate if necessary | |
| 248 | + % find halfway point of time record | |
| 249 | + halftime = round(tmstep*length(data.freq)/2); | |
| 250 | + % truncate tau to appropriate values | |
| 251 | + tau = tau(tau >= tmstep & tau <= halftime); | |
| 252 | + if verbose >= 2, fprintf(1, 'allan_modified: allowable tau range: %g to %g sec. (1/rate to total_time/2)\n',tmstep,halftime); end | |
| 253 | + | |
| 254 | + % find the number of data points in each tau group | |
| 255 | + % number of samples | |
| 256 | + N=length(dphase); | |
| 257 | + m = data.rate.*tau; | |
| 258 | + % only integer values allowed (no fractional groups of points) | |
| 259 | + %tau = tau(m-round(m)<1e-8); % numerical precision issues (v1.1) | |
| 260 | + tau = tau(m==round(m)); % The round() test is only correct for values < 2^53 | |
| 261 | + %m = m(m-round(m)<1e-8); % change to round(m) for integer test v1.22 | |
| 262 | + m = m(m==round(m)); | |
| 263 | + %m=round(m); | |
| 264 | + | |
| 265 | + if verbose >= 1, fprintf(1,'allan_modified: calculating modified Allan deviation...\n '); end | |
| 266 | + | |
| 267 | + | |
| 268 | + % calculate the modified Allan deviation for each value of tau | |
| 269 | + k=0; tic; | |
| 270 | + for i = tau | |
| 271 | + k=k+1; | |
| 272 | + pa=[]; | |
| 273 | + if verbose >= 2, fprintf(1,'%d ',i); end | |
| 274 | + | |
| 275 | + mphase = dphase; | |
| 276 | + | |
| 277 | + % calculate overlapping "phase averages" (x_k) | |
| 278 | + for p=1:m(k) | |
| 279 | + | |
| 280 | + % truncate frequency set length to an even multiple of this tau value | |
| 281 | + mphase=mphase(1:end-rem(length(mphase),m(k))); | |
| 282 | + % group phase values | |
| 283 | + mp=reshape(mphase,m(k),[]); | |
| 284 | + % find average in each "tau group" (each column of mp) | |
| 285 | + pa(p,:)=mean(mp,1); | |
| 286 | + % shift data vector by -1 and repeat | |
| 287 | + mphase=circshift(dphase,(size(dphase)>1)*-p); | |
| 288 | + | |
| 289 | + end | |
| 290 | + | |
| 291 | + % create "modified" y_k freq values | |
| 292 | + mfreq=diff(pa,1,2)./i; | |
| 293 | + mfreq=reshape(mfreq,1,[]); | |
| 294 | + | |
| 295 | + % calculate modified frequency differences | |
| 296 | + mfreqd=reshape(mfreq,m(k),[]); % Vectorize! | |
| 297 | + mfreqd=diff(mfreqd,1,2); | |
| 298 | + mfreqd=reshape(mfreqd,1,[]); | |
| 299 | + | |
| 300 | + | |
| 301 | + % calculate two-sample variance for this tau | |
| 302 | + sm(k)=sqrt((1/(2*(N-3*m(k)+1)))*(sum(mfreqd(1:N-3*m(k)+1).^2))); | |
| 303 | + | |
| 304 | + % estimate error bars | |
| 305 | + sme(k)=sm(k)/sqrt(N-3*m(k)+1); | |
| 306 | + | |
| 307 | + | |
| 308 | + end % repeat for each value of tau | |
| 309 | + | |
| 310 | + if verbose >= 2, fprintf(1,'\n'); end | |
| 311 | + calctime=toc; if verbose >= 2, fprintf(1,'allan_modified: Elapsed time for calculation: %g seconds\n',calctime); end | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | +%% Irregular data (timestamp) | |
| 316 | +elseif isfield(data,'time') | |
| 317 | + % the interval between measurements is irregular | |
| 318 | + % so we must group the data by time | |
| 319 | + if verbose >= 1, fprintf(1, 'allan_modified: irregular rate data (no fixed sample rate)\n'); end | |
| 320 | + | |
| 321 | + % string for plot title | |
| 322 | + name=[name ' (timestamp)']; | |
| 323 | + | |
| 324 | + % adjust time to remove any initial offset | |
| 325 | + dtime=data.time-data.time(1)+mean(diff(data.time)); | |
| 326 | + %dtime=data.time-data.time(1); | |
| 327 | + % where is the maximum gap in time record? | |
| 328 | + gap_pos=find(diff(dtime)==max(diff(dtime))); | |
| 329 | + % what is average data spacing? | |
| 330 | + avg_gap = mean(diff(dtime)); | |
| 331 | + | |
| 332 | + if verbose >= 2 | |
| 333 | + fprintf(1, 'allan_modified: WARNING: irregular timestamp data (no fixed sample rate).\n'); | |
| 334 | + fprintf(1, ' Calculation time may be long and the results subject to interpretation.\n'); | |
| 335 | + fprintf(1, ' You are advised to estimate using an average sample rate (%g Hz) instead of timestamps.\n',1/avg_gap); | |
| 336 | + fprintf(1, ' Continue at your own risk! (press any key to continue)\n'); | |
| 337 | + pause; | |
| 338 | + end | |
| 339 | + | |
| 340 | + if verbose >= 1 | |
| 341 | + fprintf(1, 'allan_modified: End of timestamp data: %g sec\n',dtime(end)); | |
| 342 | + fprintf(1, ' Average sample rate: %g Hz (%g sec/measurement)\n',1/avg_gap,avg_gap); | |
| 343 | + if max(diff(dtime)) ~= 1/mean(diff(dtime)) | |
| 344 | + fprintf(1, ' Max. gap in time record: %g sec at position %d\n',max(diff(dtime)),gap_pos(1)); | |
| 345 | + end | |
| 346 | + if max(diff(dtime)) > 5*avg_gap | |
| 347 | + fprintf(1, ' WARNING: Max. gap in time record is suspiciously large (>5x the average interval).\n'); | |
| 348 | + end | |
| 349 | + end | |
| 350 | + | |
| 351 | + % is phase data present? If not, generate it | |
| 352 | + if ~isfield(data,'phase') | |
| 353 | + nfreq=data.freq-s.mean; | |
| 354 | + % NOTE: uncommenting the following two lines will artificially | |
| 355 | + % allow the code to give equivalent results for validation data | |
| 356 | + % sets using fixed rate data and timestamped data by adding a | |
| 357 | + % "phantom" data point for frequency integration. Use of this | |
| 358 | + % phantom point can skew the results of calculations on real data. | |
| 359 | + %nfreq(end+1)=0; % phantom freq point, with average value | |
| 360 | + %dtime(end+1)=dtime(end)+avg_gap; % phantom average time step | |
| 361 | + dphase=zeros(1,length(nfreq)); | |
| 362 | + dphase(2:end) = cumsum(nfreq(1:end-1)).*diff(dtime); | |
| 363 | + if verbose >= 1, fprintf(1,'allan_modified: Phase data generated from fractional frequency data (N=%g).\n',length(dphase)); end | |
| 364 | + else | |
| 365 | + dphase=data.phase; | |
| 366 | + end | |
| 367 | + | |
| 368 | + % find halfway point | |
| 369 | + halftime = fix(dtime(end)/2); | |
| 370 | + % truncate tau to appropriate values | |
| 371 | + tau = tau(tau >= max(diff(dtime)) & tau <= halftime); | |
| 372 | + if isempty(tau) | |
| 373 | + error('allan_modified: ERROR: no appropriate tau values (> %g s, < %g s)\n',max(diff(dtime)),halftime); | |
| 374 | + end | |
| 375 | + | |
| 376 | +% % save the freq data for the loop | |
| 377 | +% dfreq=data.freq; | |
| 378 | + | |
| 379 | + % number of samples | |
| 380 | + N=length(dphase); | |
| 381 | + m=round(tau./mean(diff(dtime))); | |
| 382 | + | |
| 383 | + if verbose >= 1, fprintf(1,'allan_modified: calculating modified Allan deviation...\n'); end | |
| 384 | + | |
| 385 | + k=0; tic; | |
| 386 | + for i = tau | |
| 387 | + | |
| 388 | + k=k+1; pa=[]; | |
| 389 | + | |
| 390 | + mphase = dphase; time = dtime; | |
| 391 | + | |
| 392 | + if verbose >= 2, fprintf(1,'%d ',i); end | |
| 393 | + | |
| 394 | + % calculate overlapping "phase averages" (x_k) | |
| 395 | + %for j = 1:i | |
| 396 | + for j = 1:m(k) % (v1.1) | |
| 397 | + km=0; | |
| 398 | + %fprintf(1,'j: %d ',j); | |
| 399 | + | |
| 400 | + % (v1.1) truncating not correct for overlapping samples | |
| 401 | + % truncate data set to an even multiple of this tau value | |
| 402 | + %mphase = mphase(time <= time(end)-rem(time(end),i)); | |
| 403 | + %time = time(time <= time(end)-rem(time(end),i)); | |
| 404 | + | |
| 405 | + | |
| 406 | + % break up the data into overlapping groups of tau length | |
| 407 | + while i*km < time(end) | |
| 408 | + km=km+1; | |
| 409 | + | |
| 410 | + % progress bar | |
| 411 | + if verbose >= 2 | |
| 412 | + if rem(km,100)==0, fprintf(1,'.'); end | |
| 413 | + if rem(km,1000)==0, fprintf(1,'%g/%g\n',km,round(time(end)/i)); end | |
| 414 | + end | |
| 415 | + | |
| 416 | + mp = mphase(i*(km-1) < (time) & (time) <= i*km); | |
| 417 | + | |
| 418 | + if ~isempty(mp) | |
| 419 | + pa(j,km)=mean(mp); | |
| 420 | + else | |
| 421 | + pa(j,km)=0; | |
| 422 | + end | |
| 423 | + | |
| 424 | + end | |
| 425 | + | |
| 426 | + % shift data vector by -1 and repeat | |
| 427 | + mphase=circshift(dphase,(size(mphase)>1)*-j); | |
| 428 | + mphase(end-j+1:end)=[]; | |
| 429 | + time=circshift(dtime,(size(time)>1)*-j); | |
| 430 | + time(end-j+1:end)=[]; | |
| 431 | + time=time-time(1)+avg_gap; % remove time offset | |
| 432 | + | |
| 433 | + end | |
| 434 | + | |
| 435 | + % create "modified" y_k freq values | |
| 436 | + mfreq=diff(pa,1,2)./i; | |
| 437 | + mfreq=reshape(mfreq,1,[]); | |
| 438 | + | |
| 439 | + % calculate modified frequency differences | |
| 440 | + mfreqd=reshape(mfreq,m(k),[]); % Vectorize! | |
| 441 | + mfreqd=diff(mfreqd,1,2); | |
| 442 | + mfreqd=reshape(mfreqd,1,[]); | |
| 443 | + | |
| 444 | + % calculate two-sample variance for this tau | |
| 445 | + % only the first N-3*m(k)+1 samples are valid | |
| 446 | + if length(mfreqd) >= N-3*m(k)+1 | |
| 447 | + sm(k)=sqrt((1/(2*(N-3*m(k)+1)))*(sum(mfreqd(1:N-3*m(k)+1).^2))); | |
| 448 | + | |
| 449 | + % estimate error bars | |
| 450 | + sme(k)=sm(k)/sqrt(N); | |
| 451 | + | |
| 452 | + if verbose >= 2, fprintf(1,'\n'); end | |
| 453 | + else | |
| 454 | + if verbose >=2, fprintf(1,' tau=%g dropped due to timestamp irregularities\n',tau(k)); end | |
| 455 | + sm(k)=0; sme(k)=0; | |
| 456 | + end | |
| 457 | + | |
| 458 | + | |
| 459 | + end | |
| 460 | + | |
| 461 | + if verbose >= 2, fprintf(1,'\n'); end | |
| 462 | + calctime=toc; if verbose >= 2, fprintf(1,'allan_modified: Elapsed time for calculation: %g seconds\n',calctime); end | |
| 463 | + | |
| 464 | + % remove any points that were dropped | |
| 465 | + tau(sm==0)=[]; | |
| 466 | + sm(sm==0)=[]; | |
| 467 | + sme(sme==0)=[]; | |
| 468 | + | |
| 469 | + % modify time vector for plotting | |
| 470 | + dtime=dtime(1:length(medianfreq)); | |
| 471 | + | |
| 472 | +else | |
| 473 | + error('allan_modified: WARNING: no DATA.rate or DATA.time! Type "help allan_modified" for more information. [err2]'); | |
| 474 | +end | |
| 475 | + | |
| 476 | + | |
| 477 | +%%%%%%%% | |
| 478 | +%% Plotting | |
| 479 | + | |
| 480 | +if verbose >= 2 % show all data | |
| 481 | + | |
| 482 | + % plot the frequency data, centered on median | |
| 483 | + if size(dtime,2) > size(dtime,1), dtime=dtime'; end % this should not be necessary, but dsplot 1.1 is a little bit brittle | |
| 484 | + try | |
| 485 | + % dsplot makes a new figure | |
| 486 | + hd=dsplot(dtime,medianfreq); | |
| 487 | + catch ME | |
| 488 | + figure; | |
| 489 | + hd=plot(dtime,medianfreq); | |
| 490 | + if verbose >= 1, fprintf(1,'allan_modified: Note: Install dsplot.m for improved plotting of large data sets (File Exchange File ID: #15850).\n'); end | |
| 491 | + if verbose >= 2, fprintf(1,' (Message: %s)\n',ME.message); end | |
| 492 | + end | |
| 493 | + set(hd,'Marker','.','LineStyle','none','Color','b'); % equivalent to '.-' | |
| 494 | + hold on; | |
| 495 | + | |
| 496 | + fx = xlim; | |
| 497 | + % plot([fx(1) fx(2)],[s.median s.median],'-k'); | |
| 498 | + plot([fx(1) fx(2)],[0 0],':k'); | |
| 499 | + % show 5x Median Absolute deviation (MAD) values | |
| 500 | + hm=plot([fx(1) fx(2)],[5*MAD 5*MAD],'-r'); | |
| 501 | + plot([fx(1) fx(2)],[-5*MAD -5*MAD],'-r'); | |
| 502 | + % show linear fit line | |
| 503 | + hf=plot(xlim,polyval(s.linear,xlim)-s.median,'-g'); | |
| 504 | + title(['Data: ' name],'FontSize',FontSize+2,'FontName',FontName); | |
| 505 | + %set(get(gca,'Title'),'Interpreter','none'); | |
| 506 | + xlabel('Time [sec]','FontSize',FontSize,'FontName',FontName); | |
| 507 | + if isfield(data,'units') | |
| 508 | + ylabel(['data - median(data) [' data.units ']'],'FontSize',FontSize,'FontName',FontName); | |
| 509 | + else | |
| 510 | + ylabel('freq - median(freq)','FontSize',FontSize,'FontName',FontName); | |
| 511 | + end | |
| 512 | + set(gca,'FontSize',FontSize,'FontName',FontName); | |
| 513 | + legend([hd hm hf],{'data (centered on median)','5x MAD outliers',['Linear Fit (' num2str(s.linear(1),'%g') ')']},'FontSize',max(10,FontSize-2)); | |
| 514 | + % tighten up | |
| 515 | + xlim([dtime(1) dtime(end)]); | |
| 516 | + | |
| 517 | + | |
| 518 | +end % end plot raw data | |
| 519 | + | |
| 520 | + | |
| 521 | +if verbose >= 1 % show analysis results | |
| 522 | + | |
| 523 | + % plot Allan deviation results | |
| 524 | + if ~isempty(sm) | |
| 525 | + figure | |
| 526 | + | |
| 527 | + % Choose loglog or semilogx plot here #PLOTLOG | |
| 528 | + %semilogx(tau,sm,'.-b','LineWidth',plotlinewidth,'MarkerSize',24); | |
| 529 | + loglog(tau,sm,'.-b','LineWidth',plotlinewidth,'MarkerSize',24); | |
| 530 | + | |
| 531 | + % in R14SP3, there is a bug that screws up the error bars on a semilog plot. | |
| 532 | + % When this is fixed, uncomment below to use normal errorbars | |
| 533 | + %errorbar(tau,sm,sme,'.-b'); set(gca,'XScale','log'); | |
| 534 | + % this is a hack to approximate the error bars | |
| 535 | + hold on; plot([tau; tau],[sm+sme; sm-sme],'-k','LineWidth',max(plotlinewidth-1,2)); | |
| 536 | + | |
| 537 | + grid on; | |
| 538 | + title(['Modified Allan Deviation: ' name],'FontSize',FontSize+2,'FontName',FontName); | |
| 539 | + %set(get(gca,'Title'),'Interpreter','none'); | |
| 540 | + xlabel('\tau [sec]','FontSize',FontSize,'FontName',FontName); | |
| 541 | + ylabel('Modified \sigma_y(\tau)','FontSize',FontSize,'FontName',FontName); | |
| 542 | + set(gca,'FontSize',FontSize,'FontName',FontName); | |
| 543 | + % expand the x axis a little bit so that the errors bars look nice | |
| 544 | + adax = axis; | |
| 545 | + axis([adax(1)*0.9 adax(2)*1.1 adax(3) adax(4)]); | |
| 546 | + | |
| 547 | + % display the minimum value | |
| 548 | + fprintf(1,'allan: Minimum modified ADEV value: %g at tau = %g seconds\n',min(sm),tau(sm==min(sm))); | |
| 549 | + | |
| 550 | + elseif verbose >= 1 | |
| 551 | + fprintf(1,'allan_modified: WARNING: no values calculated.\n'); | |
| 552 | + fprintf(1,' Check that TAU > 1/DATA.rate and TAU values are divisible by 1/DATA.rate\n'); | |
| 553 | + fprintf(1,'Type "help allan_modified" for more information.\n\n'); | |
| 554 | + end | |
| 555 | + | |
| 556 | +end % end plot analysis | |
| 557 | + | |
| 558 | +retval = sm; | |
| 559 | +errorb = sme; | |
| 560 | + | |
| 561 | +return |
allan_overlap.m
| ... | ... | @@ -0,0 +1,547 @@ |
| 1 | +function [retval, s, errorb, tau] = allan_overlap(data,tau,name,verbose) | |
| 2 | +% ALLAN_OVERLAP Compute the overlapping Allan deviation for a set of | |
| 3 | +% time-domain frequency data | |
| 4 | +% [RETVAL, S, ERRORB, TAU] = ALLAN_OVERLAP(DATA,TAU,NAME,VERBOSE) | |
| 5 | +% | |
| 6 | +% Inputs: | |
| 7 | +% DATA should be a struct and have the following fields: | |
| 8 | +% DATA.freq or DATA.phase | |
| 9 | +% A vector of fractional frequency measurements (df/f) in | |
| 10 | +% DATA.freq *or* phase offset data (seconds) in DATA.phase | |
| 11 | +% If phase data is not present, it will be generated by | |
| 12 | +% integrating the fractional frequency data. | |
| 13 | +% If both fields are present, then DATA.phase will be used. | |
| 14 | +% | |
| 15 | +% DATA.rate or DATA.time | |
| 16 | +% The sampling rate in Hertz (DATA.rate) or a vector of | |
| 17 | +% timestamps for each measurement in seconds (DATA.time). | |
| 18 | +% DATA.rate is used if both fields are present. | |
| 19 | +% If DATA.rate == 0, then the timestamps are used. | |
| 20 | +% | |
| 21 | +% TAU is an array of tau values for computing Allan deviation. | |
| 22 | +% TAU values must be divisible by 1/DATA.rate (data points cannot be | |
| 23 | +% grouped in fractional quantities!). Invalid values are ignored. | |
| 24 | +% NAME is an optional label that is added to the plot titles. | |
| 25 | +% VERBOSE sets the level of status messages: | |
| 26 | +% 0 = silent & no data plots; 1 = status messages; 2 = all messages | |
| 27 | +% | |
| 28 | +% Outputs: | |
| 29 | +% RETVAL is the array of overlapping Allan deviation values at each TAU. | |
| 30 | +% S is an optional output of other statistical measures of the data (mean, std, etc). | |
| 31 | +% ERRORB is an optional output containing the error estimates for a 1-sigma | |
| 32 | +% confidence interval. Error bars are plotted as vertical lines at each point. | |
| 33 | +% TAU is an optional output containing the array of tau values used in the | |
| 34 | +% calculation (which may be a truncated subset of the input or default values). | |
| 35 | +% | |
| 36 | +% Example: | |
| 37 | +% | |
| 38 | +% To compute the overlapping Allan deviation for the data in the variable "lt": | |
| 39 | +% >> lt | |
| 40 | +% lt = | |
| 41 | +% freq: [1x86400 double] | |
| 42 | +% rate: 0.5 | |
| 43 | +% | |
| 44 | +% Use: | |
| 45 | +% | |
| 46 | +% >> ado = allan_overlap(lt,[2 10 100],'lt data',1); | |
| 47 | +% | |
| 48 | +% The Allan deviation will be computed and plotted at tau = 2,10,100 seconds. | |
| 49 | +% 1-sigma confidence intervals will be indicated by vertical lines. | |
| 50 | +% You can also use the default settings, which are usually a good starting point: | |
| 51 | +% | |
| 52 | +% >> ado = allan_overlap(lt); | |
| 53 | +% | |
| 54 | +% | |
| 55 | +% Notes: | |
| 56 | +% This function calculates the overlapping Allan deviation (ADEV), *not* the | |
| 57 | +% standard ADEV. Use "allan.m" for standard ADEV. | |
| 58 | +% The calculation is performed using phase data. If only frequency data is | |
| 59 | +% provided, phase data is generated by integrating the frequency data. | |
| 60 | +% However, the timestamp-based calculation is performed using frequency | |
| 61 | +% data. Phase data is differentiated to generate frequency data if necessary. | |
| 62 | +% No pre-processing of the data is performed, except to remove any | |
| 63 | +% initial offset in the time record. | |
| 64 | +% For rate-based data, ADEV is computed only for tau values greater than the | |
| 65 | +% minimum time between samples and less than the half the total time. For | |
| 66 | +% time-stamped data, only tau values greater than the maximum gap between | |
| 67 | +% samples and less than half the total time are used. | |
| 68 | +% The calculation for fixed sample rate data is *much* faster than for | |
| 69 | +% time-stamp data. You may wish to run the rate-based calculation first, | |
| 70 | +% then compare with time-stamp-based. Often the differences are insignificant. | |
| 71 | +% The error bars at each point are calculated using the 1-sigma intervals | |
| 72 | +% based on the size of the data set. This is usually an overestimate for | |
| 73 | +% overlapping ADEV; a more accurate (and usually smaller uncertainty) | |
| 74 | +% value can be determined from chi-squared statistics, but that is not | |
| 75 | +% implemented in this version. | |
| 76 | +% You can choose between loglog and semilog plotting of results by | |
| 77 | +% commenting in/out the appropriate line. Search for "#PLOTLOG". | |
| 78 | +% This function has been validated using the test data from NBS Monograph | |
| 79 | +% 140, the 1000-point test data set given by Riley [1], and the example data | |
| 80 | +% given in IEEE standard 1139-1999, Annex C. | |
| 81 | +% The author welcomes other validation results, see contact info below. | |
| 82 | +% | |
| 83 | +% For more information, see: | |
| 84 | +% [1] W. J. Riley, "Addendum to a test suite for the calculation of time domain | |
| 85 | +% frequency stability," presented at IEEE Frequency Control Symposium, | |
| 86 | +% 1996. | |
| 87 | +% Available on the web: | |
| 88 | +% http://www.ieee-uffc.org/frequency_control/teaching.asp?name=paper1ht | |
| 89 | +% | |
| 90 | +% | |
| 91 | +% M.A. Hopcroft | |
| 92 | +% mhopeng at gmail dot com | |
| 93 | +% | |
| 94 | +% I welcome your comments and feedback! | |
| 95 | +% | |
| 96 | +% MH Mar2014 | |
| 97 | +% v2.24 fix bug related to generating freq data from phase with timestamps | |
| 98 | +% (thanks to S. David-Grignot for finding the bug) | |
| 99 | +% MH Oct2010 | |
| 100 | +% v2.22 tau truncation to integer groups; tau sort | |
| 101 | +% plotting bugfix | |
| 102 | +% v2.20 update to match allan.m (dsplot.m, columns) | |
| 103 | +% discard tau values with timestamp irregularities | |
| 104 | + | |
| 105 | +versionstr = 'allan_overlap v2.24'; | |
| 106 | + | |
| 107 | +% | |
| 108 | +% MH MAR2010 | |
| 109 | +% v2.1 bugfixes for irregular sample rates | |
| 110 | +% (thanks to Ryad Ben-El-Kezadri for feedback and testing) | |
| 111 | +% handle empty rate field | |
| 112 | +% fix integer comparisons for fractional sample rates | |
| 113 | +% update consistency check | |
| 114 | +% | |
| 115 | +% MH FEB2010 | |
| 116 | +% v2.0 use phase data for calculation- much faster | |
| 117 | +% Consistent code behaviour for all "allan_x.m" functions: | |
| 118 | +% accept phase data | |
| 119 | +% verbose levels | |
| 120 | +% | |
| 121 | +% MH JAN2010 | |
| 122 | +% v1.0 based on allan v1.84 | |
| 123 | +% | |
| 124 | + | |
| 125 | +%#ok<*AGROW> | |
| 126 | + | |
| 127 | + | |
| 128 | +% defaults | |
| 129 | +if nargin < 4, verbose = 2; end | |
| 130 | +if nargin < 3, name=''; end | |
| 131 | +if nargin < 2 || isempty(tau), tau=2.^(-10:10); end | |
| 132 | +if isfield(data,'rate') && isempty(data.rate), data.rate=0; end % v2.1 | |
| 133 | + | |
| 134 | +% Formatting for plots | |
| 135 | +FontName = 'Arial'; | |
| 136 | +FontSize = 14; | |
| 137 | +plotlinewidth=2; | |
| 138 | + | |
| 139 | +if verbose >= 1, fprintf(1,'allan_overlap: %s\n\n',versionstr); end | |
| 140 | + | |
| 141 | +%% Data consistency checks v2.1 | |
| 142 | +if ~(isfield(data,'phase') || isfield(data,'freq')) | |
| 143 | + error('Either ''phase'' or ''freq'' must be present in DATA. See help file for details. [con0]'); | |
| 144 | +end | |
| 145 | +if isfield(data,'time') | |
| 146 | + if isfield(data,'phase') && (length(data.phase) ~= length(data.time)) | |
| 147 | + if isfield(data,'freq') && (length(data.freq) ~= length(data.time)) | |
| 148 | + error('The time and freq vectors are not the same length. See help for details. [con2]'); | |
| 149 | + else | |
| 150 | + error('The time and phase vectors are not the same length. See help for details. [con1]'); | |
| 151 | + end | |
| 152 | + end | |
| 153 | + if isfield(data,'phase') && (any(isnan(data.phase)) || any(isinf(data.phase))) | |
| 154 | + error('The phase vector contains invalid elements (NaN/Inf). [con3]'); | |
| 155 | + end | |
| 156 | + if isfield(data,'freq') && (any(isnan(data.freq)) || any(isinf(data.freq))) | |
| 157 | + error('The freq vector contains invalid elements (NaN/Inf). [con4]'); | |
| 158 | + end | |
| 159 | + if isfield(data,'time') && (any(isnan(data.time)) || any(isinf(data.time))) | |
| 160 | + error('The time vector contains invalid elements (NaN/Inf). [con5]'); | |
| 161 | + end | |
| 162 | +end | |
| 163 | + | |
| 164 | +% sort tau vector | |
| 165 | +tau=sort(tau); | |
| 166 | + | |
| 167 | +%% Basic statistical tests on the data set | |
| 168 | +if ~isfield(data,'freq') | |
| 169 | + if isfield(data,'rate') && data.rate ~= 0 | |
| 170 | + data.freq=diff(data.phase).*data.rate; | |
| 171 | + elseif isfield(data,'time') | |
| 172 | + data.freq=diff(data.phase)./diff(data.time); | |
| 173 | + end | |
| 174 | + if verbose >= 1, fprintf(1,'allan_overlap: Fractional frequency data generated from phase data (M=%g).\n',length(data.freq)); end | |
| 175 | +end | |
| 176 | +if size(data.freq,2) > size(data.freq,1), data.freq=data.freq'; end % ensure columns | |
| 177 | + | |
| 178 | +s.numpoints=length(data.freq); | |
| 179 | +s.max=max(data.freq); | |
| 180 | +s.min=min(data.freq); | |
| 181 | +s.mean=mean(data.freq); | |
| 182 | +s.median=median(data.freq); | |
| 183 | +if isfield(data,'time') | |
| 184 | + if size(data.time,2) > size(data.time,1), data.time=data.time'; end % ensure columns | |
| 185 | + s.linear=polyfit(data.time(1:length(data.freq)),data.freq,1); | |
| 186 | +elseif isfield(data,'rate') && data.rate ~= 0; | |
| 187 | + s.linear=polyfit((1/data.rate:1/data.rate:length(data.freq)/data.rate)',data.freq,1); | |
| 188 | +else | |
| 189 | + error('Either "time" or "rate" must be present in DATA. Type "help allan_overlap" for details. [err1]'); | |
| 190 | +end | |
| 191 | +s.std=std(data.freq); | |
| 192 | + | |
| 193 | +if verbose >= 2 | |
| 194 | + fprintf(1,'allan_overlap: fractional frequency data statistics:\n'); | |
| 195 | + disp(s); | |
| 196 | +end | |
| 197 | + | |
| 198 | + | |
| 199 | +% scale to median for plotting | |
| 200 | +medianfreq=data.freq-s.median; | |
| 201 | +sm=[]; sme=[]; | |
| 202 | + | |
| 203 | +% Screen for outliers using 5x Median Absolute Deviation (MAD) criteria | |
| 204 | +MAD = median(abs(medianfreq)/0.6745); | |
| 205 | +if verbose >= 1 && any(abs(medianfreq) > 5*MAD) | |
| 206 | + fprintf(1, 'allan_overlap: NOTE: There appear to be outliers in the frequency data. See plot.\n'); | |
| 207 | +end | |
| 208 | + | |
| 209 | +%%%% | |
| 210 | +% There are four cases, freq or phase data, using timestamps or rate: | |
| 211 | + | |
| 212 | +%% Fixed Sample Rate Data | |
| 213 | +% If there is a regular interval between measurements, calculation is much | |
| 214 | +% easier/faster | |
| 215 | +if isfield(data,'rate') && data.rate > 0 % if data rate was given | |
| 216 | + if verbose >= 1 | |
| 217 | + fprintf(1, 'allan_overlap: regular data '); | |
| 218 | + if isfield(data,'freq') | |
| 219 | + fprintf(1, '(%g freq data points @ %g Hz)\n',length(data.freq),data.rate); | |
| 220 | + elseif isfield(data,'phase') | |
| 221 | + fprintf(1, '(%g phase data points @ %g Hz)\n',length(data.phase),data.rate); | |
| 222 | + else | |
| 223 | + error('\n phase or freq data missing [err10]'); | |
| 224 | + end | |
| 225 | + end | |
| 226 | + | |
| 227 | + % string for plot title | |
| 228 | + name=[name ' (' num2str(data.rate) ' Hz)']; | |
| 229 | + | |
| 230 | + % what is the time interval between data points? | |
| 231 | + tmstep = 1/data.rate; | |
| 232 | + | |
| 233 | + % Is there time data? Just for curiosity/plotting, does not impact calculation | |
| 234 | + if isfield(data,'time') | |
| 235 | + % adjust time data to remove any starting gap; first time step | |
| 236 | + % should not be zero for comparison with freq data | |
| 237 | + dtime=data.time-data.time(1)+mean(diff(data.time)); | |
| 238 | + dtime=dtime(1:length(medianfreq)); % equalize the data vector lengths for plotting (v2.1) | |
| 239 | + if verbose >= 2 | |
| 240 | + fprintf(1,'allan_overlap: End of timestamp data: %g sec.\n',dtime(end)); | |
| 241 | + if (data.rate - 1/mean(diff(dtime))) > 1e-6 | |
| 242 | + fprintf(1,'allan_overlap: NOTE: data.rate (%f Hz) does not match average timestamped sample rate (%f Hz)\n',data.rate,1/mean(diff(dtime))); | |
| 243 | + end | |
| 244 | + end | |
| 245 | + else | |
| 246 | + % create time axis data using rate (for plotting only) | |
| 247 | + dtime=(tmstep:tmstep:length(data.freq)*tmstep); | |
| 248 | + end | |
| 249 | + | |
| 250 | + | |
| 251 | + % is phase data present? If not, generate it | |
| 252 | + if ~isfield(data,'phase') | |
| 253 | + nfreq=data.freq-s.mean; | |
| 254 | + dphase=zeros(1,length(nfreq)+1); | |
| 255 | + dphase(2:end) = cumsum(nfreq)./data.rate; | |
| 256 | + if verbose >= 1, fprintf(1,'allan_overlap: phase data generated from fractional frequency data (N=%g).\n',length(dphase)); end | |
| 257 | + else | |
| 258 | + dphase=data.phase; | |
| 259 | + end | |
| 260 | + | |
| 261 | + % check the range of tau values and truncate if necessary | |
| 262 | + % find halfway point of time record | |
| 263 | + halftime = round(tmstep*length(data.freq)/2); | |
| 264 | + % truncate tau to appropriate values | |
| 265 | + tau = tau(tau >= tmstep & tau <= halftime); | |
| 266 | + if verbose >= 2, fprintf(1, 'allan_overlap: allowable tau range: %g to %g sec. (1/rate to total_time/2)\n',tmstep,halftime); end | |
| 267 | + | |
| 268 | + % number of samples | |
| 269 | + N=length(dphase); | |
| 270 | + % number of samples per tau period | |
| 271 | + m = data.rate.*tau; | |
| 272 | + % only integer values allowed for m (no fractional groups of points) | |
| 273 | + %tau = tau(m-round(m)<1e-8); % numerical precision issues (v2.1) | |
| 274 | + tau = tau(m==round(m)); % The round() test is only correct for values < 2^53 | |
| 275 | + %m = m(m-round(m)<1e-8); % change to round(m) for integer test v2.22 | |
| 276 | + m = m(m==round(m)); | |
| 277 | + %m=round(m); | |
| 278 | + %fprintf(1,'m: %.50f\n',m) | |
| 279 | + | |
| 280 | + if verbose >= 1, fprintf(1,'allan_overlap: calculating overlapping Allan deviation...\n '); end | |
| 281 | + | |
| 282 | + % calculate the Allan deviation for each value of tau | |
| 283 | + k=0; tic; | |
| 284 | + for i = tau | |
| 285 | + k=k+1; | |
| 286 | + if verbose >= 2, fprintf(1,'%d ',i); end | |
| 287 | + | |
| 288 | + | |
| 289 | + % pad phase data set length to an even multiple of this tau value | |
| 290 | + mphase=zeros(ceil(length(dphase)./m(k))*m(k),1); | |
| 291 | + mphase(1:N)=dphase; | |
| 292 | + % group phase values | |
| 293 | + mp=reshape(mphase,m(k),[]); | |
| 294 | + % compute second differences of phase values (x_k+m - x_k) | |
| 295 | + md1=diff(mp,1,2); | |
| 296 | + md2=diff(md1,1,2); | |
| 297 | + md1=reshape(md2,1,[]); | |
| 298 | + | |
| 299 | + % compute overlapping ADEV from phase values | |
| 300 | + % only the first N-2*m(k) samples are valid | |
| 301 | + sm(k)=sqrt((1/(2*(N-2*m(k))*i^2))*sum(md1(1:N-2*m(k)).^2)); | |
| 302 | + | |
| 303 | + % estimate error bars | |
| 304 | + sme(k)=sm(k)/sqrt(N-2*m(k)); | |
| 305 | + | |
| 306 | + | |
| 307 | + end % repeat for each value of tau | |
| 308 | + | |
| 309 | + if verbose >= 2, fprintf(1,'\n'); end | |
| 310 | + calctime=toc; if verbose >= 2, fprintf(1,'allan_overlap: Elapsed time for calculation: %g seconds\n',calctime); end | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | +%% Irregular data, no fixed interval | |
| 315 | +elseif isfield(data,'time') | |
| 316 | + % the interval between measurements is irregular | |
| 317 | + % so we must group the data by time | |
| 318 | + if verbose >= 1, fprintf(1, 'allan_overlap: irregular rate data (no fixed sample rate)\n'); end | |
| 319 | + | |
| 320 | + | |
| 321 | + % string for plot title | |
| 322 | + name=[name ' (timestamp)']; | |
| 323 | + | |
| 324 | + | |
| 325 | + % adjust time to remove any starting offset | |
| 326 | + dtime=data.time-data.time(1)+mean(diff(data.time)); | |
| 327 | + | |
| 328 | + % save the freq data for the loop | |
| 329 | + dfreq=data.freq; | |
| 330 | + dtime=dtime(1:length(dfreq)); | |
| 331 | + | |
| 332 | + dfdtime=diff(dtime); % only need to do this once (v2.1) | |
| 333 | + % where is the maximum gap in time record? | |
| 334 | + gap_pos=find(dfdtime==max(dfdtime)); | |
| 335 | + % what is average data spacing? | |
| 336 | + avg_gap = mean(dfdtime); | |
| 337 | + s.avg_rate = 1/avg_gap; % save avg rate for user (v2.1) | |
| 338 | + | |
| 339 | + if verbose >= 2 | |
| 340 | + fprintf(1, 'allan_overlap: WARNING: irregular timestamp data (no fixed sample rate).\n'); | |
| 341 | + fprintf(1, ' Calculation time may be long and the results subject to interpretation.\n'); | |
| 342 | + fprintf(1, ' You are advised to estimate using an average sample rate (%g Hz) instead of timestamps.\n',1/avg_gap); | |
| 343 | + fprintf(1, ' Continue at your own risk! (press any key to continue)\n'); | |
| 344 | + pause; | |
| 345 | + end | |
| 346 | + | |
| 347 | + if verbose >= 1 | |
| 348 | + fprintf(1, 'allan_overlap: End of timestamp data: %g sec\n',dtime(end)); | |
| 349 | + fprintf(1, ' Average rate: %g Hz (%g sec/measurement)\n',1/avg_gap,avg_gap); | |
| 350 | + if max(diff(dtime)) ~= 1/mean(diff(dtime)) | |
| 351 | + fprintf(1, ' Max. gap in time record: %g sec at position %d\n',max(dfdtime),gap_pos(1)); | |
| 352 | + end | |
| 353 | + if max(diff(dtime)) > 5*avg_gap | |
| 354 | + fprintf(1, ' WARNING: Max. gap in time record is suspiciously large (>5x the average interval).\n'); | |
| 355 | + end | |
| 356 | + end | |
| 357 | + | |
| 358 | + | |
| 359 | + % find halfway point | |
| 360 | + halftime = fix(dtime(end)/2); | |
| 361 | + % truncate tau to appropriate values | |
| 362 | + tau = tau(tau >= max(dfdtime) & tau <= halftime); | |
| 363 | + if isempty(tau) | |
| 364 | + error('allan_overlap: ERROR: no appropriate tau values (> %g s, < %g s)\n',max(dfdtime),halftime); | |
| 365 | + end | |
| 366 | + | |
| 367 | + | |
| 368 | + % number of samples | |
| 369 | + M=length(dfreq); | |
| 370 | + % number of samples per tau period | |
| 371 | + m=round(tau./avg_gap); | |
| 372 | + | |
| 373 | + if verbose >= 1, fprintf(1,'allan_overlap: calculating overlapping Allan deviation...\n'); end | |
| 374 | + | |
| 375 | + k=0; tic; | |
| 376 | + for i = tau | |
| 377 | + k=k+1; | |
| 378 | + fa=[]; | |
| 379 | + | |
| 380 | + if verbose >= 2, fprintf(1,'%d ',i); end | |
| 381 | + | |
| 382 | + freq = dfreq; time = dtime; | |
| 383 | + | |
| 384 | + | |
| 385 | + % compute overlapping samples (y_k) for this tau | |
| 386 | + %for j = 1:i | |
| 387 | + for j = 1:m(k) % (v2.1) | |
| 388 | + km=0; | |
| 389 | + %fprintf(1,'j: %d ',j); | |
| 390 | + | |
| 391 | + % (v2.1) truncating not correct for overlapping samples | |
| 392 | + % truncate data set to an even multiple of this tau value | |
| 393 | + %freq = freq(time <= time(end)-rem(time(end),i)); | |
| 394 | + %time = time(time <= time(end)-rem(time(end),i)); | |
| 395 | + | |
| 396 | + % break up the data into overlapping groups of tau length | |
| 397 | + while i*km <= time(end) | |
| 398 | + km=km+1; | |
| 399 | + %i*km | |
| 400 | + | |
| 401 | + % progress bar | |
| 402 | + if verbose >= 2 | |
| 403 | + if rem(km,100)==0, fprintf(1,'.'); end | |
| 404 | + if rem(km,1000)==0, fprintf(1,'%g/%g\n',km,round(time(end)/i)); end | |
| 405 | + end | |
| 406 | + | |
| 407 | + f = freq(i*(km-1) < (time) & (time) <= i*km); | |
| 408 | + | |
| 409 | + if ~isempty(f) | |
| 410 | + fa(j,km)=mean(f); | |
| 411 | + else | |
| 412 | + fa(j,km)=0; | |
| 413 | + end | |
| 414 | + | |
| 415 | + end | |
| 416 | + %fa | |
| 417 | + | |
| 418 | + % shift data vector by -1 and repeat | |
| 419 | + freq=circshift(dfreq,(size(freq)>1)*-j); | |
| 420 | + freq(end-j+1:end)=[]; | |
| 421 | + time=circshift(dtime,(size(time)>1)*-j); | |
| 422 | + time(end-j+1:end)=[]; | |
| 423 | + time=time-time(1)+avg_gap; % remove time offset | |
| 424 | + | |
| 425 | + end | |
| 426 | + | |
| 427 | + % compute second differences of fractional frequency values (y_k+m - y_k) | |
| 428 | + fd1=diff(fa,1,2); | |
| 429 | + fd1=reshape(fd1,1,[]); | |
| 430 | + % compute overlapping ADEV from fractional frequency values | |
| 431 | + % only the first M-2*m(k)+1 samples are valid | |
| 432 | + if length(fd1) >= M-2*m(k)+1 | |
| 433 | + sm(k)=sqrt((1/(2*(M-2*m(k)+1)))*sum(fd1(1:M-2*m(k)+1).^2)); | |
| 434 | + | |
| 435 | + % estimate error bars | |
| 436 | + sme(k)=sm(k)/sqrt(M+1); | |
| 437 | + | |
| 438 | + if verbose >= 2, fprintf(1,'\n'); end | |
| 439 | + | |
| 440 | + else | |
| 441 | + if verbose >=2, fprintf(1,' tau=%g dropped due to timestamp irregularities\n',tau(k)); end | |
| 442 | + sm(k)=0; sme(k)=0; | |
| 443 | + end | |
| 444 | + | |
| 445 | + | |
| 446 | + end | |
| 447 | + | |
| 448 | + if verbose >= 2, fprintf(1,'\n'); end | |
| 449 | + calctime=toc; if verbose >= 1, fprintf(1,'allan_overlap: Elapsed time for calculation: %g seconds\n',calctime); end | |
| 450 | + | |
| 451 | + % remove any points that were dropped | |
| 452 | + tau(sm==0)=[]; | |
| 453 | + sm(sm==0)=[]; | |
| 454 | + sme(sme==0)=[]; | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | +else | |
| 459 | + error('allan_overlap: WARNING: no DATA.rate or DATA.time! Type "help allan" for more information. [err2]'); | |
| 460 | +end | |
| 461 | + | |
| 462 | + | |
| 463 | +%%%%%%%% | |
| 464 | +%% Plotting | |
| 465 | + | |
| 466 | +if verbose >= 2 % show all data | |
| 467 | + | |
| 468 | + % plot the frequency data, centered on median | |
| 469 | + if size(dtime,2) > size(dtime,1), dtime=dtime'; end % this should not be necessary, but dsplot 1.1 is a little bit brittle | |
| 470 | + try | |
| 471 | + % dsplot makes a new figure | |
| 472 | + hd=dsplot(dtime,medianfreq); | |
| 473 | + catch ME | |
| 474 | + figure; | |
| 475 | + hd=plot(dtime,medianfreq); | |
| 476 | + if verbose >= 1, fprintf(1,'allan_overlap: Note: Install dsplot.m for improved plotting of large data sets (File Exchange File ID: #15850).\n'); end | |
| 477 | + if verbose >= 2, fprintf(1,' (Message: %s)\n',ME.message); end | |
| 478 | + end | |
| 479 | + set(hd,'Marker','.','LineStyle','none','Color','b'); % equivalent to '.-' | |
| 480 | + hold on; | |
| 481 | + | |
| 482 | + fx = xlim; | |
| 483 | + % plot([fx(1) fx(2)],[s.median s.median],'-k'); | |
| 484 | + plot([fx(1) fx(2)],[0 0],':k'); | |
| 485 | + % show 5x Median Absolute deviation (MAD) values | |
| 486 | + hm=plot([fx(1) fx(2)],[5*MAD 5*MAD],'-r'); | |
| 487 | + plot([fx(1) fx(2)],[-5*MAD -5*MAD],'-r'); | |
| 488 | + % show linear fit line | |
| 489 | + hf=plot(xlim,polyval(s.linear,xlim)-s.median,'-g'); | |
| 490 | + title(['Data: ' name],'FontSize',FontSize+2,'FontName','Arial'); | |
| 491 | + %set(get(gca,'Title'),'Interpreter','none'); | |
| 492 | + xlabel('Time [sec]','FontSize',FontSize,'FontName',FontName); | |
| 493 | + if isfield(data,'units') | |
| 494 | + ylabel(['data - median(data) [' data.units ']'],'FontSize',FontSize,'FontName',FontName); | |
| 495 | + else | |
| 496 | + ylabel('freq - median(freq)','FontSize',FontSize,'FontName',FontName); | |
| 497 | + end | |
| 498 | + set(gca,'FontSize',FontSize,'FontName',FontName); | |
| 499 | + legend([hd hm hf],{'data (centered on median)','5x MAD outliers',['Linear Fit (' num2str(s.linear(1),'%g') ')']},'FontSize',max(10,FontSize-2)); | |
| 500 | + % tighten up | |
| 501 | + xlim([dtime(1) dtime(end)]); | |
| 502 | + | |
| 503 | + | |
| 504 | +end % end plot raw data | |
| 505 | + | |
| 506 | + | |
| 507 | +if verbose >= 1 % show analysis results | |
| 508 | + | |
| 509 | + % plot Allan deviation results | |
| 510 | + if ~isempty(sm) | |
| 511 | + figure | |
| 512 | + | |
| 513 | + % Choose loglog or semilogx plot here #PLOTLOG | |
| 514 | + %semilogx(tau,sm,'.-b','LineWidth',plotlinewidth,'MarkerSize',24); | |
| 515 | + loglog(tau,sm,'.-b','LineWidth',plotlinewidth,'MarkerSize',24); | |
| 516 | + | |
| 517 | + % in R14SP3, there is a bug that screws up the error bars on a semilog plot. | |
| 518 | + % When this is fixed, uncomment below to use normal errorbars | |
| 519 | + %errorbar(tau,sm,sme,'.-b'); set(gca,'XScale','log'); | |
| 520 | + % this is a hack to approximate the error bars | |
| 521 | + hold on; plot([tau; tau],[sm+sme; sm-sme],'-k','LineWidth',max(plotlinewidth-1,2)); | |
| 522 | + | |
| 523 | + grid on; | |
| 524 | + title(['Overlapping Allan Deviation: ' name],'FontSize',FontSize+2,'FontName',FontName); | |
| 525 | + %set(get(gca,'Title'),'Interpreter','none'); | |
| 526 | + xlabel('\tau [sec]','FontSize',FontSize,'FontName','Arial'); | |
| 527 | + ylabel(' Overlapping \sigma_y(\tau)','FontSize',FontSize,'FontName',FontName); | |
| 528 | + set(gca,'FontSize',FontSize,'FontName',FontName); | |
| 529 | + % expand the x axis a little bit so that the errors bars look nice | |
| 530 | + adax = axis; | |
| 531 | + axis([adax(1)*0.9 adax(2)*1.1 adax(3) adax(4)]); | |
| 532 | + | |
| 533 | + % display the minimum value | |
| 534 | + fprintf(1,'allan: Minimum overlapping ADEV value: %g at tau = %g seconds\n',min(sm),tau(sm==min(sm))); | |
| 535 | + | |
| 536 | + elseif verbose >= 1 | |
| 537 | + fprintf(1,'allan_overlap: WARNING: no values calculated.\n'); | |
| 538 | + fprintf(1,' Check that TAU > 1/DATA.rate and TAU values are divisible by 1/DATA.rate\n'); | |
| 539 | + fprintf(1,'Type "help allan_overlap" for more information.\n\n'); | |
| 540 | + end | |
| 541 | + | |
| 542 | +end % end plot analysis | |
| 543 | + | |
| 544 | +retval = sm; | |
| 545 | +errorb = sme; | |
| 546 | + | |
| 547 | +return |
allanplot.m
| ... | ... | @@ -0,0 +1,12 @@ |
| 1 | +#!/usr/bin/octave-cli --persist | |
| 2 | + | |
| 3 | +filename = argv(){1}; | |
| 4 | +col = eval(argv(){2}); | |
| 5 | +mult = eval(argv(){3}); | |
| 6 | + | |
| 7 | +data.freq = load(filename)(:,col).*mult; | |
| 8 | +data.rate = 1; | |
| 9 | + | |
| 10 | +ad = allan(data, 2.^(0:12), strsplit(filename, '/'){end}, 1); | |
| 11 | +input("Press to continue..."); | |
| 12 | +exit |
dsplot.m
| ... | ... | @@ -0,0 +1,319 @@ |
| 1 | +function hL = dsplot(x, y, numPoints) | |
| 2 | + | |
| 3 | +%DSPLOT Create down sampled plot. | |
| 4 | +% This function creates a down sampled plot to improve the speed of | |
| 5 | +% exploration (zoom, pan). | |
| 6 | +% | |
| 7 | +% DSPLOT(X, Y) plots Y versus X by downsampling if there are large number | |
| 8 | +% of elements. X and Y needs to obey the following: | |
| 9 | +% 1. X must be a monotonically increasing vector. | |
| 10 | +% 2. If Y is a vector, it must be the same size as X. | |
| 11 | +% 3. If Y is a matrix, one of the dimensions must line up with X. | |
| 12 | +% | |
| 13 | +% DSPLOT(Y) plots the columns of Y versus their index. | |
| 14 | +% | |
| 15 | +% hLine = DSPLOT(X, Y) returns the handles of the line. Note that the | |
| 16 | +% lines may be downsampled, so they may not represent the full data set. | |
| 17 | +% | |
| 18 | +% DSPLOT(X, Y, NUMPOINTS) or DSPLOT(Y, [], NUMPOINTS) specifies the | |
| 19 | +% number of points (roughly) to display on the screen. The default is | |
| 20 | +% 50000 points (~390 kB doubles). NUMPOINTS can be a number greater than | |
| 21 | +% 500. | |
| 22 | +% | |
| 23 | +% It is very likely that more points will be displayed than specified by | |
| 24 | +% NUMPOINTS, because it will try to plot any outlier points in the range. | |
| 25 | +% If the signal is stochastic or has a lot of sharp changes, there will | |
| 26 | +% be more points on plotted on the screen. | |
| 27 | +% | |
| 28 | +% The figure title (name) will indicate whether the plot shown is | |
| 29 | +% downsampled or is the true representation. | |
| 30 | +% | |
| 31 | +% The figure can be saved as a .fig file, which will include the actual | |
| 32 | +% data. The figure can be reloaded and the actual data can be exported to | |
| 33 | +% the base workspace via a menu. | |
| 34 | +% | |
| 35 | +% Run the following examples and zoom/pan to see the performance. | |
| 36 | +% | |
| 37 | +% Example 1: (with small details) | |
| 38 | +% x = linspace(0, 2*pi, 1000000); | |
| 39 | +% y1 = sin(x)+.02*cos(200*x)+0.001*sin(2000*x)+0.0001*cos(20000*x); | |
| 40 | +% dsplot(x,y1);title('Down Sampled'); | |
| 41 | +% % compare with | |
| 42 | +% figure;plot(x,y1);title('Normal Plot'); | |
| 43 | +% | |
| 44 | +% Example 2: (with outlier points) | |
| 45 | +% x = linspace(0, 2*pi, 1000000); | |
| 46 | +% y1 = sin(x) + .01*cos(200*x) + 0.001*sin(2000*x); | |
| 47 | +% y2 = sin(x) + 0.3*cos(3*x) + 0.001*randn(size(x)); | |
| 48 | +% y1([300000, 700000, 700001, 900000]) = [0, 1, -2, 0.5]; | |
| 49 | +% y2(300000:500000) = y2(300000:500000) + 1; | |
| 50 | +% y2(500001:600000) = y2(500001:600000) - 1; | |
| 51 | +% y2(800000) = 0; | |
| 52 | +% dsplot(x, [y1;y2]);title('Down Sampled'); | |
| 53 | +% % compare with | |
| 54 | +% figure;plot(x, [y1;y2]);title('Normal Plot'); | |
| 55 | +% | |
| 56 | +% See also PLOT. | |
| 57 | + | |
| 58 | +% Version: | |
| 59 | +% v1.0 - first version (Aug 1, 2007) | |
| 60 | +% v1.1 - added CreateFcn for the figure so that when the figure is saved | |
| 61 | +% and re-loaded, the zooming and panning works. Also added a menu | |
| 62 | +% item for saving out the original data back to the base | |
| 63 | +% workspace. (Aug 10, 2007) | |
| 64 | +% | |
| 65 | +% Jiro Doke | |
| 66 | +% August 1, 2007 | |
| 67 | + | |
| 68 | +debugMode = false; | |
| 69 | + | |
| 70 | +%-------------------------------------------------------------------------- | |
| 71 | +% Error checking | |
| 72 | +error(nargchk(1, 3, nargin, 'struct')); | |
| 73 | +if nargin < 3 | |
| 74 | + % Number of points to show on the screen. It's quite possible that more | |
| 75 | + % points will be displayed if there are outlier points | |
| 76 | + numPoints = 50000; % ~390 kB for doubles | |
| 77 | +end | |
| 78 | +if nargin == 1 || isempty(y) | |
| 79 | + noXVar = true; | |
| 80 | + y = x; | |
| 81 | + x = []; | |
| 82 | +else | |
| 83 | + noXVar = false; | |
| 84 | +end | |
| 85 | +myErrorCheck; | |
| 86 | +%-------------------------------------------------------------------------- | |
| 87 | + | |
| 88 | +if size(x, 2) > 1 % it's a row vector -> transpose | |
| 89 | + x = x'; | |
| 90 | + y = y'; | |
| 91 | + varTranspose = true; | |
| 92 | +else | |
| 93 | + varTranspose = false; | |
| 94 | +end | |
| 95 | + | |
| 96 | +% Number of lines | |
| 97 | +numSignals = size(y, 2); | |
| 98 | + | |
| 99 | +% If the number of lines is greater than the number of data points per | |
| 100 | +% line, it's possible that the user may have mistaken the matrix | |
| 101 | +% orientation. | |
| 102 | +if numSignals > size(y, 1) | |
| 103 | + s = input(sprintf('Are you sure you want to plot %d lines? (y/n) ', ... | |
| 104 | + numSignals), 's'); | |
| 105 | + if ~strcmpi(s, 'y') | |
| 106 | + disp('Canceled. You may want to transpose the matrix.'); | |
| 107 | + if nargout == 1 | |
| 108 | + hL = []; | |
| 109 | + end | |
| 110 | + return; | |
| 111 | + end | |
| 112 | +end | |
| 113 | + | |
| 114 | +% Attempt to find outliers. Use a running average technique | |
| 115 | +filterWidth = ceil(min([50, length(x)/10])); % max window size of 50 | |
| 116 | +a = y - filter(ones(filterWidth,1)/filterWidth, 1, y); | |
| 117 | +[iOutliers, jOutliers] = find(abs(a - repmat(mean(a), size(a, 1), 1)) > ... | |
| 118 | + repmat(4 * std(a), size(a, 1), 1)); | |
| 119 | +clear a; | |
| 120 | + | |
| 121 | +% Always create new figure because it messes around with zoom, pan, | |
| 122 | +% datacursors. | |
| 123 | +hFig = figure; | |
| 124 | +figName = ''; | |
| 125 | + | |
| 126 | +% Create template plot using NaNs | |
| 127 | +hLine = plot(NaN(2, numSignals), NaN(2, numSignals)); | |
| 128 | +set(hLine, 'tag', 'dsplot_lines'); | |
| 129 | + | |
| 130 | +% Define CreateFcn for the figure | |
| 131 | +set(hFig, 'CreateFcn', @mycreatefcn); | |
| 132 | +mycreatefcn(); | |
| 133 | + | |
| 134 | +% Create menu for exporting data | |
| 135 | +hMenu = uimenu(hFig, 'Label', 'Data'); | |
| 136 | +uimenu(hMenu, ... | |
| 137 | + 'Label' , 'Export data to workspace.', ... | |
| 138 | + 'Callback', @myExportFcn); | |
| 139 | + | |
| 140 | +% Update lines | |
| 141 | +updateLines([min(x), max(x)]); | |
| 142 | + | |
| 143 | +% Deal with output argument | |
| 144 | +if nargout == 1 | |
| 145 | + hL = hLine; | |
| 146 | +end | |
| 147 | + | |
| 148 | +%-------------------------------------------------------------------------- | |
| 149 | + function myExportFcn(varargin) | |
| 150 | + % This callback allows for extracting the actual data from the figure. | |
| 151 | + % This means that if you save this figure and load it back later, you | |
| 152 | + % can get back the data. | |
| 153 | + | |
| 154 | + % Determine the variable name | |
| 155 | + allVarNames = evalin('base', 'who'); | |
| 156 | + newVarName = genvarname('dsplotData', allVarNames); | |
| 157 | + | |
| 158 | + % X | |
| 159 | + if ~noXVar | |
| 160 | + if varTranspose | |
| 161 | + dat.x = x'; | |
| 162 | + else | |
| 163 | + dat.x = x; | |
| 164 | + end | |
| 165 | + end | |
| 166 | + | |
| 167 | + % Y | |
| 168 | + if varTranspose | |
| 169 | + dat.y = y'; | |
| 170 | + else | |
| 171 | + dat.y = y; | |
| 172 | + end | |
| 173 | + | |
| 174 | + assignin('base', newVarName, dat); | |
| 175 | + | |
| 176 | + msgbox(sprintf('Data saved to the base workspace as ''%s''.', ... | |
| 177 | + newVarName), 'Saved', 'modal'); | |
| 178 | + | |
| 179 | + end | |
| 180 | + | |
| 181 | +%-------------------------------------------------------------------------- | |
| 182 | + function mycreatefcn(varargin) | |
| 183 | + % This callback defines the custom zoom/pan functions. It is defined as | |
| 184 | + % the CreateFcn of the figure, so it allows for saving and reloading of | |
| 185 | + % the figure. | |
| 186 | + | |
| 187 | + if nargin > 0 | |
| 188 | + hFig = varargin{1}; | |
| 189 | + end | |
| 190 | + hLine = findobj(hFig, 'type', 'axes'); | |
| 191 | + hLine(strmatch('legend', get(hLine, 'tag'))) = []; | |
| 192 | + hLine = get(hLine, 'Children'); | |
| 193 | + | |
| 194 | + % Create Zoom, Pan, Datacursor objects | |
| 195 | + hZoom = zoom(hFig); | |
| 196 | + hPan = pan(hFig); | |
| 197 | + hDc = datacursormode(hFig); | |
| 198 | + set(hZoom, 'ActionPostCallback', @mypostcallback); | |
| 199 | + set(hPan , 'ActionPostCallback', @mypostcallback); | |
| 200 | + set(hDc , 'UpdateFcn' , @myDCupdatefcn); | |
| 201 | + | |
| 202 | + end | |
| 203 | + | |
| 204 | +%-------------------------------------------------------------------------- | |
| 205 | + function mypostcallback(obj, evd) %#ok | |
| 206 | + % This callback that gets called when the mouse is released after | |
| 207 | + % zooming or panning. | |
| 208 | + | |
| 209 | + % single or double-click | |
| 210 | + switch get(hFig, 'SelectionType') | |
| 211 | + case {'normal', 'alt'} | |
| 212 | + updateLines(xlim(evd.Axes)); | |
| 213 | + | |
| 214 | + case 'open' | |
| 215 | + updateLines([min(x), max(x)]); | |
| 216 | + | |
| 217 | + end | |
| 218 | + | |
| 219 | + end | |
| 220 | + | |
| 221 | +%-------------------------------------------------------------------------- | |
| 222 | + function updateLines(rng) | |
| 223 | + % This helper function is for determining the points to plot on the | |
| 224 | + % screen based on which portion is visible in the current limits. | |
| 225 | + | |
| 226 | + % find indeces inside the range | |
| 227 | + id = find(x >= rng(1) & x <= rng(2)); | |
| 228 | + | |
| 229 | + % if there are more points than we want | |
| 230 | + if length(id) > numPoints / numSignals | |
| 231 | + | |
| 232 | + % see how many outlier points are in this range | |
| 233 | + blah = iOutliers > id(1) & iOutliers < id(end); | |
| 234 | + | |
| 235 | + % determine indeces of points to plot. | |
| 236 | + idid = round(linspace(id(1), id(end), round(numPoints/numSignals)))'; | |
| 237 | + | |
| 238 | + x2 = cell(numSignals, 1); | |
| 239 | + y2 = x2; | |
| 240 | + for iSignals = 1:numSignals | |
| 241 | + % add outlier points | |
| 242 | + ididid = unique([idid; iOutliers(blah & jOutliers == iSignals)]); | |
| 243 | + x2{iSignals} = x(ididid); | |
| 244 | + y2{iSignals} = y(ididid, iSignals); | |
| 245 | + end | |
| 246 | + | |
| 247 | + if debugMode | |
| 248 | + figName = ['downsampled - ', sprintf('%d, ', cellfun('length', y2))]; | |
| 249 | + else | |
| 250 | + figName = 'downsampled'; | |
| 251 | + end | |
| 252 | + | |
| 253 | + else % no need to down sample | |
| 254 | + figName = 'true'; | |
| 255 | + | |
| 256 | + x2 = repmat({x(id)}, numSignals, 1); | |
| 257 | + y2 = mat2cell(y(id, :), length(id), ones(1, numSignals))'; | |
| 258 | + | |
| 259 | + end | |
| 260 | + | |
| 261 | + % Update plot | |
| 262 | + set(hLine, {'xdata', 'ydata'} , [x2, y2]); | |
| 263 | + set(hFig, 'Name', figName); | |
| 264 | + | |
| 265 | + end | |
| 266 | + | |
| 267 | +%-------------------------------------------------------------------------- | |
| 268 | + function txt = myDCupdatefcn(empt, event_obj) %#ok | |
| 269 | + % This function displays appropriate data cursor message based on the | |
| 270 | + % display type | |
| 271 | + | |
| 272 | + pos = get(event_obj,'Position'); | |
| 273 | + switch figName | |
| 274 | + case 'true' | |
| 275 | + txt = {['X: ',num2str(pos(1))],... | |
| 276 | + ['Y: ',num2str(pos(2))]}; | |
| 277 | + otherwise | |
| 278 | + txt = {['X: ',num2str(pos(1))],... | |
| 279 | + ['Y: ',num2str(pos(2))], ... | |
| 280 | + 'Warning: Downsampled', ... | |
| 281 | + 'May not be accurate'}; | |
| 282 | + end | |
| 283 | + end | |
| 284 | + | |
| 285 | +%-------------------------------------------------------------------------- | |
| 286 | + function myErrorCheck | |
| 287 | + % Do some error checking on the input arguments. | |
| 288 | + | |
| 289 | + if ~isa(numPoints, 'double') || numel(numPoints) > 1 || numPoints < 500 | |
| 290 | + error('Third argument must be a scalar greater than 500'); | |
| 291 | + end | |
| 292 | + if ~isnumeric(x) || ~isnumeric(y) | |
| 293 | + error('Arguments must be numeric'); | |
| 294 | + end | |
| 295 | + if length(size(x)) > 2 || length(size(y)) > 2 | |
| 296 | + error('Only 2-D data accepted'); | |
| 297 | + end | |
| 298 | + | |
| 299 | + % If only one input, create index vector X | |
| 300 | + if isempty(x) | |
| 301 | + if ismember(1, size(y)) | |
| 302 | + x = reshape(1:numel(y), size(y)); | |
| 303 | + else | |
| 304 | + x = (1:size(y, 1))'; | |
| 305 | + end | |
| 306 | + end | |
| 307 | + | |
| 308 | + if ~ismember(1, size(x)) | |
| 309 | + error('First argument has to be a vector'); | |
| 310 | + end | |
| 311 | + if ~isequal(size(x, 1), size(y, 1)) && ~isequal(size(x, 2), size(y, 2)) | |
| 312 | + error('One of the dimensions of the two arguments must match'); | |
| 313 | + end | |
| 314 | + if any(diff(x) <= 0) | |
| 315 | + error('The first argument has to be a monotonically increasing vector'); | |
| 316 | + end | |
| 317 | + end | |
| 318 | + | |
| 319 | +end | |
| 0 | 320 | \ No newline at end of file |
pt100.m
res2temp16941.m
res2temp16943.m
res2temp16944.m
res2temp16945.m
res2temp16947.m
res2temp625.m
| ... | ... | @@ -0,0 +1,5 @@ |
| 1 | +function T = res2temp625(R) | |
| 2 | + | |
| 3 | +res2temp625 = @(R) 0.333548856582638109 + 11.7361551595386118 * (1000./R) + -31.32988932320903987 * (1000./R).^2 + 262.878643524833024 * (1000./R).^3 + -704.163538021035492 * (1000./R).^4 + 1056.6040485650301 * (1000./R).^5 + -307.057196729816496 * (1000./R).^6; | |
| 4 | + | |
| 5 | +T = res2temp625(R); |
res2temp627.m
| ... | ... | @@ -0,0 +1,5 @@ |
| 1 | +function T = res2temp627(R) | |
| 2 | + | |
| 3 | +res2temp627 = @(R) 0.399341181655472610 + 10.8420092277810909 * (1000./R) + -26.4597939187660813 * (1000./R).^2 + 245.9828566655493379 * (1000./R).^3 + -668.069876596331596 * (1000./R).^4 + 1001.69882618263364 * (1000./R) .^5 + -267.272089680656791 * (1000./R).^6; | |
| 4 | + | |
| 5 | +T = res2temp627(R); |
res2temp628.m
| ... | ... | @@ -0,0 +1,5 @@ |
| 1 | +function T = res2temp628(R) | |
| 2 | + | |
| 3 | +res2temp628 = @(R) 0.463200932294057566 + 13.5049710820894688 * (1000./R) + -30.5191222755238414 * (1000./R).^2 + 231.098593852017075* (1000./R).^3 + -550.122691885568202 * (1000./R).^4 + 806.038547554984689 * (1000./R).^5 + -198.510489917360246 * (1000./R).^6; | |
| 4 | + | |
| 5 | +T = res2temp628(R); |