PageRenderTime 29ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/MatlabCode/branches/Greg's Branch/derivest.m

http://horwitzlab.googlecode.com/
MATLAB | 765 lines | 494 code | 20 blank | 251 comment | 37 complexity | 53b8b903ce69d613a113720690d9fe08 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0
  1. function [der,errest,finaldelta] = derivest(fun,x0,varargin)
  2. % DERIVEST: estimate the n'th derivative of fun at x0, provide an error estimate
  3. % usage: [der,errest] = DERIVEST(fun,x0) % first derivative
  4. % usage: [der,errest] = DERIVEST(fun,x0,prop1,val1,prop2,val2,...)
  5. %
  6. % Derivest will perform numerical differentiation of an
  7. % analytical function provided in fun. It will not
  8. % differentiate a function provided as data. Use gradient
  9. % for that purpose, or differentiate a spline model.
  10. %
  11. % The methods used by DERIVEST are finite difference
  12. % approximations of various orders, coupled with a generalized
  13. % (multiple term) Romberg extrapolation. This also yields
  14. % the error estimate provided. DERIVEST uses a semi-adaptive
  15. % scheme to provide the best estimate that it can by its
  16. % automatic choice of a differencing interval.
  17. %
  18. % Finally, While I have not written this function for the
  19. % absolute maximum speed, speed was a major consideration
  20. % in the algorithmic design. Maximum accuracy was my main goal.
  21. %
  22. %
  23. % Arguments (input)
  24. % fun - function to differentiate. May be an inline function,
  25. % anonymous, or an m-file. fun will be sampled at a set
  26. % of distinct points for each element of x0. If there are
  27. % additional parameters to be passed into fun, then use of
  28. % an anonymous function is recommended.
  29. %
  30. % fun should be vectorized to allow evaluation at multiple
  31. % locations at once. This will provide the best possible
  32. % speed. IF fun is not so vectorized, then you MUST set
  33. % 'vectorized' property to 'no', so that derivest will
  34. % then call your function sequentially instead.
  35. %
  36. % Fun is assumed to return a result of the same
  37. % shape as its input x0.
  38. %
  39. % x0 - scalar, vector, or array of points at which to
  40. % differentiate fun.
  41. %
  42. % Additional inputs must be in the form of property/value pairs.
  43. % Properties are character strings. They may be shortened
  44. % to the extent that they are unambiguous. Properties are
  45. % not case sensitive. Valid property names are:
  46. %
  47. % 'DerivativeOrder', 'MethodOrder', 'Style', 'RombergTerms'
  48. % 'FixedStep', 'MaxStep'
  49. %
  50. % All properties have default values, chosen as intelligently
  51. % as I could manage. Values that are character strings may
  52. % also be unambiguously shortened. The legal values for each
  53. % property are:
  54. %
  55. % 'DerivativeOrder' - specifies the derivative order estimated.
  56. % Must be a positive integer from the set [1,2,3,4].
  57. %
  58. % DEFAULT: 1 (first derivative of fun)
  59. %
  60. % 'MethodOrder' - specifies the order of the basic method
  61. % used for the estimation.
  62. %
  63. % For 'central' methods, must be a positive integer
  64. % from the set [2,4].
  65. %
  66. % For 'forward' or 'backward' difference methods,
  67. % must be a positive integer from the set [1,2,3,4].
  68. %
  69. % DEFAULT: 4 (a second order method)
  70. %
  71. % Note: higher order methods will generally be more
  72. % accurate, but may also suffere more from numerical
  73. % problems.
  74. %
  75. % Note: First order methods would usually not be
  76. % recommended.
  77. %
  78. % 'Style' - specifies the style of the basic method
  79. % used for the estimation. 'central', 'forward',
  80. % or 'backwards' difference methods are used.
  81. %
  82. % Must be one of 'Central', 'forward', 'backward'.
  83. %
  84. % DEFAULT: 'Central'
  85. %
  86. % Note: Central difference methods are usually the
  87. % most accurate, but sometiems one must not allow
  88. % evaluation in one direction or the other.
  89. %
  90. % 'RombergTerms' - Allows the user to specify the generalized
  91. % Romberg extrapolation method used, or turn it off
  92. % completely.
  93. %
  94. % Must be a positive integer from the set [0,1,2,3].
  95. %
  96. % DEFAULT: 2 (Two Romberg terms)
  97. %
  98. % Note: 0 disables the Romberg step completely.
  99. %
  100. % 'FixedStep' - Allows the specification of a fixed step
  101. % size, preventing the adaptive logic from working.
  102. % This will be considerably faster, but not necessarily
  103. % as accurate as allowing the adaptive logic to run.
  104. %
  105. % DEFAULT: []
  106. %
  107. % Note: If specified, 'FixedStep' will define the
  108. % maximum excursion from x0 that will be used.
  109. %
  110. % 'Vectorized' - Derivest will normally assume that your
  111. % function can be safely evaluated at multiple locations
  112. % in a single call. This would minimize the overhead of
  113. % a loop and additional function call overhead. Some
  114. % functions are not easily vectorizable, but you may
  115. % (if your matlab release is new enough) be able to use
  116. % arrayfun to accomplish the vectorization.
  117. %
  118. % When all else fails, set the 'vectorized' property
  119. % to 'no'. This will cause derivest to loop over the
  120. % successive function calls.
  121. %
  122. % DEFAULT: 'yes'
  123. %
  124. %
  125. % 'MaxStep' - Specifies the maximum excursion from x0 that
  126. % will be allowed, as a multiple of x0.
  127. %
  128. % DEFAULT: 100
  129. %
  130. % 'StepRatio' - Derivest uses a proportionally cascaded
  131. % series of function evaluations, moving away from your
  132. % point of evaluation. The StepRatio is the ratio used
  133. % between sequential steps.
  134. %
  135. % DEFAULT: 2
  136. %
  137. %
  138. % See the document DERIVEST.pdf for more explanation of the
  139. % algorithms behind the parameters of DERIVEST. In most cases,
  140. % I have chosen good values for these parameters, so the user
  141. % should never need to specify anything other than possibly
  142. % the DerivativeOrder. I've also tried to make my code robust
  143. % enough that it will not need much. But complete flexibility
  144. % is in there for your use.
  145. %
  146. %
  147. % Arguments: (output)
  148. % der - derivative estimate for each element of x0
  149. % der will have the same shape as x0.
  150. %
  151. % errest - 95% uncertainty estimate of the derivative, such that
  152. %
  153. % abs(der(j) - f'(x0(j))) < erest(j)
  154. %
  155. % finaldelta - The final overall stepsize chosen by DERIVEST
  156. %
  157. %
  158. % Example usage:
  159. % First derivative of exp(x), at x == 1
  160. % [d,e]=derivest(@(x) exp(x),1)
  161. % d =
  162. % 2.71828182845904
  163. %
  164. % e =
  165. % 1.02015503167879e-14
  166. %
  167. % True derivative
  168. % exp(1)
  169. % ans =
  170. % 2.71828182845905
  171. %
  172. % Example usage:
  173. % Third derivative of x.^3+x.^4, at x = [0,1]
  174. % derivest(@(x) x.^3 + x.^4,[0 1],'deriv',3)
  175. % ans =
  176. % 6 30
  177. %
  178. % True derivatives: [6,30]
  179. %
  180. %
  181. % See also: gradient
  182. %
  183. %
  184. % Author: John D'Errico
  185. % e-mail: woodchips@rochester.rr.com
  186. % Release: 1.0
  187. % Release date: 12/27/2006
  188. par.DerivativeOrder = 1;
  189. par.MethodOrder = 4;
  190. par.Style = 'central';
  191. par.RombergTerms = 2;
  192. par.FixedStep = [];
  193. par.MaxStep = 100;
  194. par.StepRatio = 2;
  195. par.NominalStep = [];
  196. par.Vectorized = 'yes';
  197. na = length(varargin);
  198. if (rem(na,2)==1)
  199. error 'Property/value pairs must come as PAIRS of arguments.'
  200. elseif na>0
  201. par = parse_pv_pairs(par,varargin);
  202. end
  203. par = check_params(par);
  204. % Was fun a string, or an inline/anonymous function?
  205. if (nargin<1)
  206. help derivest
  207. return
  208. elseif isempty(fun)
  209. error 'fun was not supplied.'
  210. elseif ischar(fun)
  211. % a character function name
  212. fun = str2func(fun);
  213. end
  214. % no default for x0
  215. if (nargin<2) || isempty(x0)
  216. error 'x0 was not supplied'
  217. end
  218. par.NominalStep = max(x0,0.02);
  219. % was a single point supplied?
  220. nx0 = size(x0);
  221. n = prod(nx0);
  222. % Set the steps to use.
  223. if isempty(par.FixedStep)
  224. % Basic sequence of steps, relative to a stepsize of 1.
  225. delta = par.MaxStep*par.StepRatio .^(0:-1:-25)';
  226. ndel = length(delta);
  227. else
  228. % Fixed, user supplied absolute sequence of steps.
  229. ndel = 3 + ceil(par.DerivativeOrder/2) + ...
  230. par.MethodOrder + par.RombergTerms;
  231. if par.Style(1) == 'c'
  232. ndel = ndel - 2;
  233. end
  234. delta = par.FixedStep*par.StepRatio .^(-(0:(ndel-1)))';
  235. end
  236. % generate finite differencing rule in advance.
  237. % The rule is for a nominal unit step size, and will
  238. % be scaled later to reflect the local step size.
  239. fdarule = 1;
  240. switch par.Style
  241. case 'central'
  242. % for central rules, we will reduce the load by an
  243. % even or odd transformation as appropriate.
  244. if par.MethodOrder==2
  245. switch par.DerivativeOrder
  246. case 1
  247. % the odd transformation did all the work
  248. fdarule = 1;
  249. case 2
  250. % the even transformation did all the work
  251. fdarule = 2;
  252. case 3
  253. % the odd transformation did most of the work, but
  254. % we need to kill off the linear term
  255. fdarule = [0 1]/fdamat(par.StepRatio,1,2);
  256. case 4
  257. % the even transformation did most of the work, but
  258. % we need to kill off the quadratic term
  259. fdarule = [0 1]/fdamat(par.StepRatio,2,2);
  260. end
  261. else
  262. % a 4th order method. We've already ruled out the 1st
  263. % order methods since these are central rules.
  264. switch par.DerivativeOrder
  265. case 1
  266. % the odd transformation did most of the work, but
  267. % we need to kill off the cubic term
  268. fdarule = [1 0]/fdamat(par.StepRatio,1,2);
  269. case 2
  270. % the even transformation did most of the work, but
  271. % we need to kill off the quartic term
  272. fdarule = [1 0]/fdamat(par.StepRatio,2,2);
  273. case 3
  274. % the odd transformation did much of the work, but
  275. % we need to kill off the linear & quintic terms
  276. fdarule = [0 1 0]/fdamat(par.StepRatio,1,3);
  277. case 4
  278. % the even transformation did much of the work, but
  279. % we need to kill off the quadratic and 6th order terms
  280. fdarule = [0 1 0]/fdamat(par.StepRatio,2,3);
  281. end
  282. end
  283. case {'forward' 'backward'}
  284. % These two cases are identical, except at the very end,
  285. % where a sign will be introduced.
  286. % No odd/even trans, but we already dropped
  287. % off the constant term
  288. if par.MethodOrder==1
  289. if par.DerivativeOrder==1
  290. % an easy one
  291. fdarule = 1;
  292. else
  293. % 2:4
  294. v = zeros(1,par.DerivativeOrder);
  295. v(par.DerivativeOrder) = 1;
  296. fdarule = v/fdamat(par.StepRatio,0,par.DerivativeOrder);
  297. end
  298. else
  299. % par.MethodOrder methods drop off the lower order terms,
  300. % plus terms directly above DerivativeOrder
  301. v = zeros(1,par.DerivativeOrder + par.MethodOrder - 1);
  302. v(par.DerivativeOrder) = 1;
  303. fdarule = v/fdamat(par.StepRatio,0,par.DerivativeOrder+par.MethodOrder-1);
  304. end
  305. % correct sign for the 'backward' rule
  306. if par.Style(1) == 'b'
  307. fdarule = -fdarule;
  308. end
  309. end % switch on par.style (generating fdarule)
  310. nfda = length(fdarule);
  311. % will we need fun(x0)?
  312. if (rem(par.DerivativeOrder,2) == 0) || ~strncmpi(par.Style,'central',7)
  313. if strcmpi(par.Vectorized,'yes')
  314. f_x0 = fun(x0);
  315. else
  316. % not vectorized, so loop
  317. f_x0 = zeros(size(x0));
  318. for j = 1:numel(x0)
  319. f_x0(j) = fun(x0(j));
  320. end
  321. end
  322. else
  323. f_x0 = [];
  324. end
  325. % Loop over the elements of x0, reducing it to
  326. % a scalar problem. Sorry, vectorization is not
  327. % complete here, but this IS only a single loop.
  328. der = zeros(nx0);
  329. errest = der;
  330. finaldelta = der;
  331. for i = 1:n
  332. x0i = x0(i);
  333. h = par.NominalStep(i);
  334. % a central, forward or backwards differencing rule?
  335. % f_del is the set of all the function evaluations we
  336. % will generate. For a central rule, it will have the
  337. % even or odd transformation built in.
  338. if par.Style(1) == 'c'
  339. % A central rule, so we will need to evaluate
  340. % symmetrically around x0i.
  341. if strcmpi(par.Vectorized,'yes')
  342. f_plusdel = fun(x0i+h*delta);
  343. f_minusdel = fun(x0i-h*delta);
  344. else
  345. % not vectorized, so loop
  346. f_minusdel = zeros(size(delta));
  347. f_plusdel = zeros(size(delta));
  348. for j = 1:numel(delta)
  349. f_plusdel(j) = fun(x0i+h*delta(j));
  350. f_minusdel(j) = fun(x0i-h*delta(j));
  351. end
  352. end
  353. if ismember(par.DerivativeOrder,[1 3])
  354. % odd transformation
  355. f_del = (f_plusdel - f_minusdel)/2;
  356. else
  357. f_del = (f_plusdel + f_minusdel)/2 - f_x0(i);
  358. end
  359. elseif par.Style(1) == 'f'
  360. % forward rule
  361. % drop off the constant only
  362. if strcmpi(par.Vectorized,'yes')
  363. f_del = fun(x0i+h*delta) - f_x0(i);
  364. else
  365. % not vectorized, so loop
  366. f_del = zeros(size(delta));
  367. for j = 1:numel(delta)
  368. f_del(j) = fun(x0i+h*delta(j)) - f_x0(i);
  369. end
  370. end
  371. else
  372. % backward rule
  373. % drop off the constant only
  374. if strcmpi(par.Vectorized,'yes')
  375. f_del = fun(x0i-h*delta) - f_x0(i);
  376. else
  377. % not vectorized, so loop
  378. f_del = zeros(size(delta));
  379. for j = 1:numel(delta)
  380. f_del(j) = fun(x0i-h*delta(j)) - f_x0(i);
  381. end
  382. end
  383. end
  384. % check the size of f_del to ensure it was properly vectorized.
  385. f_del = f_del(:);
  386. if length(f_del)~=ndel
  387. error 'fun did not return the correct size result (fun must be vectorized)'
  388. end
  389. % Apply the finite difference rule at each delta, scaling
  390. % as appropriate for delta and the requested DerivativeOrder.
  391. % First, decide how many of these estimates we will end up with.
  392. ne = ndel + 1 - nfda - par.RombergTerms;
  393. % Form the initial derivative estimates from the chosen
  394. % finite difference method.
  395. der_init = vec2mat(f_del,ne,nfda)*fdarule.';
  396. % scale to reflect the local delta
  397. der_init = der_init(:)./(h*delta(1:ne)).^par.DerivativeOrder;
  398. % Each approximation that results is an approximation
  399. % of order par.DerivativeOrder to the desired derivative.
  400. % Additional (higher order, even or odd) terms in the
  401. % Taylor series also remain. Use a generalized (multi-term)
  402. % Romberg extrapolation to improve these estimates.
  403. switch par.Style
  404. case 'central'
  405. rombexpon = 2*(1:par.RombergTerms) + par.MethodOrder - 2;
  406. otherwise
  407. rombexpon = (1:par.RombergTerms) + par.MethodOrder - 1;
  408. end
  409. [der_romb,errors] = rombextrap(par.StepRatio,der_init,rombexpon);
  410. % Choose which result to return
  411. % first, trim off the
  412. if isempty(par.FixedStep)
  413. % trim off the estimates at each end of the scale
  414. nest = length(der_romb);
  415. switch par.DerivativeOrder
  416. case {1 2}
  417. trim = [1 2 nest-1 nest];
  418. case 3
  419. trim = [1:4 nest+(-3:0)];
  420. case 4
  421. trim = [1:6 nest+(-5:0)];
  422. end
  423. [der_romb,tags] = sort(der_romb);
  424. der_romb(trim) = [];
  425. tags(trim) = [];
  426. errors = errors(tags);
  427. trimdelta = delta(tags);
  428. [errest(i),ind] = min(errors);
  429. finaldelta(i) = h*trimdelta(ind);
  430. der(i) = der_romb(ind);
  431. else
  432. [errest(i),ind] = min(errors);
  433. finaldelta(i) = h*delta(ind);
  434. der(i) = der_romb(ind);
  435. end
  436. end
  437. end % mainline end
  438. % ============================================
  439. % subfunction - romberg extrapolation
  440. % ============================================
  441. function [der_romb,errest] = rombextrap(StepRatio,der_init,rombexpon)
  442. % do romberg extrapolation for each estimate
  443. %
  444. % StepRatio - Ratio decrease in step
  445. % der_init - initial derivative estimates
  446. % rombexpon - higher order terms to cancel using the romberg step
  447. %
  448. % der_romb - derivative estimates returned
  449. % errest - error estimates
  450. % amp - noise amplification factor due to the romberg step
  451. srinv = 1/StepRatio;
  452. % do nothing if no romberg terms
  453. nexpon = length(rombexpon);
  454. rmat = ones(nexpon+2,nexpon+1);
  455. switch nexpon
  456. case 0
  457. % rmat is simple: ones(2,1)
  458. case 1
  459. % only one romberg term
  460. rmat(2,2) = srinv^rombexpon;
  461. rmat(3,2) = srinv^(2*rombexpon);
  462. case 2
  463. % two romberg terms
  464. rmat(2,2:3) = srinv.^rombexpon;
  465. rmat(3,2:3) = srinv.^(2*rombexpon);
  466. rmat(4,2:3) = srinv.^(3*rombexpon);
  467. case 3
  468. % three romberg terms
  469. rmat(2,2:4) = srinv.^rombexpon;
  470. rmat(3,2:4) = srinv.^(2*rombexpon);
  471. rmat(4,2:4) = srinv.^(3*rombexpon);
  472. rmat(5,2:4) = srinv.^(4*rombexpon);
  473. end
  474. % qr factorization used for the extrapolation as well
  475. % as the uncertainty estimates
  476. [qromb,rromb] = qr(rmat,0);
  477. % the noise amplification is further amplified by the Romberg step.
  478. % amp = cond(rromb);
  479. % this does the extrapolation to a zero step size.
  480. ne = length(der_init);
  481. rhs = vec2mat(der_init,nexpon+2,max(1,ne - (nexpon+2)));
  482. rombcoefs = rromb\(qromb.'*rhs);
  483. der_romb = rombcoefs(1,:).';
  484. % uncertainty estimate of derivative prediction
  485. s = sqrt(sum((rhs - rmat*rombcoefs).^2,1));
  486. rinv = rromb\eye(nexpon+1);
  487. cov1 = sum(rinv.^2,2); % 1 spare dof
  488. errest = s.'*12.7062047361747*sqrt(cov1(1));
  489. end % rombextrap
  490. % ============================================
  491. % subfunction - vec2mat
  492. % ============================================
  493. function mat = vec2mat(vec,n,m)
  494. % forms the matrix M, such that M(i,j) = vec(i+j-1)
  495. [i,j] = ndgrid(1:n,0:m-1);
  496. ind = i+j;
  497. mat = vec(ind);
  498. if n==1
  499. mat = mat.';
  500. end
  501. end % vec2mat
  502. % ============================================
  503. % subfunction - fdamat
  504. % ============================================
  505. function mat = fdamat(sr,parity,nterms)
  506. % Compute matrix for fda derivation.
  507. % parity can be
  508. % 0 (one sided, all terms included but zeroth order)
  509. % 1 (only odd terms included)
  510. % 2 (only even terms included)
  511. % nterms - number of terms
  512. % sr is the ratio between successive steps
  513. srinv = 1./sr;
  514. switch parity
  515. case 0
  516. % single sided rule
  517. [i,j] = ndgrid(1:nterms);
  518. c = 1./factorial(1:nterms);
  519. mat = c(j).*srinv.^((i-1).*j);
  520. case 1
  521. % odd order derivative
  522. [i,j] = ndgrid(1:nterms);
  523. c = 1./factorial(1:2:(2*nterms));
  524. mat = c(j).*srinv.^((i-1).*(2*j-1));
  525. case 2
  526. % even order derivative
  527. [i,j] = ndgrid(1:nterms);
  528. c = 1./factorial(2:2:(2*nterms));
  529. mat = c(j).*srinv.^((i-1).*(2*j));
  530. end
  531. end % fdamat
  532. % ============================================
  533. % subfunction - check_params
  534. % ============================================
  535. function par = check_params(par)
  536. % check the parameters for acceptability
  537. %
  538. % Defaults
  539. % par.DerivativeOrder = 1;
  540. % par.MethodOrder = 2;
  541. % par.Style = 'central';
  542. % par.RombergTerms = 2;
  543. % par.FixedStep = [];
  544. % DerivativeOrder == 1 by default
  545. if isempty(par.DerivativeOrder)
  546. par.DerivativeOrder = 1;
  547. else
  548. if (length(par.DerivativeOrder)>1) || ~ismember(par.DerivativeOrder,1:4)
  549. error 'DerivativeOrder must be scalar, one of [1 2 3 4].'
  550. end
  551. end
  552. % MethodOrder == 2 by default
  553. if isempty(par.MethodOrder)
  554. par.MethodOrder = 2;
  555. else
  556. if (length(par.MethodOrder)>1) || ~ismember(par.MethodOrder,[1 2 3 4])
  557. error 'MethodOrder must be scalar, one of [1 2 3 4].'
  558. elseif ismember(par.MethodOrder,[1 3]) && (par.Style(1)=='c')
  559. error 'MethodOrder==1 or 3 is not possible with central difference methods'
  560. end
  561. end
  562. % style is char
  563. valid = {'central', 'forward', 'backward'};
  564. if isempty(par.Style)
  565. par.Style = 'central';
  566. elseif ~ischar(par.Style)
  567. error 'Invalid Style: Must be character'
  568. end
  569. ind = find(strncmpi(par.Style,valid,length(par.Style)));
  570. if (length(ind)==1)
  571. par.Style = valid{ind};
  572. else
  573. error(['Invalid Style: ',par.Style])
  574. end
  575. % vectorized is char
  576. valid = {'yes', 'no'};
  577. if isempty(par.Vectorized)
  578. par.Vectorized = 'yes';
  579. elseif ~ischar(par.Vectorized)
  580. error 'Invalid Vectorized: Must be character'
  581. end
  582. ind = find(strncmpi(par.Vectorized,valid,length(par.Vectorized)));
  583. if (length(ind)==1)
  584. par.Vectorized = valid{ind};
  585. else
  586. error(['Invalid Vectorized: ',par.Vectorized])
  587. end
  588. % RombergTerms == 2 by default
  589. if isempty(par.RombergTerms)
  590. par.RombergTerms = 2;
  591. else
  592. if (length(par.RombergTerms)>1) || ~ismember(par.RombergTerms,0:3)
  593. error 'RombergTerms must be scalar, one of [0 1 2 3].'
  594. end
  595. end
  596. % FixedStep == [] by default
  597. if (length(par.FixedStep)>1) || (~isempty(par.FixedStep) && (par.FixedStep<=0))
  598. error 'FixedStep must be empty or a scalar, >0.'
  599. end
  600. % MaxStep == 10 by default
  601. if isempty(par.MaxStep)
  602. par.MaxStep = 10;
  603. elseif (length(par.MaxStep)>1) || (par.MaxStep<=0)
  604. error 'MaxStep must be empty or a scalar, >0.'
  605. end
  606. end % check_params
  607. % ============================================
  608. % Included subfunction - parse_pv_pairs
  609. % ============================================
  610. function params=parse_pv_pairs(params,pv_pairs)
  611. % parse_pv_pairs: parses sets of property value pairs, allows defaults
  612. % usage: params=parse_pv_pairs(default_params,pv_pairs)
  613. %
  614. % arguments: (input)
  615. % default_params - structure, with one field for every potential
  616. % property/value pair. Each field will contain the default
  617. % value for that property. If no default is supplied for a
  618. % given property, then that field must be empty.
  619. %
  620. % pv_array - cell array of property/value pairs.
  621. % Case is ignored when comparing properties to the list
  622. % of field names. Also, any unambiguous shortening of a
  623. % field/property name is allowed.
  624. %
  625. % arguments: (output)
  626. % params - parameter struct that reflects any updated property/value
  627. % pairs in the pv_array.
  628. %
  629. % Example usage:
  630. % First, set default values for the parameters. Assume we
  631. % have four parameters that we wish to use optionally in
  632. % the function examplefun.
  633. %
  634. % - 'viscosity', which will have a default value of 1
  635. % - 'volume', which will default to 1
  636. % - 'pie' - which will have default value 3.141592653589793
  637. % - 'description' - a text field, left empty by default
  638. %
  639. % The first argument to examplefun is one which will always be
  640. % supplied.
  641. %
  642. % function examplefun(dummyarg1,varargin)
  643. % params.Viscosity = 1;
  644. % params.Volume = 1;
  645. % params.Pie = 3.141592653589793
  646. %
  647. % params.Description = '';
  648. % params=parse_pv_pairs(params,varargin);
  649. % params
  650. %
  651. % Use examplefun, overriding the defaults for 'pie', 'viscosity'
  652. % and 'description'. The 'volume' parameter is left at its default.
  653. %
  654. % examplefun(rand(10),'vis',10,'pie',3,'Description','Hello world')
  655. %
  656. % params =
  657. % Viscosity: 10
  658. % Volume: 1
  659. % Pie: 3
  660. % Description: 'Hello world'
  661. %
  662. % Note that capitalization was ignored, and the property 'viscosity'
  663. % was truncated as supplied. Also note that the order the pairs were
  664. % supplied was arbitrary.
  665. npv = length(pv_pairs);
  666. n = npv/2;
  667. if n~=floor(n)
  668. error 'Property/value pairs must come in PAIRS.'
  669. end
  670. if n<=0
  671. % just return the defaults
  672. return
  673. end
  674. if ~isstruct(params)
  675. error 'No structure for defaults was supplied'
  676. end
  677. % there was at least one pv pair. process any supplied
  678. propnames = fieldnames(params);
  679. lpropnames = lower(propnames);
  680. for i=1:n
  681. p_i = lower(pv_pairs{2*i-1});
  682. v_i = pv_pairs{2*i};
  683. ind = strmatch(p_i,lpropnames,'exact');
  684. if isempty(ind)
  685. ind = find(strncmp(p_i,lpropnames,length(p_i)));
  686. if isempty(ind)
  687. error(['No matching property found for: ',pv_pairs{2*i-1}])
  688. elseif length(ind)>1
  689. error(['Ambiguous property name: ',pv_pairs{2*i-1}])
  690. end
  691. end
  692. p_i = propnames{ind};
  693. % override the corresponding default in params
  694. params = setfield(params,p_i,v_i); %#ok
  695. end
  696. end % parse_pv_pairs