PageRenderTime 55ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Demeter/Fit/Sanity.pm

https://bitbucket.org/bruceravel/demeter
Perl | 1092 lines | 914 code | 125 blank | 53 comment | 139 complexity | f8c40670e48a899502f72b9cb95c7df9 MD5 | raw file
Possible License(s): LGPL-2.0, GPL-2.0
  1. package Demeter::Fit::Sanity;
  2. =for Copyright
  3. .
  4. Copyright (c) 2006-2017 Bruce Ravel (http://bruceravel.github.io/home).
  5. All rights reserved.
  6. .
  7. This file is free software; you can redistribute it and/or
  8. modify it under the same terms as Perl itself. See The Perl
  9. Artistic License.
  10. .
  11. This program is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  14. =cut
  15. use Moose::Role;
  16. use Demeter::StrTypes qw( IfeffitFunction IfeffitProgramVar );
  17. use Carp;
  18. use File::Spec;
  19. use List::MoreUtils qw(any true);
  20. use Graph;
  21. use Regexp::Assemble;
  22. use Demeter::Constants qw($NUMBER $NULLFILE);
  23. use Text::Wrap;
  24. $Text::Wrap::columns = 65;
  25. sub S_data_files_exist {
  26. my ($self) = @_;
  27. my $found = 0;
  28. my @data = @{ $self->data };
  29. foreach my $d (@data) {
  30. next if $d->from_athena;
  31. next if $d->from_yaml;
  32. next if not $d->fit_include;
  33. return 0 if ($d->file eq $NULLFILE);
  34. my $file = $d->file;
  35. if (not -e $file) {
  36. ++$found;
  37. $d->add_trouble('-e');
  38. } elsif (not -r $file) {
  39. ++$found;
  40. $d->add_trouble('-r');
  41. };
  42. };
  43. return $found;
  44. };
  45. sub S_feff_files_exist {
  46. my ($self) = @_;
  47. my $found = 0;
  48. my @paths = @{ $self->paths };
  49. foreach my $p (@paths) {
  50. next if not $p->include;
  51. next if not $p->data->fit_include;
  52. my ($pathto, $nnnn) = $p->get(qw(folder file));
  53. my $file = File::Spec->catfile($pathto, $nnnn);
  54. if (not -e $file) {
  55. ++$found;
  56. $p->add_trouble('-e');
  57. } elsif (not -r $file) {
  58. ++$found;
  59. $p->add_trouble('-r');
  60. };
  61. };
  62. return $found;
  63. };
  64. sub S_sp_exist {
  65. my ($self) = @_;
  66. my $found = 0;
  67. my @paths = @{ $self->paths };
  68. foreach my $p (@paths) {
  69. next if not $p->include;
  70. next if not $p->data->fit_include;
  71. ## could be reading a feffNNNN.dat file directly
  72. next if (($p->file !~ m{\A\s+\z}) and (not -e $p->file));
  73. if (ref($p->sp) !~ m{Path}) {
  74. ++$found;
  75. $p->add_trouble('spnotexist');
  76. };
  77. };
  78. return $found;
  79. };
  80. ## 1. check that all guesses are used in defs and pathparams
  81. sub S_defined_not_used {
  82. my ($self) = @_;
  83. my $found = 0;
  84. my @gds = @{ $self->gds };
  85. my @paths = @{ $self->paths };
  86. foreach my $g (@gds) {
  87. my $name = lc($g->name);
  88. my $thisfound = 0;
  89. next if ($g->gds ne 'guess');
  90. foreach my $d (@gds) {
  91. next if ($d->gds !~ m{(?:def|restrain)});
  92. ++$thisfound if (lc($d->mathexp) =~ /\b$name\b/);
  93. last if $thisfound;
  94. };
  95. foreach my $p (@paths) {
  96. next if (ref($p) !~ m{Path});
  97. next if not $p->include;
  98. next if not $p->data->fit_include;
  99. last if $thisfound;
  100. foreach my $pp (qw(s02 e0 delr sigma2 ei third fourth dphase)) {
  101. ++$thisfound if (lc($p->$pp) =~ /\b$name\b/);
  102. last if $thisfound;
  103. };
  104. };
  105. if (not $thisfound) {
  106. ++$found;
  107. $g->trouble('notused');
  108. };
  109. };
  110. return $found;
  111. };
  112. ## 2. check that defs and path paramers do not use undefined GDS parameters
  113. sub S_used_not_defined {
  114. my ($self) = @_;
  115. my $found = 0;
  116. my @gds = @{ $self->gds };
  117. my @paths = @{ $self->paths };
  118. my @all_params = ();
  119. foreach my $g (@gds) {
  120. next if ($g->gds =~ m{(?:merge|skip)});
  121. push @all_params, lc($g->name);
  122. };
  123. my $params_regexp = Regexp::Assemble->new()->add(@all_params)->re;
  124. my $tokenizer_regexp = '(?-xism:(?=[\t\ \(\)\*\+\,\-\/\^])[\-\+\*\^\/\(\)\,\ \t])';
  125. ## this came from:
  126. # use Regexp::List;
  127. # my $opt = Regexp::List->new;
  128. # print $opt->list2re('-', '+', '*', '^', '/', '(', ')', ',', " ", "\t"), $/;
  129. foreach my $g (@gds) {
  130. next if ($g->gds =~ m{(?:guess|merge|skip)});
  131. my $mathexp = $g->mathexp;
  132. my @list = split(/$tokenizer_regexp+/, $mathexp);
  133. foreach my $token (@list) {
  134. #print $mathexp, " ", $token, $/;
  135. next if ($token =~ m{\A\s*\z}); # space, ok
  136. next if ($token =~ m{\A$NUMBER\z}); # number, ok
  137. next if (is_IfeffitFunction($token)); # function, ok
  138. next if (lc($token) =~ m{\A(?:etok|pi)\z}); # Ifeffit's defined constants, ok
  139. next if (lc($token) =~ m{\A$params_regexp\z}); # defined param, ok
  140. next if (lc($token) eq 'reff'); # reff, ok
  141. if (lc($token) =~ m{\[?cv\]?}) {
  142. ++$found;
  143. $g->add_trouble('usecv');
  144. } else {
  145. ++$found;
  146. $g->add_trouble('useundef');
  147. };
  148. };
  149. };
  150. foreach my $p (@paths) {
  151. next if not defined($p);
  152. next if not $p->include;
  153. next if not $p->data->fit_include;
  154. my $label = $p->name;
  155. foreach my $pp (qw(s02 e0 delr sigma2 ei third fourth dphase)) {
  156. my @list = split(/$tokenizer_regexp+/, $p->$pp);
  157. foreach my $token (@list) {
  158. next if ($token =~ m{\A\s*\z}); # space, ok
  159. next if ($token =~ m{\A$NUMBER\z}); # number, ok
  160. next if (is_IfeffitFunction($token)); # function, ok
  161. next if (lc($token) =~ m{\A(?:etok|pi)\z}); # Ifeffit's defined constants, ok
  162. next if (lc($token) =~ m{\A$params_regexp\z}); # defined param, ok
  163. next if (lc($token) eq 'reff'); # reff, ok
  164. next if (lc($token) =~ m{\[?cv\]?}); # cv, ok
  165. ++$found;
  166. # "The math expression for $pp for \"$label\" uses an undefined token: $token"
  167. # );
  168. $p->add_trouble(join('~', 'useundef', $pp, $token));
  169. };
  170. };
  171. };
  172. return $found;
  173. };
  174. ## 3. check that ++ -- // *** ^^ do not appear in math expression
  175. sub S_binary_ops {
  176. my ($self) = @_;
  177. my $found = 0;
  178. my @gds = @{ $self->gds };
  179. my @paths = @{ $self->paths };
  180. my $bad_binary_op_regexp = '(?-xism:(?=[\*\+\-\/\^])(?:\+\+|\-\-|\*\*\*|\/\/|\^\^))';
  181. ##Regexp::Assemble->new()->add('++', '--', '***', '//', '^^')->re;
  182. foreach my $g (@gds) {
  183. next if ($g->gds =~ m{(?:merge|skip)});
  184. my $mathexp = $g->mathexp;
  185. if ($mathexp =~ m{($bad_binary_op_regexp)}) {
  186. my $which = $1;
  187. ++$found;
  188. # "The math expression for \"" . $g->name . "\" uses an invalid binary operation: $which"
  189. # );
  190. $g->add_trouble("binary_x_$which");
  191. };
  192. };
  193. foreach my $p (@paths) {
  194. next if not defined($p);
  195. next if not $p->include;
  196. next if not $p->data->fit_include;
  197. my $label = $p->name;
  198. foreach my $pp (qw(s02 e0 delr sigma2 ei third fourth dphase)) {
  199. my $mathexp = $p->$pp;
  200. if ($mathexp =~ m{($bad_binary_op_regexp)}) {
  201. my $which = $1;
  202. ++$found;
  203. # "The math expression for $pp for \"$label\" uses an invalid binary operation: $which"
  204. # );
  205. $p->add_trouble(join('~', 'binary', $pp, $which));
  206. };
  207. };
  208. };
  209. return $found;
  210. };
  211. ## 4. check that all function() names are valid in math expressions
  212. sub S_function_names {
  213. my ($self) = @_;
  214. my $found = 0;
  215. my @gds = @{ $self->gds };
  216. my @paths = @{ $self->paths };
  217. foreach my $g (@gds) {
  218. next if ($g->gds =~ m{(?:merge|skip)});
  219. if ($g->mathexp =~ m{(\b\w+)\s*\(}) {
  220. my $match = $1;
  221. if (not is_IfeffitFunction($match)) {
  222. ++$found;
  223. $g->add_trouble("function_$match");
  224. };
  225. };
  226. };
  227. foreach my $p (@paths) {
  228. next if not defined($p);
  229. next if not $p->include;
  230. next if not $p->data->fit_include;
  231. foreach my $pp (qw(s02 e0 delr sigma2 ei third fourth dphase)) {
  232. my $mathexp = $p->$pp;
  233. if ($mathexp =~ m{(\b\w+)\s*\(}) {
  234. my $match = $1;
  235. if (not is_IfeffitFunction($match)) {
  236. ++$found;
  237. $p->add_trouble(join('~', 'function', $pp, $match));
  238. };
  239. };
  240. };
  241. };
  242. return $found;
  243. };
  244. ## 5. check that all data have unique group names
  245. ## 6. check that all paths have unique group names
  246. sub S_unique_group_names {
  247. my ($self) = @_;
  248. my $found = 0;
  249. my @data = @{ $self->data };
  250. my @paths = @{ $self->paths };
  251. # check data group names
  252. my %dseen = ();
  253. my %tag_seen = ();
  254. my %cv_seen = ();
  255. foreach my $d (@data) {
  256. next if not $d->fit_include;
  257. ++$dseen{$d->group};
  258. $d->add_trouble('namenotunique') if ($dseen{$d->group} > 1);
  259. ++$tag_seen{$d->tag};
  260. $d->add_trouble('tagnotunique') if ($tag_seen{$d->tag} > 1);
  261. ++$cv_seen{$d->cv};
  262. $d->add_trouble('cvnotunique') if ($cv_seen{$d->cv} > 1);
  263. };
  264. foreach my $s (keys %dseen) {
  265. if ($dseen{$s} > 1) {
  266. ++$found;
  267. };
  268. };
  269. foreach my $s (keys %tag_seen) {
  270. if ($tag_seen{$s} > 1) {
  271. ++$found;
  272. };
  273. };
  274. ## foreach my $s (keys %cv_seen) {
  275. ## if ($cv_seen{$s} > 1) {
  276. ## ++$found;
  277. ## };
  278. ## };
  279. # check path group names
  280. my %pseen = ();
  281. foreach my $p (@paths) {
  282. next if not defined($p);
  283. next if not $p->include;
  284. next if not $p->data->fit_include;
  285. ++$pseen{$p->group};
  286. $p->add_trouble('namenotunique') if ($pseen{$p->group} > 1);
  287. };
  288. foreach my $s (keys %pseen) {
  289. if ($pseen{$s} > 1) {
  290. ++$found;
  291. };
  292. };
  293. # cross check data and path group names
  294. my %seen = ();
  295. foreach my $p (@data, @paths) {
  296. next if not defined($p);
  297. ++$seen{$p->group};
  298. $p->add_trouble('pathdataname') if ($seen{$p->group} > 1);
  299. };
  300. foreach my $s (keys %seen) {
  301. if ($seen{$s} > 1 and $pseen{$s} and $pseen{$s} < 2 and $dseen{$s} < 2) {
  302. ++$found;
  303. };
  304. };
  305. return $found;
  306. };
  307. ## 7. check that all GDS have unique names
  308. sub S_gds_unique_names {
  309. my ($self) = @_;
  310. my $found = 0;
  311. my @gds = @{ $self->gds };
  312. my %seen = ();
  313. foreach my $g (@gds) {
  314. next if ($g->gds eq 'skip');
  315. ++$seen{lc($g->name)};
  316. $g->add_trouble('notunique') if ($seen{lc($g->name)} > 1);
  317. };
  318. foreach my $s (keys %seen) {
  319. if ($seen{$s} > 1) {
  320. ++$found;
  321. };
  322. };
  323. return $found;
  324. };
  325. ## 8. check that parens match
  326. sub S_parens_not_match {
  327. my ($self) = @_;
  328. my $found = 0;
  329. my @gds = @{ $self->gds };
  330. my @paths = @{ $self->paths };
  331. foreach my $g (@gds) {
  332. next if ($g->gds =~ m{(?:merge|skip)});
  333. my $not_ok = $self->check_parens($g->mathexp);
  334. if ($not_ok) {
  335. ++$found;
  336. $g->add_trouble('parens');
  337. };
  338. };
  339. foreach my $p (@paths) {
  340. next if not defined($p);
  341. next if not $p->include;
  342. next if not $p->data->fit_include;
  343. foreach my $pp (qw(s02 e0 delr sigma2 ei third fourth dphase)) {
  344. my $mathexp = $p->$pp;
  345. my $not_ok = $self->check_parens($mathexp);
  346. if ($not_ok) {
  347. ++$found;
  348. $p->add_trouble("parens_".$pp);
  349. };
  350. };
  351. };
  352. return $found;
  353. };
  354. ## 9. check that data params make sense
  355. sub S_data_parameters {
  356. my ($self) = @_;
  357. my $found = 0;
  358. my @data = @{ $self->data };
  359. foreach my $d (@data) {
  360. next if (not $d->fit_include);
  361. my ($kmin, $kmax) = $d->get(qw(fft_kmin fft_kmax));
  362. if ($kmin >= $kmax) {
  363. ++$found;
  364. $d->add_trouble('kminkmax');
  365. };
  366. my ($rmin, $rmax) = $d->get(qw(bft_rmin bft_rmax));
  367. if ($rmin >= $rmax) {
  368. ++$found;
  369. $d->add_trouble('rminrmax');
  370. };
  371. };
  372. return $found;
  373. };
  374. ## 10. check that number of guesses does not exceed Nidp
  375. sub S_nidp {
  376. my ($self) = @_;
  377. my $found = 0;
  378. my @gds = @{ $self->gds };
  379. my @data = @{ $self->data };
  380. my ($nidp, $ndata) = (0,0);
  381. foreach my $d (@data) {
  382. next if (not $d->fit_include);
  383. ++$ndata;
  384. $nidp += $d->nidp;
  385. };
  386. my $nguess = 0;
  387. foreach my $g (@gds) {
  388. ++$nguess if ($g->gds eq 'guess');
  389. };
  390. if ($nguess > $nidp) {
  391. ++$found;
  392. $self->add_trouble('nvarnidp');
  393. };
  394. return $found;
  395. };
  396. ## 11. check that rmin is not greater than rbkg
  397. sub S_rmin_rbkg {
  398. my ($self) = @_;
  399. my $found = 0;
  400. my @data = @{ $self->data };
  401. foreach my $d (@data) {
  402. next if ($d->datatype eq 'chi');
  403. next if (not $d->fit_include);
  404. if ($d->bft_rmin < $d->bkg_rbkg) {
  405. ++$found;
  406. $d->add_trouble('rminrbkg');
  407. };
  408. };
  409. return $found;
  410. };
  411. ## 12. check that reff is not far beyond Rmax for any path
  412. sub S_reff_rmax {
  413. my ($self) = @_;
  414. my $found = 0;
  415. my @data = @{ $self->data };
  416. my @paths = @{ $self->paths };
  417. foreach my $d (@data) {
  418. next if (not $d->fit_include);
  419. foreach my $p (@paths) {
  420. next if not defined($p);
  421. next if (not $p->include);
  422. next if ($p->data ne $d);
  423. if ($p->reff > (0.2+$d->bft_rmax*$self->co->default('warnings', 'reff_margin'))) {
  424. my $identify = $p->name || $p->Index;
  425. ++$found;
  426. $p->add_trouble('reffrmax');
  427. };
  428. };
  429. };
  430. return $found;
  431. };
  432. # &max_scalars = 65536.000000000
  433. # &max_arrays = 8192.000000000
  434. # &max_strings = 8192.000000000
  435. # &max_paths = 1024.000000000
  436. # &max_varys = 128.000000000
  437. # &max_data_sets = 16.000000000
  438. # spline knots = 32
  439. # restraints = 10
  440. ## 13. check that ifeffit hardwired limits are not exceeded
  441. sub S_exceed_ifeffit_limits {
  442. my ($self) = @_;
  443. my $found = 0;
  444. return 0 if Demeter->is_larch;
  445. my @gds = @{ $self->gds };
  446. my @data = @{ $self->data };
  447. my @paths = @{ $self->paths };
  448. ## number of guess params
  449. my $n_guess = 0;
  450. my $n_params = 0;
  451. my $n_restraint = 0;
  452. foreach my $g (@gds) {
  453. ++$n_guess if ($g->gds eq 'guess');
  454. ++$n_params if ($g->gds !~ m{(?:merge|skip)});
  455. ++$n_restraint if ($g->gds eq 'restrain');
  456. };
  457. if ($n_guess > $self->fetch_scalar('&max_varys')) {
  458. ++$found;
  459. $self->add_trouble('nvarys');
  460. };
  461. if ($n_params > $self->fetch_scalar('&max_scalars')) {
  462. ++$found;
  463. $self->add_trouble('nparams');
  464. };
  465. if ($n_restraint > 10) {
  466. ++$found;
  467. $self->add_trouble('nrestraints');
  468. };
  469. ## number of data sets
  470. my $n_data = 0;
  471. foreach my $d (@data) {
  472. ++$n_data if ($d->fit_include);
  473. };
  474. if ($n_data > $self->fetch_scalar('&max_data_sets')) {
  475. ++$found;
  476. $self->add_trouble('ndatasets');
  477. };
  478. ## number of paths
  479. my $n_paths = 0;
  480. foreach my $p (@paths) {
  481. next if not defined($p);
  482. next if not $p->include;
  483. next if not $p->data->fit_include;
  484. ++$n_paths if ($p->include);
  485. };
  486. if ($n_paths > $self->fetch_scalar('&max_paths')) {
  487. ++$found;
  488. $self->add_trouble('npaths');
  489. };
  490. return $found;
  491. };
  492. ## 14. check that parameters do not have program variable names
  493. sub S_program_var_names {
  494. my ($self) = @_;
  495. my $found = 0;
  496. my @gds = @{ $self->gds };
  497. foreach my $g (@gds) {
  498. if (is_IfeffitProgramVar(lc($g->name))) {
  499. ++$found;
  500. $g->add_trouble('progvar');
  501. };
  502. };
  503. return $found;
  504. };
  505. sub S_bad_character {
  506. my ($self) = @_;
  507. my $found = 0;
  508. my @gds = @{ $self->gds };
  509. foreach my $g (@gds) {
  510. if (lc($g->name) !~ m{\A[a-z_][a-z0-9_]*\z}) {
  511. ++$found;
  512. $g->add_trouble('badchar');
  513. };
  514. };
  515. return $found;
  516. };
  517. ## 16. check that all Path objects have either a ScatteringPath or a folder/file defined
  518. sub S_path_calculation_exists {
  519. my ($self) = @_;
  520. my $found = 0;
  521. my @paths = @{ $self->paths };
  522. foreach my $p (@paths) {
  523. next if not $p->include;
  524. next if not $p->data->fit_include;
  525. next if (ref($p->sp) =~ m{(?:ScatteringPath|SSPath|FPath)});
  526. my $nnnn = File::Spec->catfile($p->folder, $p->file);
  527. next if ((-e $nnnn) and $p->file);
  528. ++$found;
  529. $p->add_trouble('nocalc');
  530. };
  531. return $found;
  532. };
  533. ## 17. check that there are no unresolved merge parameetrs
  534. sub S_notice_merge {
  535. my ($self) = @_;
  536. my $found = 0;
  537. my @gds = @{ $self->gds };
  538. foreach my $g (@gds) {
  539. if ($g->gds eq 'merge') {
  540. ++$found;
  541. $g->add_trouble('merge');
  542. };
  543. };
  544. return $found;
  545. };
  546. ## 18. check that no more than one path is flagged as the default
  547. sub S_default_path {
  548. my ($self) = @_;
  549. my $found = 0;
  550. my @paths = @{ $self->paths };
  551. foreach my $p (@paths) {
  552. next if not $p->include;
  553. next if not $p->data->fit_include;
  554. ++$found if $p->default_path;
  555. };
  556. $self->add_trouble('defaultpath') if ($found > 1);
  557. return $found;
  558. };
  559. ## 19. check for loops and cycles among the GDS math expressions
  560. sub S_cycle_loop {
  561. my ($self) = @_;
  562. my $found = 0;
  563. my @gds = @{ $self->gds };
  564. my @all_params = ();
  565. foreach my $g (@gds) {
  566. next if ($g->gds =~ m{(?:merge|skip)});
  567. push @all_params, $g->name;
  568. };
  569. my $tokenizer_regexp = '(?-xism:(?=[\t\ \(\)\*\+\,\-\/\^])[\-\+\*\^\/\(\)\,\ \t])';
  570. #my $tokenizer_regexp = Regexp::Assemble->new()->add('-', '+', '*', '^', '/', '(', ')', ',', " ", "\t")->re;
  571. my $graph = Graph->new;
  572. foreach my $g (@gds) {
  573. next if ($g->gds =~ m{(?:merge|skip)});
  574. my $mathexp = $g->mathexp;
  575. my @list = split(/$tokenizer_regexp+/, lc($mathexp));
  576. foreach my $token (@list) {
  577. next if ($token =~ m{\A\s*\z}); # space, ok
  578. next if ($token =~ m{\A$NUMBER\z}); # number, ok
  579. next if (is_IfeffitFunction($token)); # function, ok
  580. next if (lc($token) =~ m{\A(?:etok|pi)\z}); # Ifeffit's defined constants, ok
  581. next if (lc($token) eq 'reff'); # reff, ok
  582. $graph -> add_edge(lc($g->name), $token);
  583. };
  584. };
  585. foreach my $loop ($graph->self_loop_vertices) {
  586. $self->add_trouble(join('~', 'loop', 'x', $loop));
  587. ++$found;
  588. };
  589. if ($graph->has_a_cycle) {
  590. my @cycle = $graph->find_a_cycle;
  591. if ($#cycle) { # we have already reported on loops
  592. $self->add_trouble(join('~', 'cycle', 'x', join(" --> ", @cycle)));
  593. ++$found;
  594. };
  595. };
  596. return $found;
  597. };
  598. ## 20. check for an obvious data repitition, Data attribute collided set to 1 for any data group
  599. sub S_data_collision {
  600. my ($self) = @_;
  601. my $found = 0;
  602. my @data = @{ $self->data };
  603. foreach my $d (@data) {
  604. next if not $d->fit_include;
  605. if ((true {$_->group eq $d->group} @data) > 1) {
  606. ++$found;
  607. $d->add_trouble('collision');
  608. };
  609. };
  610. return $found;
  611. };
  612. ## 21. check that each data set has at least one path associated with it
  613. sub S_data_paths {
  614. my ($self) = @_;
  615. my $found = 0;
  616. my @data = @{ $self->data };
  617. my @paths = @{ $self->paths };
  618. foreach my $d (@data) {
  619. next if (not $d->fit_include);
  620. my $count = 0;
  621. foreach my $p (@paths) {
  622. ++$count if ($p->data eq $d);
  623. };
  624. if ($count == 0) {
  625. ++$found;
  626. $d->add_trouble('datanopaths');
  627. };
  628. };
  629. return $found;
  630. };
  631. 1;
  632. =head1 NAME
  633. Demeter::Fit::Sanity - Sanity checks for EXAFS fitting models
  634. =head1 VERSION
  635. This documentation refers to Demeter version 0.9.26.
  636. =head1 SYNOPSIS
  637. my $fitobject = Demeter::Fit ->
  638. new(gds => \@gds_objects,
  639. data => [$data_object],
  640. paths => \@path_objects,
  641. );
  642. $command = $fitobject -> fit;
  643. Before the fit method is run, a series of sanity check on the data
  644. contained in the fit object is run. The sanity checks all live in
  645. this module.
  646. =head1 DESCRIPTION
  647. This module contains all the sanity checks made on a Fit object before
  648. the fit starts. This file forms part of the base of the
  649. Demeter::Fit class and serves no independent function. That
  650. is, using this module directly in a program does nothing useful -- it
  651. is purely a utility module for the Feff object.
  652. The user should never need to call the methods explicitly since they
  653. are called automatically whenever a fit or a sum is performed.
  654. However they are documented here so that the scope of such checks made
  655. is clearly understood.
  656. When problems are found, the fit will exit and a descriptive report
  657. will be made.
  658. =head1 METHODS
  659. The following sanity checks are made on the Fit object:
  660. =over 4
  661. =item *
  662. All data files included in the fit exist.
  663. =item *
  664. No data set is obviously used twice in the fit.
  665. =item *
  666. All F<feffNNNN.dat> files used in the fit exist.
  667. =item *
  668. All guess parameters are used in at least one def parameter or path
  669. parameter.
  670. =item *
  671. No def or path parameters use parameters which have not been defined.
  672. =item *
  673. Binary operators are used correctly, specifically that none of these
  674. strings appear in a math expression:
  675. ++ -- // *** ^^
  676. =item *
  677. All function names (i.e. strings that are followed by an open paren)
  678. are valid Ifeffit functions.
  679. =item *
  680. All data and path objects have unique group names.
  681. =item *
  682. All GDS parameters have unique names.
  683. =item *
  684. All opening parens are matched by closing parens.
  685. =item *
  686. All data paremeters make sense, for example that C<fft_kmin> is
  687. smaller than C<fft_kmax>.
  688. =item *
  689. The number of guess parameters does not exceed the number of
  690. independent points.
  691. =item *
  692. The C<bft_rmin> value is not greater than C<bkg_rbkg>.
  693. =item *
  694. The R_eff of any path is not far beyond C<bft_rmax>.
  695. =item *
  696. Ifeffit's hardwired limits on things like the maximum number of guess
  697. parameters and the maximum number of data sets are not exceeded by the
  698. fitting model.
  699. =item *
  700. No GDS parameters have the names of Ifeffit program variables or other
  701. reserved words.
  702. =item *
  703. No merge parameters remain unresolved.
  704. =back
  705. =head1 TROUBLE REPORTING
  706. The C<trouble> attribute of an Demeter object will be filled with a
  707. pipe-separated list of problem codes.
  708. Some error codes contain additional information to further identify
  709. the problem. These codes have a keyword separated from the other
  710. information by an underscore, making these sufficiently easy to parse
  711. on the fly. Indeed, the C<translate_trouble> method of the base
  712. object (see L<Demeter>) does just that, so error reporting during a
  713. fit is an example of literate programming.
  714. Here are the explanations:
  715. =head2 Problems with Data objects
  716. =over 4
  717. =item C<-e>
  718. You specified an explicit data file to use in the fit (i.e. not part
  719. of a project file) and that file does not exist.
  720. =item C<-r>
  721. You specified an explicit data file to use in the fit (i.e. not part
  722. of a project file) and that data file cannot be read.
  723. =item C<namenotunique>
  724. The Ifeffit group name of this data group is not unique.
  725. =item C<pathdataname>
  726. This path has an Ifeffit group name which is used by a Path object.
  727. =item C<tagnotunique>
  728. The tag of this data group is not unique.
  729. =item C<cvnotunique>
  730. The characteristic value of this data group is not unique.
  731. =item C<kminkmax>
  732. C<kmin> is larger than C<kmax>.
  733. =item C<rminrmax>
  734. C<rmin> is larger than C<rmax>.
  735. =item C<rminrbkg>
  736. C<rmin> is smaller than the value of C<rbkg> that was used in the
  737. background removal.
  738. =item C<collision>
  739. This data came from the the same source as another data group. You
  740. seem to be trying to increase your number of independent points by
  741. fitting the same data more than once in a multiple data set fit.
  742. =item C<datanopaths>
  743. This data has no paths associated with it. You must either assign
  744. paths to it or exclude it from the fit.
  745. =back
  746. =head2 Problems with Path objects
  747. =over 4
  748. =item C<-e>
  749. The path file does not exist (perhaps the Feff calculation was not run).
  750. =item C<-r>
  751. The path file cannot be read.
  752. =item C<spnotexist>
  753. The C<sp> attribute is not defined or not set to a ScatteringPath or
  754. other Path object, and the path is not using a feffNNNN.dat file
  755. directly.
  756. =item C<useundef> + C<$pp> + C<$token>
  757. The math expression for the C<$pp> path parameter contains an undefined
  758. parameter, C<$token>.
  759. =item C<binary> + C<$pp> + C<$token>
  760. The math expression for the C<$pp> path parameter contains an unallowed
  761. binary math operator, C<$token>.
  762. =item C<function> + C<$pp> + C<$token>
  763. The math expression for the C<$pp> path parameter contains a
  764. mathematical function unknown to Ifeffit, C<$token>.
  765. =item C<namenotunique>
  766. The Ifeffit group name for this path is not unique.
  767. =item C<pathdataname>
  768. This path has an Ifeffit/Larch group name which is used by a Data object.
  769. =item C<parens> + C<$pp>
  770. The math expression for the C<$pp> path parameter has unmatched parentheses.
  771. =item C<reffrmax>
  772. The R effective for this path is much larger than the C<rmax> value
  773. chosen for the fit to the data.
  774. =item C<nocalc>
  775. It seems as though the Feff calculation for this path has not been made yet.
  776. =back
  777. =head2 Problems with GDS objects
  778. =over 4
  779. =item C<notused>
  780. This is a guess parameter which is not used in the math expressions
  781. for any def or path parameters.
  782. =item C<usecv>
  783. This is a def parameter which uses the characteristic value (cv).
  784. This is not yet allowed for def parameters.
  785. =item C<useundef>
  786. The math expression for this GDS parameter uses an undefined parameter
  787. name.
  788. =item C<binary> + C<$token>
  789. The math expression for this GDS parameter contains an unallowed
  790. binary math operator, C<$token>.
  791. =item C<function> + C<$token>
  792. The math expression for this GDS parameter contains a mathematical
  793. function unknown to Ifeffit, C<$token>.
  794. =item C<notunique>
  795. The name of this GDS parameter is not unique.
  796. =item C<parens>
  797. The math expression for this GDS parameter has unmatched parentheses.
  798. =item C<progvar>
  799. The name of this GDS parameter is an Ifeffit program variable name.
  800. =item C<badchar>
  801. The name of this GDS parameter contains an unallowed character.
  802. Allowed characters are letters (a-z), numbers (0-9), and underscore
  803. (_). The first character must not be a number.
  804. =item C<merge>
  805. This is an parameter which has been defined twice, possibly from the
  806. merge of fitting projects or the creation of two more similar quick
  807. first shell fitting models.
  808. =back
  809. =head2 Problems with Fit objects
  810. =over 4
  811. =item C<gds>
  812. No GDS parameters are defined for this fit
  813. =item C<data>
  814. No data sets are defined for this fit
  815. =item C<paths>
  816. No paths are defined for this fit
  817. =item C<nvarnidp>
  818. This fitting model uses more guess parameters than the available
  819. information content of the data.
  820. =item C<nvarys>
  821. This fitting model uses more than Ifeffit's compiled-in limit of guess
  822. parameters (&max_varys).
  823. =item C<nparams>
  824. This fitting model uses more than Ifeffit's compiled-in limit of
  825. parameters (&max_scalars).
  826. =item C<nrestraints>
  827. This fitting model uses more than Ifeffit's compiled-in limit of
  828. restraints (10).
  829. =item C<ndatasets>
  830. This fitting model uses more than Ifeffit's compiled-in limit of
  831. data sets (&max_data_sets).
  832. =item C<npaths>
  833. This fitting model uses more than Ifeffit's compiled-in limit of
  834. paths (&max_paths).
  835. =item C<defaultpath>
  836. More than one path is flagged as being the default path, making it
  837. unclear how to evaluate the log file.
  838. =item C<loop> + C<$token>
  839. The parameter C<$token> refers to itself in its math expression.
  840. =item C<cycle> + C<$token>
  841. There is a cyclical dependence among a set of parameter math
  842. expressions. This cycle is C<$token>.
  843. =back
  844. =head1 CONFIGURATION AND ENVIRONMENT
  845. See L<Demeter> for a description of the configuration system.
  846. =head1 BUGS AND LIMITATIONS
  847. Missing tests:
  848. =over 4
  849. =item *
  850. Test that every Path is associated with a data set. (Warn, not fatal.)
  851. =item *
  852. Test that each data in the data array is properly defined.
  853. =item *
  854. Test that every Path points to a real path file
  855. =back
  856. Please report problems to the Ifeffit Mailing List
  857. (L<http://cars9.uchicago.edu/mailman/listinfo/ifeffit/>)
  858. Patches are welcome.
  859. =head1 AUTHOR
  860. Bruce Ravel, L<http://bruceravel.github.io/home>
  861. L<http://bruceravel.github.io/demeter/>
  862. =head1 LICENCE AND COPYRIGHT
  863. Copyright (c) 2006-2017 Bruce Ravel (L<http://bruceravel.github.io/home>). All rights reserved.
  864. This module is free software; you can redistribute it and/or
  865. modify it under the same terms as Perl itself. See L<perlgpl>.
  866. This program is distributed in the hope that it will be useful,
  867. but WITHOUT ANY WARRANTY; without even the implied warranty of
  868. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  869. =cut