PageRenderTime 66ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Demeter/Fit.pm

https://bitbucket.org/bruceravel/demeter
Perl | 2227 lines | 1632 code | 368 blank | 227 comment | 205 complexity | ea02d8883a2bcbabe0513f53c3b12f09 MD5 | raw file
Possible License(s): LGPL-2.0, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. package Demeter::Fit;
  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 diagnostics;
  16. use autodie qw( open close );
  17. use File::Copy;
  18. use File::Spec;
  19. use Moose;
  20. extends 'Demeter';
  21. use MooseX::Aliases;
  22. with 'Demeter::Fit::Happiness';
  23. #with 'Demeter::Fit::Horae';
  24. with 'Demeter::Fit::Sanity';
  25. if ($Demeter::mode->ui eq 'screen') {
  26. with 'Demeter::UI::Screen::Interview';
  27. with 'Demeter::UI::Screen::Progress';
  28. };
  29. use Demeter::NumTypes qw( NonNeg Natural NaturalC );
  30. use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
  31. local $Archive::Zip::UNICODE = 1;
  32. use Carp;
  33. use Cwd;
  34. use File::Basename;
  35. use File::Copy;
  36. use File::Path;
  37. use File::Spec;
  38. use List::Util qw(max);
  39. use List::MoreUtils qw(any none zip uniq minmax);
  40. use Regexp::Assemble;
  41. use Demeter::Constants qw($NUMBER $NULLFILE $STATS);
  42. use Text::Wrap;
  43. use YAML::Tiny;
  44. use Text::Wrap;
  45. $Text::Wrap::columns = 65;
  46. ## -------- properties
  47. has 'description' => (is => 'rw', isa => 'Str', default => q{});
  48. has 'fom' => (is => 'rw', isa => 'LaxNum', default => 0);
  49. has 'fitenvironment' => (is => 'rw', isa => 'Str', default => sub{ shift->environment });
  50. has 'interface' => (is => 'rw', isa => 'Str', default => 'Demeter-based perl script'); # should be sensitive to :ui "pragma"
  51. has 'started' => (is => 'rw', isa => 'Str', default => q{}); # should be a Date/Time object
  52. has 'time_of_fit' => (is => 'rw', isa => 'Str', default => q{}); # should be a Date/Time object
  53. has 'prepared_by' => (is => 'rw', isa => 'Str', default => sub{ shift->who });
  54. has 'contact' => (is => 'rw', isa => 'Str', default => q{});
  55. has 'fitted' => (is => 'rw', isa => 'Bool', default => 0);
  56. has 'update_gds' => (is => 'rw', isa => 'Bool', default => 1);
  57. has 'number' => (is => 'rw', isa => 'LaxNum', default => 0);
  58. ## -------- serialization/deserialization
  59. has 'project' => (is => 'rw', isa => 'Str', default => q{},
  60. trigger => sub{my ($self, $new) = @_; $self->deserialize(file=>$new) if $new} );
  61. has 'folder' => (is => 'rw', isa => 'Str', default => q{});
  62. has 'grabbed' => (is => 'rw', isa => 'Bool', default => 0);
  63. has 'thawed' => (is => 'rw', isa => 'Bool', default => 0);
  64. has 'logcache' => (is => 'rw', isa => 'Str', default => q{});
  65. has 'keep' => (is => 'rw', isa => 'Bool', default => 1);
  66. ## -------- mechanics of the fit
  67. has 'cormin' => (is => 'rw', isa => NonNeg, default => sub{ shift->co->default("fit", "cormin") || 0.4});
  68. has 'header' => (is => 'rw', isa => 'Str', default => q{});
  69. has 'footer' => (is => 'rw', isa => 'Str', default => q{});
  70. has 'restraints' => (is => 'rw', isa => 'Str', default => q{});
  71. has 'ndata' => (is => 'rw', isa => Natural, default => 0);
  72. has 'indeces' => (is => 'rw', isa => 'Str', default => q{});
  73. has 'location' => (is => 'rw', isa => 'Str', default => q{});
  74. has 'fit_performed' => (is => 'rw', isa => 'Bool', default => 0);
  75. has 'ignore_errors' => (is => 'rw', isa => 'Bool', default => 0);
  76. has 'stop' => (is => 'rw', isa => 'Bool', default => 0);
  77. has 'troubletext' => (is => 'rw', isa => 'Str', default => q{});
  78. ## -------- flags for skipping certain sanity checks
  79. has 'ignore_nidp' => (is => 'rw', isa => 'Bool', default => 0);
  80. has 'ignore_rbkg' => (is => 'rw', isa => 'Bool', default => 0);
  81. has 'ignore_rmax' => (is => 'rw', isa => 'Bool', default => 0);
  82. has 'ignore_datacollision' => (is => 'rw', isa => 'Bool', default => 0);
  83. ## -------- array attributes
  84. has 'gds' => (
  85. traits => ['Array'],
  86. is => 'rw',
  87. isa => 'ArrayRef',
  88. default => sub { [] },
  89. handles => {
  90. 'push_gds' => 'push',
  91. 'pop_gds' => 'pop',
  92. 'shift_gds' => 'shift',
  93. 'unshift_gds' => 'unshift',
  94. 'clear_gds' => 'clear',
  95. },
  96. trigger => sub{my ($self) = @_; $self->update_gds(1)},
  97. );
  98. has 'data' => (
  99. traits => ['Array'],
  100. is => 'rw',
  101. isa => 'ArrayRef',
  102. default => sub { [] },
  103. handles => {
  104. 'push_data' => 'push',
  105. 'pop_data' => 'pop',
  106. 'shift_data' => 'shift',
  107. 'unshift_data' => 'unshift',
  108. 'clear_data' => 'clear',
  109. }
  110. );
  111. has 'datagroups' => (is => 'rw', isa => 'ArrayRef', default => sub{[]});
  112. has 'paths' => (
  113. traits => ['Array'],
  114. is => 'rw',
  115. isa => 'ArrayRef',
  116. default => sub { [] },
  117. handles => {
  118. 'push_paths' => 'push',
  119. 'pop_paths' => 'pop',
  120. 'shift_paths' => 'shift',
  121. 'unshift_paths' => 'unshift',
  122. 'clear_paths' => 'clear',
  123. }
  124. );
  125. has 'vpaths' => (
  126. traits => ['Array'],
  127. is => 'rw',
  128. isa => 'ArrayRef',
  129. default => sub { [] },
  130. handles => {
  131. 'push_vpaths' => 'push',
  132. 'pop_vpaths' => 'pop',
  133. 'shift_vpaths' => 'shift',
  134. 'unshift_vpaths' => 'unshift',
  135. 'clear_vpaths' => 'clear',
  136. }
  137. );
  138. ## -------- statistics
  139. has 'happiness' => (is => 'rw', isa => NonNeg, default => 0);
  140. has 'happiness_summary' => (is => 'rw', isa => 'Str', default => q{});
  141. has 'n_idp' => (is => 'rw', isa => NonNeg, default => 0);
  142. has 'n_varys' => (is => 'rw', isa => NaturalC, default => 0, coerce => 1);
  143. has 'data_total' => (is => 'rw', isa => NaturalC, default => 0, coerce => 1);
  144. has 'epsilon_k' => (is => 'rw', isa => NonNeg, default => 0);
  145. has 'epsilon_r' => (is => 'rw', isa => NonNeg, default => 0);
  146. has 'r_factor' => (is => 'rw', isa => NonNeg, default => 0);
  147. has 'chi_square' => (is => 'rw', isa => NonNeg, default => 0);
  148. has 'chi_reduced' => (is => 'rw', isa => NonNeg, default => 0);
  149. ## deprecated and unused...
  150. has 'fancyline' => (is => 'rw', isa => 'Str', default => q{});
  151. has 'correlations' => (
  152. traits => ['Hash'],
  153. is => 'rw',
  154. isa => 'HashRef[HashRef]',
  155. default => sub { {} },
  156. handles => {
  157. 'exists_in_correlations' => 'exists',
  158. 'keys_in_correlations' => 'keys',
  159. 'get_correlations' => 'get',
  160. 'set_correlations' => 'set',
  161. }
  162. );
  163. has 'parameters' => (
  164. traits => ['Array'],
  165. is => 'rw',
  166. isa => 'ArrayRef',
  167. default => sub { [] },
  168. handles => {
  169. 'push_parameters' => 'push',
  170. 'pop_parameters' => 'pop',
  171. 'clear_parameters' => 'clear',
  172. }
  173. );
  174. has 'pathresults' => (
  175. traits => ['Array'],
  176. is => 'rw',
  177. isa => 'ArrayRef',
  178. default => sub { [] },
  179. handles => {
  180. 'push_pathresults' => 'push',
  181. 'pop_pathresults' => 'pop',
  182. 'clear_pathresults' => 'clear',
  183. }
  184. );
  185. sub BUILD {
  186. my ($self, @params) = @_;
  187. $self->mo->push_Fit($self);
  188. $self->number($self->mo->currentfit);
  189. };
  190. # sub set_all {
  191. # my ($self, $which, $rhash) = @_;
  192. # return 0 if ($which !~ m{\A(?:data|gds|paths)\z}i);
  193. # return 0 if (ref($rhash) ne 'HASH');
  194. # my @list = @{ $component_of{ident $self}{$which} };
  195. # foreach my $obj (@list) {
  196. # $obj -> set($rhash);
  197. # };
  198. # return $self;
  199. # };
  200. sub DEMOLISH {
  201. my ($self) = @_;
  202. $self->alldone;
  203. };
  204. override 'alldone' => sub {
  205. my ($self) = @_;
  206. $self->remove;
  207. rmtree $self->folder if (-d $self->folder);
  208. };
  209. override all => sub {
  210. my ($self) = @_;
  211. my %all = $self->SUPER::all;
  212. delete $all{gds};
  213. delete $all{data};
  214. delete $all{paths};
  215. delete $all{vpaths};
  216. delete $all{logcache};
  217. return %all;
  218. };
  219. override Clone => sub {
  220. my ($self, @arguments) = @_;
  221. my $new = ref($self) -> new();
  222. my %hash = $self->SUPER::all;
  223. delete $hash{group};
  224. $new -> set(%hash);
  225. $new -> set(@arguments);
  226. ## the cloned object needs its own group name
  227. #$new->group($self->_get_group());
  228. return $new;
  229. };
  230. sub rm {
  231. my ($self) = @_;
  232. #print "removing ", $self->location, $/;
  233. rmtree($self->location);
  234. };
  235. ## ------------------------------------------------------------
  236. ## sanity checks see Demeter::Fit::Sanity
  237. sub _verify_fit {
  238. my ($self) = @_;
  239. my $trouble_found = 0;
  240. my @gds = @{ $self->gds };
  241. $self->add_trouble("gds"), ++$trouble_found if ($#gds == -1);
  242. my @data = @{ $self->data };
  243. $self->add_trouble("data"), ++$trouble_found if ($#data == -1);
  244. my @paths = @{ $self->paths };
  245. $self->add_trouble("paths"), ++$trouble_found if ($#paths == -1);
  246. ## all these tests live in Demeter::Fit::Sanity
  247. ## 1. check that all data and feffNNNN.dat files exist
  248. $trouble_found += $self->S_data_files_exist;
  249. $trouble_found += $self->S_feff_files_exist;
  250. ## 2. Check that the Path sp attributes are set sensibly
  251. $trouble_found += $self->S_sp_exist;
  252. ## 3. check that all guesses are used in defs and pathparams
  253. $trouble_found += $self->S_defined_not_used;
  254. ## 4. check that defs and path paramers do not use undefined GDS parameters
  255. $trouble_found += $self->S_used_not_defined;
  256. ## 5. check that ++ -- // *** do not appear in math expression
  257. $trouble_found += $self->S_binary_ops;
  258. ## 6. check that all function() names are valid in math expressions
  259. $trouble_found += $self->S_function_names;
  260. ## 7. check that all data have unique group names and tags
  261. ## 8. check that all paths have unique group names
  262. $trouble_found += $self->S_unique_group_names;
  263. ## 9. check that all GDS have unique names
  264. $trouble_found += $self->S_gds_unique_names;
  265. ## 10. check that parens match
  266. $trouble_found += $self->S_parens_not_match;
  267. ## 11. check that data parameters are sensible
  268. $trouble_found += $self->S_data_parameters;
  269. ## 12. check number of guesses against Nidp
  270. $trouble_found += $self->S_nidp unless $self->ignore_nidp;
  271. ## 13. verify that Rmin is >= Rbkg for data imported as mu(E)
  272. $trouble_found += $self->S_rmin_rbkg unless $self->ignore_rbkg;
  273. ## 14. verify that Reffs of all paths are within some margin of rmax
  274. $trouble_found += $self->S_reff_rmax unless $self->ignore_rmax;
  275. ## 15. check that Ifeffit's hard wired limits are not exceeded
  276. $trouble_found += $self->S_exceed_ifeffit_limits;
  277. ## 16. check that parameters do not have program variable names
  278. $trouble_found += $self->S_program_var_names;
  279. ## 17.1. check that parameters do not have unallowed characters in their names
  280. $trouble_found += $self->S_bad_character;
  281. ## 18. check that all Path objects have either a ScatteringPath or a folder/file defined
  282. $trouble_found += $self->S_path_calculation_exists;
  283. ## 19. check that there are no unresolved merge parameters
  284. $trouble_found += $self->S_notice_merge;
  285. ## 20. check that no more than one path is flagged as the default path
  286. $trouble_found += $self->S_default_path;
  287. ## 21. check that GDS math expressions do not have loops or cycles
  288. $trouble_found += $self->S_cycle_loop;
  289. ## 22. check for obvious cases of a data set used more than once
  290. $trouble_found += $self->S_data_collision unless $self->ignore_datacollision;
  291. ## 23. check that each data set used in the fit has one or more paths assigned to it
  292. $trouble_found += $self->S_data_paths;
  293. return $trouble_found;
  294. };
  295. ## ------------------------------------------------------------
  296. ## fit and ff2chi
  297. sub pre_fit {
  298. my ($self) = @_;
  299. ## reset use attribute (in case this fit involved local parameters)
  300. ## and clear all trouble attributes
  301. foreach my $gds (@{ $self->gds }) {
  302. $gds -> Use(1);
  303. $gds -> trouble(q{});
  304. };
  305. foreach my $d (@{ $self->data }) {
  306. $d -> fitting(0);
  307. $d -> trouble(q{});
  308. };
  309. foreach my $p (@{ $self->paths }) {
  310. $p -> _update('path') if (ref($p) =~ m{FSPath});
  311. $p -> trouble(q{});
  312. #print $/;
  313. #print $p->s02, $/;
  314. #print $p->e0, $/;
  315. #print $p->delr, $/;
  316. #print $p->sigma2, $/;
  317. };
  318. $self -> trouble(q{});
  319. $self -> troubletext(q{});
  320. return $self->template("fit", "prep_fit");
  321. };
  322. sub fit {
  323. my ($self) = @_;
  324. $self->stop(0);
  325. local $SIG{ALRM} = sub { 1; } if not $SIG{ALRM};
  326. $self->start_spinner("Demeter is performing a fit") if ($self->mo->ui eq 'screen');
  327. my $prefit = $self->pre_fit;
  328. $self->number($self->mo->currentfit);
  329. my $trouble_found = $self->_verify_fit;
  330. if ($trouble_found) {
  331. $self->stop(1);
  332. my $text = $self->trouble_report;
  333. carp($text) if ($self->mo->ui ne 'Wx');
  334. $self->troubletext($text);
  335. if (not $self->ignore_errors) {
  336. if ($self->mo->ui eq 'Wx') {
  337. return "This fit has unrecoverable errors";
  338. } else {
  339. croak("This fit has unrecoverable errors");
  340. };
  341. };
  342. };
  343. return "Tilt!" if $self->stop;
  344. $self->dispose($prefit);
  345. foreach my $gds (@{ $self->gds }) {
  346. $self->dispense("fit", "erase_gds", {items=>$gds->name});
  347. };
  348. $self->mo->fit($self);
  349. $self->mo->pathindex(1);
  350. foreach my $p (@{ $self->paths }) {
  351. $self->dispense('fit', 'path_index') if ($p->default_path);
  352. ##$self->dispose("set path_index = " . $p->Index) if ($p->default_path);
  353. };
  354. my $command = q{};
  355. foreach my $type (qw(guess lguess set def restrain)) {
  356. $command .= $self->_gds_commands($type);
  357. };
  358. $self->update_gds(0);
  359. ## get a list of all data sets included in the fit
  360. my @datasets = @{ $self->data };
  361. my @useddata = grep {$_->fit_include && $_} @datasets;
  362. my $ndata = $#useddata+1;
  363. my $ipath = 0;
  364. my $count = 0;
  365. my $str = q{};
  366. $self->name("fit to " . join(", ", map {$_->name} @useddata)) if not $self->name;
  367. $self->description("fit to " . join(", ", map {$_->name} @useddata)) if not $self->description;
  368. ## munge parameters and path parameters to deal with lguess
  369. $command .= $self->_local_parameters;
  370. my $restraints_string = q{};
  371. foreach my $gds (@{ $self->gds }) {
  372. if (($gds->gds eq 'restrain') and ($gds->Use)) {
  373. #$restraints_string .= "restraint=%s, ", $gds->name;
  374. my $this = $gds->template("fit", "restraint");
  375. chomp($this);
  376. $restraints_string .= $this;
  377. };
  378. };
  379. if ($restraints_string) {
  380. $restraints_string = substr($restraints_string, 0, -2);
  381. $restraints_string = wrap("", " ", $restraints_string);
  382. };
  383. $self -> restraints($restraints_string);
  384. $self -> ndata($ndata);
  385. foreach my $data (@datasets) {
  386. next if not $data->fit_include;
  387. ++$count;
  388. $data -> set(fitting=>1, fit_data=>$count, fit_group=>$self->group);
  389. ## read the data
  390. $data -> _update('fft');
  391. $command .= "\n";
  392. ## define all the paths for this data set
  393. ##
  394. ## ifeffit needs a list of path indeces
  395. ## larch needs a list of path group names, larch uses the misnamed indeces attribute to carry this list
  396. my $group = $data->group;
  397. my @indexstring = ();
  398. my $iii=1;
  399. foreach my $p (@{ $self->paths }) {
  400. next if not defined($p);
  401. next if ($p->data ne $data);
  402. next if not $p->include;
  403. $p->_update_from_ScatteringPath if $p->sp;
  404. ++$ipath;
  405. my $i = $p->mo->pathindex;
  406. $p->Index($i);
  407. $p->mo->pathindex(++$i);
  408. my $lab = $p->name;
  409. ($lab = "path $ipath") if ($lab =~ m{\A(?:\s*|path\s+\d+)\z});
  410. $p->set(name=>$lab);
  411. $p->rewrite_cv;
  412. my $this_command = $p->_path_command(0);
  413. $this_command =~ s{(?<!sigma2_)(debye|eins)\(}{sigma2_$1\(}g if (Demeter->is_larch);
  414. $this_command =~ s{sigma2_(debye|eins)\(}{$1\(}g if (Demeter->is_ifeffit);
  415. $command .= $this_command;
  416. if (Demeter->is_ifeffit) {
  417. push @indexstring, $p->Index;
  418. } elsif (Demeter->is_larch) {
  419. push @indexstring, $p->group;
  420. };
  421. };
  422. $command .= "\n";
  423. $command .= $data->template("fit", "next") if ($count > 1);
  424. if (Demeter->is_ifeffit) {
  425. $self -> indeces(_normalize_paths(\@indexstring));
  426. } elsif (Demeter->is_larch) {
  427. $self -> indeces(join(',', @indexstring));
  428. };
  429. if ($data->fit_data lt $self->ndata) {
  430. $command .= $data->template("fit", "fit");
  431. } else {
  432. my @mdsgroups = map {$_->group} @datasets;
  433. $command .= $data->template("fit", "endfit", {MDS=>\@mdsgroups});
  434. };
  435. $self -> restraints(q{}) if ($count == 1);
  436. $data -> fitsum('fit');
  437. };
  438. ## write out afters
  439. $command .= $self->_gds_commands('after');
  440. ## make residual and background arrays
  441. foreach my $data (@useddata) {
  442. $command .= $data->template("fit", "residual");
  443. if ($data->fit_do_bkg) {
  444. $command .= $data->template("fit", "background");
  445. };
  446. };
  447. #my @xxx = Ifeffit::get_array('lmzpb_fit.k');
  448. #Demeter->pjoin(3, minmax(@xxx));
  449. #print $command, $/;
  450. $self->dispose($command);
  451. #@xxx = Ifeffit::get_array('lmzpb_fit.k');
  452. #Demeter->pjoin(3.1, minmax(@xxx));
  453. $self->evaluate;
  454. ## set happiness statistics
  455. my @joy = $self->get_happiness;
  456. $self->happiness( $joy[0] || 0 );
  457. $self->happiness_summary( $joy[1] || q{} );
  458. foreach my $g (@ {$self->gds}) {
  459. $g->autoannotate;
  460. };
  461. $self->fitted(1);
  462. $self->mo->fit(q{});
  463. $self->mo->increment_fit;
  464. ## prep data for plotting
  465. foreach my $data (@useddata) {
  466. $data->update_fft(1);
  467. #$data->fitting(0);
  468. #$data->fit_group(q{});
  469. };
  470. $self->stop_spinner if ($self->mo->ui eq 'screen');
  471. #$self->ifeffit_heap;
  472. return $self;
  473. };
  474. alias feffit => 'fit';
  475. sub sum {
  476. my ($self, $data) = @_;
  477. $data ||= $self->data->[0];
  478. my $sum = Demeter::VPath->new(name=>$self->name . ' -- sum');
  479. foreach my $p (@{ $self->paths }) {
  480. next if ($data ne $p->data);
  481. $sum -> include($p);
  482. };
  483. if ($self->update_gds) {
  484. my $command = q{};
  485. foreach my $type (qw(guess lguess set def)) {
  486. $command .= $self->_gds_commands($type);
  487. };
  488. $self->dispose($command);
  489. $self->update_gds(0);
  490. };
  491. return $sum;
  492. };
  493. sub trouble_report {
  494. my ($fit) = @_;
  495. my $text = q{};
  496. $Text::Wrap::columns = 60;
  497. foreach my $obj ($fit, @{ $fit->data }, @{ $fit->gds }, @{ $fit->paths }) {
  498. next if not $obj->trouble;
  499. my $which = ref($obj);
  500. $which =~ s{Demeter::}{};
  501. foreach my $t (split(/\|/, $obj->trouble)) {
  502. my $pathfile = q{};
  503. if ($which =~ m{Path}) {
  504. #$pathfile = ($obj->sp) ? $obj->sp->intrpline : File::Spec->catfile($obj->folder, $obj->file);
  505. #$pathfile = '(' . $pathfile . ')';
  506. $pathfile = "in data set '".$obj->data->name."'";
  507. };
  508. $text .= sprintf("%s: '%s' %s\n%s\n\n", uc($which), $obj->name, $pathfile,
  509. wrap(" ", " ", $obj->translate_trouble($t)));
  510. ##$text .= sprintf("%s: %s\t(%s)\n%s\n\n", $which, $obj->name, $t, wrap(" ", " ", $obj->translate_trouble($t)));
  511. };
  512. };
  513. return $text;
  514. };
  515. sub _gds_commands {
  516. my ($self, $type) = @_;
  517. my $string = q{};
  518. foreach my $gds (@{ $self->gds }) {
  519. next unless ($gds->gds eq lc($type));
  520. next if (not $gds->Use);
  521. $string .= $gds -> write_gds;
  522. $gds->initial($gds->mathexp);
  523. };
  524. $string = "\n" . $self->hashes . " $type parameters:\n" . $string if $string;
  525. return $string;
  526. };
  527. sub _local_parameters {
  528. my ($self) = @_;
  529. my $string = q{};
  530. my %created = ();
  531. ## list of lguesses
  532. my @local_list = grep {$_->gds eq 'lguess'} (@{ $self->gds });
  533. ## regex that matches all the lguesses
  534. my $local_regex = join("|", map {$_->name} @local_list);
  535. return q{} if (not $local_regex);
  536. ## need to fetch a complete list of lguess and the def, after,
  537. ## restrain that depend upon lguess by digging into the math
  538. ## expression dependencies
  539. my $continue = 1;
  540. while ($continue) {
  541. my $count = 0;
  542. my @llist = ();
  543. foreach my $gds (@{ $self->gds }) {
  544. next if ($gds->name =~ m{\b($local_regex)\b}i);
  545. next if ($gds->gds =~ m{(?:skip|merge)});
  546. if (($gds->mathexp =~ m{\b($local_regex)\b}i) and
  547. (none {$gds->name eq $_->name} @local_list)) {
  548. push @llist, $gds;
  549. ++$count;
  550. };
  551. };
  552. push @local_list, @llist;
  553. $local_regex = join("|", map {$_->name} @local_list);
  554. $continue = $count;
  555. };
  556. ## need to unguess any locally dependent guesses
  557. my $setguess_header_written = 0;
  558. foreach my $p (@local_list) {
  559. next if ($p->gds ne 'guess');
  560. if (not $setguess_header_written) {
  561. $string .= "\n" . $self->hashes . " unguessing locally dependent guess parameters:\n";
  562. $setguess_header_written = 1;
  563. };
  564. $p->Use(0); # so it doesn't get reported in the log file
  565. my $this = $p->name;
  566. $string .= "set $this = $this\n";
  567. };
  568. ## need to make a mapping of param names back to their objects
  569. my @keys = map {$_->name} @local_list;
  570. my %type = zip(@keys, @local_list);
  571. ## loop through all the data to find and rewrite math expressions
  572. ## that depend on lguesses
  573. foreach my $data (@{$self->data}) {
  574. my $tag = $data->cv || $data->tag;
  575. $tag =~ s{\.}{_}g;
  576. $string .= "\n" . $self->hashes . " local guess and def parameters for " . $data->name . ":\n";
  577. foreach my $p (@{ $self->paths }) {
  578. next if not defined($p);
  579. next if ($p->data->group ne $data->group);
  580. next if not $p->include;
  581. foreach my $pp (qw(e0 ei sigma2 s02 delr third fourth dphase)) {
  582. my $me = $p->$pp;
  583. if ($me =~ m{\b($local_regex)\b}i) {
  584. my $global = $1;
  585. my $local = join("_", $global, $tag);
  586. ## correct this path parameter's math expression
  587. $me =~ s/\b$global\b/$local/g;
  588. $p->$pp($me);
  589. ## define a local guess and rewrite local guesses if not
  590. ## already defined
  591. if (not $created{$local}) {
  592. my ($this_me, $this_type) = ($global, 'guess');
  593. if ($type{$global}->gds eq 'def') {
  594. $this_type = "def";
  595. ($this_me = $type{$global}->mathexp) =~ s{\b($local_regex)\b}{$1_$tag}g;
  596. };
  597. my $new_gds = Demeter::GDS->new(gds => $this_type,
  598. name => $local,
  599. mathexp => $this_me);
  600. $self->push_gds($new_gds);
  601. $string .= $new_gds -> write_gds;
  602. ++$created{$local};
  603. };
  604. };
  605. };
  606. };
  607. ## rewrite remaining defs and all afters
  608. foreach my $ldef (@local_list) {
  609. next if ($ldef->gds !~ m{(?:after|def)});
  610. my $global = $ldef->name;
  611. my $local = join("_", $global, $tag);
  612. my $me = $ldef->mathexp;
  613. $me =~ s{\b($local_regex)\b}{$1_$tag}g;
  614. next if ($created{$local});
  615. my $new_gds = Demeter::GDS->new(gds => $ldef->gds,
  616. name => $local,
  617. mathexp => $me);
  618. $self->push_gds($new_gds);
  619. $string .= $new_gds -> write_gds if ($ldef->gds eq 'def');
  620. $ldef->Use(0) if ($ldef->gds eq 'after');
  621. ++$created{$local};
  622. };
  623. ## next rewrite restraints
  624. my $restraint_header_written = 0;
  625. foreach my $lres (@local_list) {
  626. next if ($lres->gds ne 'restrain');
  627. if (not $restraint_header_written) {
  628. $string .= "\n" . $self->hashes . " local restraints " . $data->name . ":\n";
  629. $restraint_header_written = 1;
  630. };
  631. my $global = $lres->name;
  632. my $local = join("_", $global, $tag);
  633. my $me = $lres->mathexp;
  634. $me =~ s{\b($local_regex)\b}{$1_$tag}g;
  635. next if ($created{$local});
  636. my $new_gds = Demeter::GDS->new(gds => 'restrain',
  637. name => $local,
  638. mathexp => $me);
  639. $self->push_gds($new_gds);
  640. $lres -> Use(0);
  641. $string .= $new_gds -> write_gds;
  642. ++$created{$local};
  643. };
  644. };
  645. ## finally set expandsto attribute of each lguess
  646. foreach my $l (@local_list) {
  647. my $this = $l->name;
  648. my @these = ();
  649. foreach my $c (sort keys %created) {
  650. push(@these, $c) if ($c =~ m{\A$this});
  651. };
  652. $l -> expandsto(join(", ", @these));
  653. };
  654. return $string;
  655. };
  656. # swiped from the old Ifeffit::IO:
  657. # change (3,1,14,5,15,2,13,7,8,6,12) to "1-3,5-8,12-15"
  658. sub _normalize_paths {
  659. my @tmplist; # expand 'X-Y'
  660. map { push @tmplist, ($_ =~ /(\d+)-(\d+)/) ? $1 .. $2 : $_ } @{$_[0]};
  661. my @list = grep /\d+/, @tmplist; # weed out non-integers
  662. @list = sort {$a<=>$b} @list; # sort 'em
  663. my $this = shift(@list);
  664. my $string = $this;
  665. my ($prev, $concat) = ('', '');
  666. while (@list) {
  667. $prev = $this;
  668. $this = shift(@list);
  669. if ($this == $prev+1) {
  670. $concat = "-";
  671. } else {
  672. $concat = ",";
  673. $string .= join("", "-", $prev, $concat, $this);
  674. };
  675. $prev = $this;
  676. };
  677. ($concat eq "-") and $string .= $concat . $this;
  678. $string =~ s{(\d+)-(\1)\b}{$1}g;
  679. return $string || q{};
  680. };
  681. sub evaluate {
  682. my ($self) = @_;
  683. ## retrieve bestfit and errors for gds params, handle annotation
  684. foreach my $gds (@{ $self->gds }) {
  685. $gds->evaluate;
  686. };
  687. ## evaluate all path parameter math expressions
  688. foreach my $path (@{ $self->paths }) {
  689. next if not defined($path);
  690. $path->fetch;
  691. $path->update_path(1);
  692. };
  693. ## get fit and data set statistics (store in fit and data objects respectively)
  694. $self->fetch_statistics;
  695. ## get_parameter values for this fit
  696. $self->fetch_parameters;
  697. ## get correlations
  698. $self->fetch_correlations;
  699. ## get path parameters
  700. $self->fetch_pathresults;
  701. ## set properties
  702. $self->set(time_of_fit=>$self->now, fit_performed=>1);
  703. return $self;
  704. };
  705. my @keys = qw(name description fom time_of_fit fitenvironment interface prepared_by contact);
  706. my @vals = ('Name', 'Description', 'Figure of merit', 'Time of fit', 'Environment', 'Interface', 'Prepared by', 'Contact');
  707. my %properties = zip(@keys, @vals);
  708. sub properties_header {
  709. my ($self, $is_summary) = @_;
  710. my $string = "\n";
  711. foreach my $k (@keys) {
  712. next if ($is_summary and ($k !~ m{name|description|fom}));
  713. if ($k eq 'description') {
  714. my @lines = ($self->$k) ? split($/, $self->$k) : (q{});
  715. $string .= sprintf " %-15s : %s\n", $properties{$k}, shift @lines;
  716. $string .= (sprintf " %-15s %s\n", ' ...', $_) foreach @lines;
  717. } else {
  718. $string .= sprintf " %-15s : %s\n", $properties{$k}, $self->$k;
  719. };
  720. };
  721. return $string;
  722. };
  723. sub summary {
  724. my ($self) = @_;
  725. my $text = q{};
  726. $text .= $self->template("report", "properties_summary");
  727. $text .= $self->template("report", "statistics"); #statistics_report;
  728. $text .= $self->gds_report;
  729. $text .= $self->template("report", "fancyline");
  730. return $text;
  731. };
  732. sub logfile {
  733. my ($self, $fname, $header, $footer) = @_;
  734. $header ||= $self->get('header') || q{};
  735. $footer ||= $self->get('footer') || q{};
  736. open my $LOG, ">$fname";
  737. print $LOG $self->logtext($header, $footer);
  738. close $LOG;
  739. return $self;
  740. };
  741. sub logtext {
  742. my ($self, $header, $footer) = @_;
  743. $header ||= q{};
  744. $footer ||= q{};
  745. $self -> set(header=>$header, footer=>$footer);
  746. ($header .= "\n") if ($header !~ m{\n\z});
  747. #return $self->logcache if $self->logcache;
  748. my $text = q{};
  749. return $text if (not @{ $self->paths });
  750. $text .= $header;
  751. $text .= $self->template("report", "properties"); #properties_header;
  752. $text .= $self->template("report", "fancyline");
  753. $text .= $self->template("report", "statistics"); #statistics_report;
  754. $text .= $self->template("report", "happiness"); #happiness_report;
  755. $text .= $self->gds_report;
  756. $text .= $self->correl_report(); # arg is cormin
  757. foreach my $data (@{ $self->data }) {
  758. next if (not $data->fitting);
  759. $data->rfactor;
  760. if (lc($data->fit_space) eq "r") {
  761. $data->_update("bft");
  762. $data->part_fft("fit") if (lc($data->fitsum) eq 'sum');
  763. };
  764. if (lc($data->fit_space) eq "q") {
  765. $data->_update("bft");
  766. $data->part_fft("fit") if (lc($data->fitsum) eq 'sum');
  767. $data->part_bft("fit") if (lc($data->fitsum) eq 'sum');
  768. };
  769. $text .= $data->fit_parameter_report($#{ $self->data }, $self->fit_performed);
  770. my $length = max( map { length($_->[1]) if ($_->[0] eq $data->group) } @{ $self->pathresults } ) || 10;
  771. $length += 1;
  772. my $pattern = '%-' . $length . 's';
  773. $text .= $self->paths->[0]->row_main_label($length);
  774. foreach my $p (@{ $self->pathresults }) {
  775. next if ($data->group ne $p->[0]);
  776. next if not $p->[11];
  777. $text .= sprintf($pattern, $p->[1]);
  778. $text .= sprintf(" %8.3f %7.3f %9.5f %7.3f %8.5f %8.5f %8.5f\n",
  779. $p->[2], $p->[3], $p->[4], $p->[5], $p->[6], $p->[7], $p->[6]+$p->[7]);
  780. };
  781. $text .= $/;
  782. $text .= $self->paths->[0]->row_second_label($length);
  783. foreach my $p (@{ $self->pathresults }) {
  784. next if ($data->group ne $p->[0]);
  785. next if not $p->[11];
  786. $text .= sprintf($pattern, $p->[1]);
  787. $text .= sprintf(" %9.5f %9.5f %9.5f\n", $p->[8], $p->[9], $p->[10]);
  788. };
  789. };
  790. $text .= $self->template("report", "fancyline");
  791. ($footer .= "\n") if ($footer !~ m{\n\z});
  792. $text .= $footer;
  793. $self->logcache($text);
  794. return $text;
  795. };
  796. sub gds_report {
  797. my ($self) = @_;
  798. my $text = q{};
  799. foreach my $type (qw(guess lguess set def restrain after)) {
  800. my $tt = $type;
  801. my $head = "$type parameters:\n";
  802. my $string = q{};
  803. foreach my $gds (@{ $self->parameters} ) {
  804. ## ## need to not lose guesses that get flagged as local by
  805. ## ## virtue of a math expression dependence
  806. ## if ( ($type eq 'lguess') and ($gds->type) and (not $gds->Use) ) {
  807. ## $string .= " " . $gds->report(0);
  808. ## next;
  809. ## };
  810. next if ($gds->[1] ne $type);
  811. next if (not $gds->[5]);
  812. my $toss = Demeter::GDS->new(name => $gds->[0],
  813. gds => $gds->[1],
  814. mathexp => $gds->[2],
  815. bestfit => $gds->[3],
  816. error => $gds->[4],
  817. );
  818. $string .= " " . $toss->report(0);
  819. $toss->DEMOLISH;
  820. };
  821. if ($string) {
  822. $text.= $head . $string . "\n";
  823. };
  824. };
  825. return $text;
  826. };
  827. sub fetch_statistics {
  828. my ($self) = @_;
  829. ## !!!! need to abstract out these words... see Demeter::Constants
  830. #foreach my $stat (qw(n_idp n_varys chi_square chi_reduced r_factor epsilon_k epsilon_r data_total)) {
  831. foreach my $stat (split(" ", $STATS)) {
  832. $self->$stat(sprintf("%.7f", $self->fetch_scalar($stat)));
  833. };
  834. ## in the case of a sum and with ifeffit, the stats cannot be obtained via the normal mechanism
  835. if ($self->n_idp == 0) {
  836. my $nidp = 0;
  837. foreach my $d (@ {$self->data} ) {
  838. $nidp += $d->nidp;
  839. };
  840. $nidp = 0 if ($nidp < 0);
  841. $self->n_idp(sprintf("%.3f", $nidp));
  842. };
  843. if ($self->n_varys == 0) {
  844. my $nv = 0;
  845. foreach my $g (@ {$self->gds} ) {
  846. ++$nv if ($g->gds eq 'guess');
  847. };
  848. $self->n_varys($nv);
  849. };
  850. if ($self->data_total == 0) {
  851. $self->data_total($#{ $self->data } + 1);
  852. };
  853. # if ($self->epsilon_k == 0) {
  854. # my $which = q{};
  855. # foreach my $d (@ {$self->data} ) {
  856. # ($which = $d) if ($d->fitting);
  857. # };
  858. # $self->epsilon_k($which->epsk);
  859. # $self->epsilon_r($which->epsr);
  860. # };
  861. return 0;
  862. };
  863. # sub statistics_report {
  864. # my ($self) = @_;
  865. # my %things = ("n_idp" => "Independent points ",
  866. # "n_varys" => "Number of variables ",
  867. # "chi_square" => "Chi-square ",
  868. # "chi_reduced" => "Reduced chi-square ",
  869. # "r_factor" => "R-factor ",
  870. # "epsilon_k" => "Measurement uncertainty (k) ",
  871. # "epsilon_r" => "Measurement uncertainty (R) ",
  872. # "data_total" => "Number of data sets ",
  873. # );
  874. # my $string = q{};
  875. # foreach my $stat (split(" ", $STAT_TEXT)) {
  876. # $string .= $self->template("report", "statistics", {name=>$things{$stat}, stat=>$stat});
  877. # ##$string .= sprintf("%s : %s\n", $things{$stat}, $self->$stat||0);
  878. # };
  879. # return $string;
  880. # };
  881. sub happiness_report {
  882. my ($self) = @_;
  883. my $string = sprintf("Happiness = %.5f / 100\t\tcolor = %s\n", $self->happiness, $self->color);
  884. foreach my $line (split "\n", $self->happiness_summary) {
  885. $string .= " $line\n";
  886. };
  887. $string .= "***** Note: happiness is a semantic parameter and should *****\n";
  888. $string .= "***** NEVER be reported in a publication -- NEVER! *****\n";
  889. return $string;
  890. };
  891. sub fetch_parameters {
  892. my ($self) = @_;
  893. $self->clear_parameters;
  894. foreach my $g (@ {$self->gds}) {
  895. $self->push_parameters([$g->name, $g->gds, $g->mathexp, $g->bestfit, $g->error, $g->Use]);
  896. };
  897. };
  898. ## handle correlations: store every correlation as attributes of the
  899. ## object. provide a variety of convenience functions for accessing
  900. ## this information as relatively flat data
  901. ## use the Mode objects feedback attribute (takes a coderef) to gather
  902. ## up the echo-ed text containing the correlations
  903. my @correl_text = ();
  904. sub fetch_correlations {
  905. my ($self) = @_;
  906. my %correlations_of;
  907. if (Demeter->is_ifeffit) {
  908. @correl_text = (); # initialize array buffer for accumulating correlations text
  909. my @save = ($self->toggle_echo(0), # turn screen echo off, saving prior state
  910. $self->get_mode("screen"),
  911. $self->get_mode("plotscreen"),
  912. $self->get_mode("feedback"));
  913. $self->set_mode(screen=>0, plotscreen=>0,
  914. feedback=>sub{push @correl_text, $_[0]}); # set feedback coderef
  915. my $d = $self -> data -> [0];
  916. #my $correl_lines;
  917. #$self->set_mode(buffer=>\$correl_lines);
  918. $self->dispense("fit", "correl");
  919. $self->toggle_echo($save[0]); # reset everything
  920. $self->set_mode(screen=>$save[1], plotscreen=>$save[2], feedback=>$save[3]);
  921. my @gds = map {lc($_->name)} @{ $self->gds };
  922. my $regex = Regexp::Assemble->new()->add(@gds)->re;
  923. foreach my $line (@correl_text) { # parse the correlations text
  924. if ($line =~ m{correl_
  925. ($regex)_ # first variable name followed by underscore
  926. ($regex) # second variable name
  927. \s+=\s+ # space equals space
  928. ($NUMBER) # a number
  929. }xi) {
  930. my ($x, $y, $correl) = ($1, $2, $3);
  931. #print join(" ", $x, $y, $correl), $/;
  932. $correlations_of{$x}{$y} = $correl;
  933. };
  934. if ($line =~ m{correl_
  935. (bkg\d\d_\d\d)_ # bkg parameter followed by an underscore
  936. ($regex) # variable name
  937. \s+=\s+ # space equals space
  938. ($NUMBER) # a number
  939. }xi) {
  940. my ($x, $y, $correl) = ($1, $2, $3);
  941. #print join(" ", $x, $y, $correl), $/;
  942. $correlations_of{$x}{$y} = $correl;
  943. };
  944. if ($self->co->default("fit", "bkg_corr")) {
  945. if ($line =~ m{correl_
  946. (bkg\d\d_\d\d)_ # bkg parameter followed by an underscore
  947. (bkg\d\d_\d\d) # another bkg parameter
  948. \s+=\s+ # space equals space
  949. ($NUMBER) # a number
  950. }xi) {
  951. my ($x, $y, $correl) = ($1, $2, $3);
  952. #print join(" ", $x, $y, $correl), $/;
  953. $correlations_of{$x}{$y} = $correl;
  954. };
  955. };
  956. };
  957. } elsif (Demeter->is_larch) {
  958. my @params = $self->fetch_array(join('.', $self->group, 'params', 'covar_vars'));
  959. foreach my $p1 (@params) {
  960. my %correls = $self->fetch_array(join('.', $self->group, 'params', $p1, 'correl'));
  961. foreach my $p2 (@params) {
  962. next if $p1 eq $p2;
  963. $correlations_of{$p1}{$p2} = $correls{$p2};
  964. };
  965. };
  966. };
  967. ##print Demeter->Dump([\%correlations_of]);
  968. foreach my $k (keys %correlations_of) {
  969. $self->set_correlations($k, $correlations_of{$k});
  970. };
  971. return 0;
  972. };
  973. sub fetch_pathresults {
  974. my ($self) = @_;
  975. $self->clear_pathresults;
  976. foreach my $p (@ {$self->paths}) {
  977. $self->push_pathresults([$p->data->group,
  978. $p->get(qw(name n s02_value sigma2_value
  979. e0_value delr_value reff
  980. ei_value third_value fourth_value include))]);
  981. };
  982. };
  983. sub correl {
  984. my ($self, $x, $y) = @_;
  985. my $value = ($self->exists_in_correlations($x)) ? $self->get_correlations($x)->{$y}
  986. : ($self->exists_in_correlations($y)) ? $self->get_correlations($y)->{$x}
  987. : 0;
  988. return $value;
  989. };
  990. sub all_correl {
  991. my ($self) = @_;
  992. my %all = ();
  993. my %seen = ();
  994. foreach my $x ($self->keys_in_correlations) {
  995. foreach my $y (keys %{ $self->get_correlations($x) } ) {
  996. my $key = join("|", $x, $y);
  997. next if $seen{join("|", $y, $x)};
  998. $all{$key} = $self->get_correlations($x)->{$y} || 0;
  999. ++$seen{$key};
  1000. };
  1001. };
  1002. return %all;
  1003. };
  1004. sub correl_report {
  1005. my ($self, $cormin) = @_;
  1006. my $string = "Correlations between variables:\n";
  1007. $cormin ||= $self->cormin;
  1008. my %all = $self->all_correl;
  1009. my @order = sort {abs($all{$b}) <=> abs($all{$a})} (keys %all);
  1010. foreach my $k (@order) {
  1011. last if (abs($all{$k}) < $cormin);
  1012. my ($x, $y) = split(/\|/, $k);
  1013. $string .= $self->template("report", "correl", {p1=>$x, p2=>$y, correl=>$all{$k}});
  1014. };
  1015. $string .= "All other correlations below $cormin\n" if $cormin;
  1016. return $string;
  1017. };
  1018. sub fetch_gds {
  1019. my ($self, $which) = @_;
  1020. $which = lc($which);
  1021. foreach my $g (@{$self->parameters}) {
  1022. return $g if ($which eq lc($g->[0]));
  1023. };
  1024. return 0;
  1025. };
  1026. sub has_data {
  1027. my ($self, $which) = @_;
  1028. foreach my $g (@{$self->data}) {
  1029. return 1 if ($which->group eq $g->group);
  1030. };
  1031. return 0;
  1032. };
  1033. sub repair_parameters {
  1034. my ($self) = @_;
  1035. my @params = @{$self->parameters};
  1036. my @repaired = ();
  1037. my %seen = ();
  1038. foreach my $p (@params) {
  1039. push @repaired, $p if not $seen{$p->[0]};
  1040. ++$seen{$p->[0]};
  1041. };
  1042. $self->parameters(\@repaired);
  1043. return $self;
  1044. };
  1045. sub grab { # deserialize lite -- grab the yaml
  1046. my ($self, @args) = @_; # without importing any data or paths
  1047. my %args = @args;
  1048. $args{plot} ||= 0;
  1049. $args{file} ||= 0;
  1050. $args{folder} ||= 0;
  1051. $args{regenerate} ||= 0;
  1052. my ($zip, $dpj, $yaml);
  1053. if ($args{file}) {
  1054. $dpj = File::Spec->rel2abs($args{file});
  1055. $self->start_spinner("Demeter is unpacking \"$args{file}\"") if ($self->mo->ui eq 'screen');
  1056. my $folder = $self->project_folder("raw_demeter");
  1057. $zip = Archive::Zip->new();
  1058. carp("Error reading project file ".$args{file}."\n\n"), return 1 unless ($zip->read($dpj) == AZ_OK);
  1059. };
  1060. ## -------- import the fit properties, statistics, correlations
  1061. $yaml = ($args{file}) ? $zip->contents("fit.yaml")
  1062. : $self->slurp(File::Spec->catfile($args{folder}, "fit.yaml"));
  1063. my $rhash = YAML::Tiny::Load($yaml);
  1064. my @array = %$rhash;
  1065. $self -> set(@array);
  1066. $self -> fit_performed(0);
  1067. my $structure = ($args{file}) ? $zip->contents('structure.yaml')
  1068. : $self->slurp(File::Spec->catfile($args{folder}, 'structure.yaml'));
  1069. my ($r_gdsnames, $r_data, $r_paths, $r_feff) = YAML::Tiny::Load($structure); # vpaths...
  1070. $self->datagroups($r_data);
  1071. my @data = ();
  1072. foreach my $d (@$r_data) {
  1073. #print join("|", $self->name, $d, $self->mo->fetch('Data', $d)), $/;
  1074. #print ">>>>>>> $d\n";
  1075. foreach my $which ('', '_standard') {
  1076. my $yaml = ($args{file}) ? $zip->contents("$d$which.yaml")
  1077. : $self->slurp(File::Spec->catfile($args{folder}, "$d$which.yaml"));
  1078. my ($r_attributes, $r_x, $r_y) = YAML::Tiny::Load($yaml);
  1079. ## the current implementation of XDI support has the xdifile attribute read-only if Xray::XDI is not available
  1080. delete $r_attributes->{xdifile} if (not $INC{'Xray/XDI.pm'});
  1081. delete $r_attributes->{fit_pcpath}; # correct an early
  1082. delete $r_attributes->{fit_do_pcpath}; # design mistake...
  1083. ## correct for earlier XDI design
  1084. foreach my $x (qw(xdi_mu_reference xdi_ring_current xdi_abscissa xdi_start_time
  1085. xdi_crystal xdi_focusing xdi_mu_transmission xdi_ring_energy
  1086. xdi_collimation xdi_d_spacing xdi_undulator_harmonic xdi_mu_fluorescence
  1087. xdi_end_time xdi_source xdi_edge_energy xdi_harmonic_rejection
  1088. xdi_mono xdi_sample xdi_scan xdi_extensions xdi_applications
  1089. xdi_labels xdi_detector xdi_beamline xdi_column xdi_comments xdi_version
  1090. xdi_facility
  1091. )) {
  1092. delete $r_attributes->{$x};
  1093. };
  1094. ## correct for change in energy-dependent normalization
  1095. delete $r_attributes->{bkg_fnorm};
  1096. # if (ref($r_attributes->{xdi_beamline}) ne 'HASH') {
  1097. # $r_attributes->{xdi_beamline} = {name=>$r_attributes->{xdi_beamline}||q{}};
  1098. # };
  1099. my %hash = %$r_attributes;
  1100. next if not exists $hash{group};
  1101. #Demeter->trace;
  1102. #print '>>>>', $hash{group}, $/;
  1103. my $savecv = $self->mo->datacount;
  1104. my $this = $self->mo->fetch('Data', $hash{group}) || Demeter::Data -> new(group=>$hash{group});
  1105. delete $hash{group};
  1106. $this->set(%hash);
  1107. if ($which eq '') {
  1108. $this->cv($r_attributes->{cv}||0);
  1109. $self->mo->datacount($savecv);
  1110. };
  1111. #$datae{$d} = $this;
  1112. #$datae{$this->group} = $this;
  1113. if ($this->datatype eq 'xmu') {
  1114. $this->dispense('fit', 'group');
  1115. $self->place_array($this->group.".energy", $r_x);
  1116. $self->place_array($this->group.".xmu", $r_y);
  1117. } elsif ($this->datatype eq 'chi') {
  1118. $this->dispense('fit', 'group');
  1119. $self->place_array($this->group.".k", $r_x);
  1120. $self->place_array($this->group.".chi", $r_y);
  1121. };
  1122. $this -> set(update_data=>0, update_columns=>0, fit_group=>q{});
  1123. push @data, $this;
  1124. };
  1125. };
  1126. $self->data(\@data);
  1127. $self->repair_parameters;
  1128. $self->grabbed(1);
  1129. $self->thawed(0);
  1130. };
  1131. ## ------------------------------------------------------------
  1132. ## Serialization and deserialization of the Fit object
  1133. override 'serialization' => sub {
  1134. my ($self) = @_;
  1135. my @gds = @{ $self->gds };
  1136. my @data = @{ $self->data };
  1137. my @paths = @{ $self->paths };
  1138. my @vpaths = @{ $self->vpaths };
  1139. my @gdsgroups = map { $_->group } @gds;
  1140. my @datagroups = map { $_->group } @data;
  1141. my @pathsgroups = map { $_->group } grep {defined $_} @paths;
  1142. my @feffgroups = map { $_ ? $_->group : q{} } map {$_ -> parent} grep {defined $_} @paths;
  1143. my @vpathsgroups = map { $_->group } grep {defined $_} @vpaths;
  1144. @feffgroups = uniq @feffgroups;
  1145. my $text = "# gdsgroups, datagroups, pathsgroups, feffgroups, vpathsgroups\n";
  1146. $text .= YAML::Tiny::Dump(\@gdsgroups, \@datagroups, \@pathsgroups, \@feffgroups, \@vpathsgroups);
  1147. $text .= "\n";
  1148. my %hash = $self->all;
  1149. $text .= YAML::Tiny::Dump(\%hash);
  1150. return $text;
  1151. };
  1152. override 'serialize' => sub {
  1153. my ($self, @args) = @_;
  1154. my %args = @args; # coerce args into a hash
  1155. $args{tree} ||= File::Spec->catfile($self->project_folder("raw_demeter"), 'fit');
  1156. $args{folder} ||= $self->group;
  1157. $args{file} ||= $args{project};
  1158. ($args{nozip} = 1) if not $args{file};
  1159. $args{copyfeff} ||= 1;
  1160. my @gds = @{ $self->gds };
  1161. my @data = @{ $self->data };
  1162. my @paths = @{ $self->paths };
  1163. my @vpaths = @{ $self->vpaths };
  1164. my @gdsgroups = map { $_->group } @gds;
  1165. my @datagroups = map { $_->group } @data;
  1166. my @pathsgroups = map { $_->group } grep {defined $_} @paths;
  1167. my @feffgroups = map { $_ ? $_->group : q{} } map {$_ -> parent} grep {defined $_} @paths;
  1168. my @vpathsgroups = map { $_->group } grep {defined $_} @vpaths;
  1169. @feffgroups = uniq @feffgroups;
  1170. unlink ($args{file}) if ($args{file} and (-e $args{file}));
  1171. $self->folder(File::Spec->catfile($args{tree}, $args{folder}));
  1172. mkpath($self->folder);
  1173. ## -------- save a yaml containing the structure of the fit
  1174. my $structure = File::Spec->catfile($self->folder, "structure.yaml");
  1175. open my $STRUCTURE, ">$structure";
  1176. print $STRUCTURE YAML::Tiny::Dump(\@gdsgroups, \@datagroups, \@pathsgroups, \@feffgroups, \@vpathsgroups);
  1177. close $STRUCTURE;
  1178. ## -------- save a yaml containing all GDS parameters
  1179. my $gdsfile = File::Spec->catfile($self->folder, "gds.yaml");
  1180. open my $gfile, ">$gdsfile";
  1181. foreach my $p (@gds) {
  1182. print $gfile $p->serialization;
  1183. };
  1184. close $gfile;
  1185. ## -------- save a yaml for each data file
  1186. foreach my $d (@data) {
  1187. my $dd = $d->group;
  1188. $d -> file($NULLFILE) if $d->prjrecord;
  1189. my $datafile = File::Spec->catfile($self->folder, "$dd.yaml");
  1190. $d -> serialization($datafile);
  1191. $d -> serialize($datafile);
  1192. if ($d->bkg_stan ne 'None') {
  1193. my $stan = $d->mo->fetch('Data', $d->bkg_stan);
  1194. $datafile = File::Spec->catfile($self->folder, $dd."_standard.yaml");
  1195. $stan -> serialization($datafile);
  1196. $stan -> serialize($datafile);
  1197. };
  1198. };
  1199. ## -------- save a yaml containing the paths
  1200. my $pathsfile = File::Spec->catfile($self->folder, "paths.yaml");
  1201. my %feffs = ();
  1202. open my $PATHS, ">$pathsfile";
  1203. foreach my $p (@paths) {
  1204. next if not defined($p);
  1205. print $PATHS $p->serialization;
  1206. if ($p->sp) { # this path used a ScatteringPath object
  1207. my $this = sprintf("%s", $p->parent);
  1208. $feffs{$this} = $p->get("parent");
  1209. } else { # this path imported a feffNNNN.dat file
  1210. 1;
  1211. };
  1212. };
  1213. close $PATHS;
  1214. ## -------- save a yaml containing the vpaths
  1215. my $vpathsfile = File::Spec->catfile($self->folder, "vpaths.yaml");
  1216. open my $VPATHS, ">$vpathsfile";
  1217. foreach my $vp (@vpaths) {
  1218. next if not defined($vp);
  1219. print $VPATHS $vp->serialization;
  1220. };
  1221. close $VPATHS;
  1222. ## -------- save yamls and phase.bin for the feff calculations (turn
  1223. ## this off in Artemis, where interaction with feff files
  1224. ## is handled somewhat differently)
  1225. if ($args{copyfeff}) {
  1226. foreach my $f (values %feffs) {
  1227. next if not defined($f);
  1228. my $ff = $f->group;
  1229. my $feffyaml = File::Spec->catfile($self->folder, $ff.".yaml");
  1230. $f->serialize($feffyaml, 1);
  1231. my $feff_from = File::Spec->catfile($f->get("workspace"), "original_feff.inp");
  1232. my $feff_to = File::Spec->catfile($self->folder, $ff.".inp");
  1233. copy($feff_from, $feff_to);
  1234. my $phase_from = File::Spec->catfile($f->get("workspace"), "phase.bin");
  1235. my $phase_to = File::Spec->catfile($self->folder, $ff.".bin");
  1236. copy($phase_from, $phase_to);
  1237. if (-e File::Spec->catfile($f->get("workspace"), "files.dat")) {
  1238. my $files_from = File::Spec->catfile($f->get("workspace"), "files.dat");
  1239. my $files_to = File::Spec->catfile($self->folder, $ff.".files");
  1240. copy($files_from, $files_to);
  1241. };
  1242. };
  1243. };
  1244. ## -------- save a yaml containing the fit properties
  1245. my @properties = grep {$_ !~ m{\A(?:gds|data|paths|vpaths|project|folder|rate|thingy|progress)\z}} $self->meta->get_attribute_list;
  1246. push @properties, 'name';
  1247. my @vals = $self->get(@properties);
  1248. my %props = zip(@properties, @vals);
  1249. my $propsfile = File::Spec->catfile($self->folder, "fit.yaml");
  1250. open my $PROPS, ">$propsfile";
  1251. print $PROPS YAML::Tiny::Dump(\%props);
  1252. close $PROPS;
  1253. ## -------- write fit and log files to the folder
  1254. foreach my $d (@data) {
  1255. my $dd = $d->group;
  1256. $d -> _update("bft");
  1257. $d -> dispense('fit', 'zeros') if not $self->fitted;
  1258. $d -> save("fit", File::Spec->catfile($self->folder, $dd.".fit"));
  1259. };
  1260. $self -> logfile(File::Spec->catfile($self->folder, "log"), $self->header, $self->footer);
  1261. ## -------- finally save a yaml containing the Plot object
  1262. my $plotfile = File::Spec->catfile($self->folder, "plot.yaml");
  1263. open my $PLOT, ">$plotfile";
  1264. print $PLOT $self->po->serialization;
  1265. close $PLOT;
  1266. if ($args{file}) {
  1267. my $readme = File::Spec->catfile($self->share_folder, "Readme.fit_serialization");
  1268. my $target = File::Spec->catfile($self->folder, "Readme");
  1269. copy($readme, $target);
  1270. open(my $touch, '>', File::Spec->catfile($self->folder, "FIT.SERIALIZATION"));
  1271. close $touch;
  1272. if (not $args{nozip}) {
  1273. $self->zip_project($self->folder, $args{file});
  1274. rmtree($self->folder);
  1275. };
  1276. };
  1277. return $self;
  1278. };
  1279. override 'deserialize' => sub {
  1280. my ($self, @args) = @_;
  1281. my %args = @args;
  1282. $args{plot} ||= 0;
  1283. $args{file} ||= 0;
  1284. $args{folder} ||= 0;
  1285. $args{regenerate} ||= 0;
  1286. my (%datae, %sps, %parents, $dpj, $zip, $project_folder);
  1287. if ($args{file}) {
  1288. $dpj = File::Spec->rel2abs($args{file});
  1289. $self->start_spinner("Demeter is unpacking \"$args{file}\"") if ($self->mo->ui eq 'screen');
  1290. my $folder = $self->project_folder("raw_demeter");
  1291. $zip = Archive::Zip->new();
  1292. carp("Error reading project file ".$args{file}."\n\n"), return 1 unless ($zip->read($dpj) == AZ_OK);
  1293. };
  1294. my $structure = ($args{file}) ? $zip->contents('structure.yaml')
  1295. : $self->slurp(File::Spec->catfile($args{folder}, 'structure.yaml'));
  1296. my ($r_gdsnames, $r_data, $r_paths, $r_feff) = YAML::Tiny::Load($structure); # vpaths...
  1297. $r_data = [] if not defined($r_data);
  1298. $self->datagroups($r_data);
  1299. ## -------- import the data
  1300. my @data = ();
  1301. $self->call_sentinal("Importing data");
  1302. foreach my $d (@$r_data) {
  1303. #print ">>>>>>> $d\n";
  1304. my $yaml = ($args{file}) ? $zip->contents("$d.yaml")
  1305. : $self->slurp(File::Spec->catfile($args{folder}, "$d.yaml"));
  1306. my ($r_attributes, $r_x, $r_y) = YAML::Tiny::Load($yaml);
  1307. ## the current implementation of XDI support has the xdifile attribute read-only if Xray::XDI is not available
  1308. delete $r_attributes->{xdifile} if (not $INC{'Xray/XDI.pm'});
  1309. delete $r_attributes->{fit_pcpath}; # correct an early
  1310. delete $r_attributes->{fit_do_pcpath}; # design mistake...
  1311. ## correct for change in energy-dependent normalization
  1312. delete $r_attributes->{bkg_fnorm};
  1313. ## clean up from old implementation(s) of XDI
  1314. foreach my $x (qw(xdi_mu_reference xdi_ring_current xdi_abscissa xdi_start_time
  1315. xdi_crystal xdi_focusing xdi_mu_transmission xdi_ring_energy
  1316. xdi_collimation xdi_d_spacing xdi_undulator_harmonic xdi_mu_fluorescence
  1317. xdi_end_time xdi_source xdi_edge_energy xdi_harmonic_rejection
  1318. xdi_mono xdi_sample xdi_scan xdi_extensions xdi_applications
  1319. xdi_labels xdi_detector xdi_beamline xdi_column xdi_comments xdi_version
  1320. xdi_facility
  1321. )) {
  1322. delete $r_attributes->{$x};
  1323. };
  1324. #if (ref($r_attributes->{xdi_beamline}) ne 'HASH') {
  1325. # $r_attributes->{xdi_beamline} = {name=>$r_attributes->{xdi_beamline}||q{}};
  1326. #};
  1327. my %hash = %$r_attributes;
  1328. next if not exists $hash{group};
  1329. #Demeter->trace;
  1330. #print '>>>>', $hash{group}, $/;
  1331. my $savecv = $self->mo->datacount;

Large files files are truncated, but you can click here to view the full file