Commit b197c3fdf011f9972e68675ac4ca59e0cd09ad67

Authored by bmarechal
0 parents
Exists in master

first commit

Showing 14 changed files with 2060 additions and 0 deletions Inline Diff

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