Commit b197c3fdf011f9972e68675ac4ca59e0cd09ad67

Authored by bmarechal
0 parents
Exists in master

first commit

Showing 14 changed files with 2060 additions and 0 deletions Side-by-side Diff

... ... @@ -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
... ... @@ -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
... ... @@ -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
... ... @@ -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
... ... @@ -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
... ... @@ -0,0 +1,5 @@
  1 +function T = pt100(R)
  2 +
  3 +pt100 = @(R) (R/100.-1)/0.003850+273.15;
  4 +
  5 +T = pt100(R);
... ... @@ -0,0 +1,5 @@
  1 +function T = res2temp16941(R)
  2 +
  3 +res2temp16941 = @(R) 10.^(2.9486 * (log10(1000./R)).^2 + 4.5862 * log10(1000./R) + 2.266);
  4 +
  5 +T = res2temp16941(R);
... ... @@ -0,0 +1,5 @@
  1 +function T = res2temp16943(R)
  2 +
  3 +res2temp16943 = @(R) 10.^(3.4738 * (log10(1000./R)).^2 + 5.1198 * log10(1000./R) + 2.3681);
  4 +
  5 +T = res2temp16943(R);
... ... @@ -0,0 +1,5 @@
  1 +function T = res2temp16944(R)
  2 +
  3 +res2temp16944 = @(R) 10.^(3.3674 * (log10(1000./R)).^2 + 5.2874 * log10(1000./R) + 2.5165);
  4 +
  5 +T = res2temp16944(R);
... ... @@ -0,0 +1,5 @@
  1 +function T = res2temp16945(R)
  2 +
  3 +res2temp16945 = @(R) 10.^(3.2497 * (log10(1000./R)).^2 + 5.1777 * log10(1000./R) + 2.499);
  4 +
  5 +T = res2temp16945(R);
... ... @@ -0,0 +1,5 @@
  1 +function T = res2temp16947(R)
  2 +
  3 +res2temp16947 = @(R) 10.^(3.4597 * (log10(1000./R)).^2 + 5.2422 * log10(1000./R) + 2.4169);
  4 +
  5 +T = res2temp16947(R);
... ... @@ -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);
... ... @@ -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);
... ... @@ -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);