~ubuntu-branches/ubuntu/gutsy/horae/gutsy

« back to all changes in this revision

Viewing changes to Xray/Atoms.pm

  • Committer: Bazaar Package Importer
  • Author(s): Carlo Segre
  • Date: 2006-12-28 12:36:48 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20061228123648-9xnjr76wfthd92cq
Tags: 064-1
New upstream release, dropped dependency on libtk-filedialog-perl.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/bin/perl -w
 
2
######################################################################
 
3
## This is the Xray::Atoms.pm module.  It contains various subroutines
 
4
## used by the various versions of atoms.  See the pod for details and
 
5
## one of the atoms versions for how they are implemented.
 
6
##
 
7
##  This program is copyright (c) 1998-2006 Bruce Ravel
 
8
##  <bravel@anl.gov>
 
9
##  http://cars9.uchicago.edu/~ravel/software/
 
10
##
 
11
## -------------------------------------------------------------------
 
12
##     All rights reserved. This program is free software; you can
 
13
##     redistribute it and/or modify it under the same terms as Perl
 
14
##     itself.
 
15
##
 
16
##     This program is distributed in the hope that it will be useful,
 
17
##     but WITHOUT ANY WARRANTY; without even the implied warranty of
 
18
##     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
19
##     Artistic License for more details.
 
20
## -------------------------------------------------------------------
 
21
##
 
22
######################################################################
 
23
## Time-stamp: <06/01/02 11:56:22 bruce>
 
24
######################################################################
 
25
## Code:
 
26
 
 
27
=head1 NAME
 
28
 
 
29
Xray::Atoms - Utilities and data structures for the Atoms program
 
30
 
 
31
=head1 SYNOPSIS
 
32
 
 
33
  use Xray::Xtal;
 
34
  use Xray::Atoms qw(parse_input keyword_defaults parse_atp
 
35
                     absorption mcmaster);
 
36
 
 
37
=head1 DESCRIPTION
 
38
 
 
39
This module contains the utility subroutines used by the program
 
40
Atoms.  It defines a number of exportable routines for performing
 
41
chores specific to Atoms.  It also defines an object for storing input
 
42
data to the various programs which use this file.  For details about
 
43
the crystallographic objects used by Atoms, see L<Xray::Xtal>.
 
44
 
 
45
There are several versions of Atoms with different user interfaces all
 
46
of which use routines from this module.  These include the command
 
47
line, Tk, and CGI versions.
 
48
 
 
49
=cut
 
50
;
 
51
 
 
52
package Xray::Atoms;
 
53
 
 
54
use strict;
 
55
use vars qw($VERSION $atoms_dir $languages @available_languages
 
56
            $cvs_info $module_version $messages @ISA @EXPORT @EXPORT_OK);
 
57
 
 
58
require Exporter;
 
59
 
 
60
@ISA = qw(Exporter AutoLoader);
 
61
# Items to export into callers namespace by default. Note: do not export
 
62
# names by default without a very good reason. Use EXPORT_OK instead.
 
63
# Do not simply export all your public functions/methods/constants.
 
64
@EXPORT_OK = qw(build_cluster number rcdirectory rcfile_name
 
65
                absorption mcmaster i_zero self);
 
66
 
 
67
$VERSION = '3.0beta10';
 
68
$cvs_info = '$Id: Atoms.pm,v 1.40 2001/12/21 02:28:44 bruce Exp $ ';
 
69
$module_version = 1.41; #(split(' ', $cvs_info))[2];
 
70
 
 
71
use Carp;
 
72
##use Safe;
 
73
use Xray::Xtal;
 
74
use Xray::Absorption;
 
75
use Chemistry::Elements qw(get_name get_Z get_symbol);
 
76
use Statistics::Descriptive;
 
77
use Text::Abbrev;
 
78
use File::Basename;
 
79
use Ifeffit::FindFile;
 
80
my $STAR_Parser_exists = (eval "require STAR::Parser");
 
81
if ($STAR_Parser_exists) {
 
82
  import STAR::Parser;
 
83
  require STAR::DataBlock;
 
84
  import STAR::DataBlock;
 
85
};
 
86
 
 
87
#use constant EV2RYD => 13.605698;
 
88
use constant EPSILON => 0.00001;
 
89
 
 
90
my $elem_match = '([BCFHIKNOPSUVWY]|A[cglrstu]|B[aeir]|C[adelorsu]|Dy|E[ru]|F[er]|G[ade]|H[efgo]|I[nr]|Kr|L[aiu]|M[gno]|N[abdeip]|Os|P[abdmortu]|R[abehnu]|S[bceimnr]|T[abcehilm]|Xe|Yb|Z[nr])';
 
91
 
 
92
## location of atp files
 
93
use File::Spec;
 
94
use vars qw($atp_dir $lib_dir $is_windows);
 
95
$is_windows = $Ifeffit::FindFile::is_windows;
 
96
$atoms_dir = Xray::Xtal::identify_self();
 
97
$atp_dir = ($is_windows) ?
 
98
  Ifeffit::FindFile->find("atoms", "atp_sys") :
 
99
  File::Spec->catfile($atoms_dir, "atp");
 
100
$lib_dir = ($is_windows) ?
 
101
  Ifeffit::FindFile->find("atoms", "xray_lib") :
 
102
  File::Spec->catfile($atoms_dir, "lib");
 
103
 
 
104
## my $languagerc = (($^O eq 'MSWin32') or ($^O eq 'cygwin')) ?
 
105
##   File::Spec->catfile($lib_dir, "languages");
 
106
## eval "do '$languagerc'" or warn "Language rc file not found.  Using English.$/";
 
107
 
 
108
 
 
109
use vars qw(%meta %colors %fonts);
 
110
## use vars qw($always_write_feff $atoms_language $write_to_pwd
 
111
##          $prefer_feff_eight $absorption_tables $dafs_default
 
112
##          $plotting_hook $default_filepath $unused_modifier
 
113
##          $display_balloons $no_crystal_warnings $one_frame $convolve_dafs
 
114
##          $never_ask_to_save $ADB_location);
 
115
%meta = (ADB_location        => "http://cars9.uchicago.edu/atomsdb/",
 
116
         absorption_tables   => 'elam',
 
117
         always_write_feff   => 0,
 
118
         atoms_language      => 'english',
 
119
         convolve_dafs       => 1,
 
120
         dafs_default        => 'cl',
 
121
         default_filepath    => '',
 
122
         display_balloons    => 1,
 
123
         never_ask_to_save   => 0,
 
124
         no_crystal_warnings => 0,
 
125
         one_frame           => 1,
 
126
         plotting_hook       => '',
 
127
         prefer_feff_eight   => 0,
 
128
         unused_modifier     => 'Shift',
 
129
         write_to_pwd        => 1,
 
130
        );
 
131
 
 
132
## keep these refs to anon hashes so as not to break rcfiles from
 
133
## alpha16 and earlier
 
134
## use vars qw();       # unused variables from atomsrc
 
135
## ## must declare variables for colors and fonts for TkAtoms
 
136
## use vars qw($c_foreground $c_background $c_trough $c_entry $c_label
 
137
##          $c_balloon $c_button $c_buttonActive $c_sgbActive $c_sgbGroup
 
138
##          $c_done $c_todo $c_plot
 
139
##          $f_balloon $f_label $f_menu $f_button $f_header $f_entry $f_sgb);
 
140
 
 
141
use vars qw($rcfile);
 
142
$rcfile = (($^O eq 'MSWin32') or ($^O eq 'cygwin')) ?
 
143
  File::Spec->catfile($lib_dir, "tkatoms.ini"):
 
144
  File::Spec->catfile($lib_dir, "atomsrc");
 
145
(-e $rcfile) and &read_rc($rcfile);
 
146
my $users_rc = &rcfile_name;
 
147
(-e $users_rc) and &read_rc($users_rc);
 
148
$meta{atoms_language}    = lc($meta{atoms_language});
 
149
$meta{absorption_tables} = lc($meta{absorption_tables});
 
150
Xray::Absorption -> load($meta{absorption_tables});
 
151
#Xray::Absorption -> load('Elam');
 
152
$meta{unused_modifier}   = ucfirst($meta{unused_modifier});
 
153
($meta{unused_modifier} =~ /Alt|Control|Meta|Shift/) or
 
154
  $meta{unused_modifier} = 'Shift';
 
155
 
 
156
my $language_file;
 
157
if (not $languages) {
 
158
  $language_file = (($^O eq 'MSWin32') or ($^O eq 'cygwin')) ?
 
159
    File::Spec->catfile($lib_dir, "atomsrc.en"):
 
160
      File::Spec->catfile($lib_dir, "atomsrc.en");
 
161
} else {
 
162
  $language_file = "atomsrc." . $$languages{$meta{atoms_language}};
 
163
  $language_file = (($^O eq 'MSWin32') or ($^O eq 'cygwin')) ?
 
164
    File::Spec->catfile($lib_dir, $language_file):
 
165
      File::Spec->catfile($lib_dir, $language_file);
 
166
  unless (-e $language_file) {
 
167
    $language_file = (($^O eq 'MSWin32') or ($^O eq 'cygwin')) ?
 
168
      File::Spec->catfile($lib_dir, "atomsrc.en"):
 
169
        File::Spec->catfile($lib_dir, "atomsrc.en");
 
170
  };
 
171
};
 
172
eval "do '$language_file'";     # read strings
 
173
($meta{atoms_language} eq 'english') or
 
174
  Xray::Xtal::set_language($meta{atoms_language});
 
175
 
 
176
my $overfull_margin = 0.1;
 
177
 
 
178
# Preloaded methods go here.
 
179
 
 
180
=head1 THE KEYWORDS OBJECT
 
181
 
 
182
To simplify the handling of input data, this module provides an
 
183
object.  It is used like this:
 
184
 
 
185
   my $keywords = Xray::Atoms -> new();
 
186
   $keywords -> make('rmax' => 5.7)
 
187
 
 
188
The make method is the general way of storing data in the keywords
 
189
object.  In general, this method assumes that the keyword takes a
 
190
scalar value.  The data structure is just a hash, so the keyword can
 
191
be anything.  Indeed, keyword recognition is not done in this module.
 
192
Keyword/value pairs are just blindly stored for later processing.
 
193
Several keywords are treated specially because they are commonly used
 
194
in the programs included in the Atoms package.  These keywords
 
195
 
 
196
    a b c alpha beta gamma argon krypton nitrogen rmax
 
197
 
 
198
are treated as numbers.  This means that they are eval-ed (and so can
 
199
be short expressions like 1/2 or 3.5+0.001) and return 0 if they
 
200
cannot be interpreted as numbers.  This evaluation is done in a
 
201
taint-safe manner.
 
202
 
 
203
A couple of keywords are treated specially.
 
204
 
 
205
  $keywords -> make(title=>'This is a title!');
 
206
 
 
207
pushes the given string onto an anonymous array of strings.  In this
 
208
way, any number of title lines can be kept in the object.
 
209
 
 
210
The C<shift> keyword is also handled specially.  This takes three
 
211
arguments, the three components of the shift vector.  A syntax like
 
212
this works fine:
 
213
 
 
214
  $keywords -> make('shift' => $x, $y, $z);
 
215
 
 
216
This stores the three values of the shift vector as an anonymous
 
217
array.
 
218
 
 
219
 
 
220
=cut
 
221
 
 
222
 
 
223
sub new {
 
224
  my $classname = shift;
 
225
  my $self = {};
 
226
                                # fuctional keywords
 
227
  $self->{'title'}    = [];
 
228
  $self->{'edge'}     = "";
 
229
  $self->{'core'}     = "";
 
230
  $self->{'argon'}    = 0;
 
231
  $self->{'krypton'}  = 0;
 
232
  $self->{'nitrogen'} = 0;
 
233
  $self->{'rmax'}     = 0;
 
234
  $self->{'shift'}    = [0,0,0];
 
235
                                # lattice parameters
 
236
  $self->{'space'}    = '';
 
237
  $self->{'a'}        = 0;
 
238
  $self->{'b'}        = 0;
 
239
  $self->{'c'}        = 0;
 
240
  $self->{'alpha'}    = 0;
 
241
  $self->{'beta'}     = 0;
 
242
  $self->{'gamma'}    = 0;
 
243
                                # sites
 
244
  $self->{'sites'}    = [];
 
245
                                # operational keywords
 
246
  $self->{'overfull_margin'} = 0.1;
 
247
  $self->{'found_output'} = 0;
 
248
  $self->{'quiet'} = 0;
 
249
  $self->{'program'} = 'atoms';
 
250
                                # atp flags
 
251
  my @atpfiles = ();
 
252
  my @atp_dir_list = ();
 
253
  push @atp_dir_list, $Xray::Atoms::atp_dir;
 
254
  push @atp_dir_list, File::Spec->catfile(&rcdirectory, "atp");
 
255
  foreach my $dir (@atp_dir_list) {
 
256
    if (-d $dir) {
 
257
      opendir (ATPDIR, $dir) ||
 
258
        die $$messages{'no_directory'} . " " . $dir . $/;
 
259
      push @atpfiles, grep /\.atp/, readdir ATPDIR;
 
260
      closedir ATPDIR;
 
261
    };
 
262
  };  ## feff feff8 p1 unit alchemy xyz geom symmetry test
 
263
  $self->{'files'}     = {};
 
264
                                # feff potentials
 
265
  $self->{'ipots'}         = 'species'; # 'sites'
 
266
  $self->{'ipot_vals'}     = {};
 
267
  $self->{'always_feff'}   = $meta{always_write_feff};
 
268
  $self->{'prefer_feff_8'} = $meta{prefer_feff_eight};
 
269
  $self->{'language'}      = $meta{atoms_language};
 
270
  $self->{'write_to_pwd'}  = $meta{write_to_pwd};
 
271
 
 
272
  bless($self, $classname);
 
273
  return $self;
 
274
};
 
275
# dafs
 
276
##"fdat"        => 0,           "refile"      => "reflect.dat",
 
277
##"qvec"        => (0,0,0),     "nepoints"    => 100,
 
278
##"feout"       => "fe.dat",    "egrid"       => 1,
 
279
##"reflections" => (0,0,0),     "noanomalous" => 0 );
 
280
## corrections, index
 
281
 
 
282
 
 
283
sub make {
 
284
  my $self = shift;
 
285
  unless (($#_ % 2) || (grep /\b(files|sites)\b/, @_)) {
 
286
    my $this = (caller(0))[3];
 
287
    croak "$this " . $$messages{make_error};
 
288
    return;
 
289
  };
 
290
 
 
291
  my $die = $self->{die} || 1;
 
292
  while (@_) {
 
293
    my $att   = lc(shift);
 
294
    my $value = shift;
 
295
  KEYWORDS: {
 
296
      ($att eq 'title') && do {
 
297
        foreach my $l (split(/\n/, $value)) {
 
298
          push @{$self->{'title'}}, $l unless ($l =~ /^\s*$/);
 
299
        };
 
300
        last KEYWORDS;
 
301
      };
 
302
      ($att =~ /(shift|qvec)/) && do {
 
303
        my $v1 = number($value, $die, $self);
 
304
        my $v2 = number(shift,  $die, $self);
 
305
        my $v3 = number(shift,  $die, $self);
 
306
        $self->{$1} = [$v1, $v2, $v3];
 
307
        last KEYWORDS;
 
308
      };
 
309
      ($att =~ /^(argon|krypton|nitrogen|rmax)$/) && do {
 
310
        $self->{$1} = number($value, $die, $self);
 
311
        last KEYWORDS;
 
312
      };
 
313
      ($att =~ /^(a|b|c|alpha|beta|gamma)$/) && do {
 
314
        $self->{$1} = number($value, $die, $self);
 
315
        last KEYWORDS;
 
316
      };
 
317
      ($att eq 'atp') && do {
 
318
        $value = $value;
 
319
        $self->{atp}{$value} = 1;
 
320
        last KEYWORDS;
 
321
      };
 
322
      ($att eq 'files') && do {
 
323
        $value = lc($value);
 
324
        my $v2 = shift;
 
325
        ($v2 =~ /^\s*$/) and $v2 = undef;
 
326
        $self->{files}{$value} = $v2;
 
327
        last KEYWORDS;
 
328
      };
 
329
      ($att eq 'sites') && do {
 
330
        my $e = $value;
 
331
        my $x = number(shift, $die, $self);
 
332
        my $y = number(shift, $die, $self);
 
333
        my $z = number(shift, $die, $self);
 
334
        my $t = shift;
 
335
        my $o = number(shift, $die, $self);
 
336
        push @{$self->{'sites'}}, [$e, $x, $y, $z, $t, $o];
 
337
        last KEYWORDS;
 
338
      };
 
339
      do {
 
340
        $self->{$att} = $value;
 
341
        last KEYWORDS;
 
342
      };
 
343
 
 
344
    };
 
345
 
 
346
  };
 
347
 
 
348
  return $self;
 
349
};
 
350
 
 
351
 
 
352
=head1 METHODS
 
353
 
 
354
=head2 C<parse_input>
 
355
 
 
356
This method is used by atoms to read an atoms input file.  It is
 
357
typically called by
 
358
 
 
359
  $keywords -> parse_input($file, 0);
 
360
 
 
361
The first argument is the name of the input file.  The input file is
 
362
presumed to be of the atoms.inp sort unless the file extension is
 
363
C<.cif>, in which case it is presumed to be a CIF file.  This check is
 
364
made case-insensitively.
 
365
 
 
366
The second argument is tells the error trapping mechnism what kind of
 
367
environment you are working in.  If you have written a command-line
 
368
program, it should be 0.  In a Tk program, it should 1.  In a CGI
 
369
script it should be 2.
 
370
 
 
371
The contents of the file are parsed and stored in a keywords object.
 
372
The method is fairly clever about interpreting the file and offering
 
373
warning and error messages.  It interprets numbers in a fairly secure
 
374
manner, thus allowing simple math expressions to be used in the atom
 
375
list and as the shift vectors.
 
376
 
 
377
Alternately, you can pass a CIF file with the syntax
 
378
 
 
379
  $keywords -> parse_input($file, 0, 'cif');
 
380
 
 
381
or
 
382
 
 
383
  $keywords -> parse_input($file, 0, 'cif', 2);
 
384
 
 
385
The third argument is "cif" or "inp" to specify the input file type.
 
386
The fourth argument tells the CIF parser which structure to use from a
 
387
multi-structure file.
 
388
 
 
389
=cut
 
390
 
 
391
$$::messages{adb_unknown} = "You requested an unknown ADB file";
 
392
sub parse_input {
 
393
 
 
394
  my $href = abbrev qw(a alpha argon atoms b basis beta c core corrections
 
395
                       edge egrid emax emin egrid estep dopants fdat feff
 
396
                       feff8 gamma geom index ipots krypton
 
397
                       nepoints nitrogen noanomalous out output p1
 
398
                       qvec refile reflection reflections rmax shift
 
399
                       space title unit xanes);
 
400
 
 
401
  my $keys = shift;
 
402
  my ($file, $die, $type, $entry) = @_;
 
403
  $type ||= 'inp';
 
404
  $type = ($type =~ /cif/i) ? "cif" : "inp";
 
405
  $entry ||= 0;
 
406
  my ($nsites, $ntitles, $iline) = (0,0,0);
 
407
 
 
408
  ## is this a CIF file?
 
409
  my ($nn,$pp,$suff) = fileparse($file,
 
410
                                 ".inp", ".INP", ".Inp",
 
411
                                 ".cif", ".CIF", ".Cif");
 
412
  cif($keys, $file ,$entry), return
 
413
    if ($STAR_Parser_exists and ((lc($type) eq 'cif') or ($suff =~ /cif$/i)));
 
414
 
 
415
 
 
416
                                ## divy up the keywords into birds of
 
417
                                ## a feather
 
418
  my @normal = ("argon", "center", "core", "ipots", "krypton",
 
419
                "nitrogen", "rmax");
 
420
  ##my @logicals = ();
 
421
  my @threevecs    = ("shift", "dafs", "qvec", "reflection");
 
422
  my @cellwords    = ("a", "alpha", "b", "beta", "c", "gamma");
 
423
  my @output_types = ("feff", "feff8", "p1", "geom", "unit", "out");
 
424
  my @dafs         = ("emin", "emax", "estep", "egrid");
 
425
  my @deprecated   = ("fdat", "nepoints", "xanes", "modules",
 
426
                      "message", "noanomalous", "self", "i0", "mcmaster",
 
427
                      "dwarf", "reflections", "refile",
 
428
                      "egrid", "index", "corrections");
 
429
  my ($term, $prompt, $stdin);
 
430
  my $fh;
 
431
  if ($file =~ /^http:/) {
 
432
    if (eval "require LWP::Simple") {
 
433
      unless (LWP::Simple::head($file)) { # handle unknown ADB file
 
434
        $keys -> warn_or_die("$$::messages{adb_unknown}:\n\t$file.\n", $die);
 
435
        return;
 
436
      };
 
437
      open ($fh, "GET $file |")
 
438
        or die "A pipe using LWP::Simple could not be opened to fetch ADB file.\n";
 
439
    } else {
 
440
      die "You must install LWP::Simple to fetch ADB files.\n";
 
441
    };
 
442
  } elsif ($file ne '____stdin') {
 
443
    open ($fh, $file) || die "could not open " . $file . " for reading" . $/;
 
444
  };
 
445
  ## what about STDIN???
 
446
 
 
447
 READ: while (<$fh>) {
 
448
 
 
449
    my @line = ();
 
450
    ++$iline;
 
451
    next if /^\s*$/;             # skip blanks
 
452
    next if /^\s*[!\#%*]/;       # skip comment lines
 
453
    chomp;
 
454
    (m/<HTML>/) and do {        # handle an unknown ADB file name
 
455
 
 
456
    };
 
457
    (my $line = $_) =~ s/^\s+//; # trim leading blanks
 
458
    $line =~ s/\r//g;            # strip spurious control-M characters
 
459
    @line = split(/\s*[ \t=,]\s*/, $line);
 
460
 
 
461
  KEYWORDS: while (@line) {
 
462
      my $word = lc(shift @line);
 
463
      next READ if ($word =~ /^[!\#%*]/);          # skip comment lines
 
464
      my $wasword = $word;
 
465
      $word = $href -> {$word};
 
466
      $word ||= $wasword;
 
467
      ##       unless ($word) {
 
468
      ##        my $message = "\"$wasword\": " . $$messages{'unknown_keyword'} .
 
469
      ##          ", " . $$messages{'line'} . " $..$/";
 
470
      ##        $keys -> warn_or_die($message, $die);
 
471
      ##        next READ;
 
472
      ##       };
 
473
      ($word eq "end")  && last READ;
 
474
 
 
475
                                ## title/comment
 
476
 
 
477
      ($word =~ /^(com|tit)/) && do {
 
478
        ## trim `title' or `comment' and leading spaces
 
479
        $line =~ s/^\s+//;
 
480
        my $string = substr($line, length($word));
 
481
        $string =~ s/^\s+//; $string =~ s/^=//; $string =~ s/^\s+//;
 
482
        unless ($keys -> {"quiet"}) {
 
483
          print STDOUT "   ", $$messages{'title'}, " > ",
 
484
          substr($string, 0, 59), "$/";
 
485
        };
 
486
        $keys -> make('title'=>$string);
 
487
        foreach my $a (@line) {
 
488
          shift @line;
 
489
        };
 
490
        next READ;
 
491
      };
 
492
                                ## edge/hole can be error-checked en
 
493
                                ## passant
 
494
      ($word =~ /^(edg|hol)/) && do {
 
495
        my $value = shift @line;
 
496
        $keys -> make('edge'=>$value);
 
497
        next KEYWORDS;
 
498
      };
 
499
                                ## keywords which take the next word
 
500
                                ## literally
 
501
                                ## argon center core krypton nitrogen rmax
 
502
      (grep(/\b$word\b/, @normal)) && do {
 
503
        ($word eq "center") && ($word = "core");
 
504
        my $value = shift @line;
 
505
        $keys -> make($word=>$value);
 
506
        next KEYWORDS;
 
507
      };
 
508
                                ## DAFS keywords which take the next
 
509
                                ## word literally: emin estep emax
 
510
      (grep(/\b$word\b/, @dafs)) && do {
 
511
        ($word eq "egrid") && ($word = "estep");
 
512
        my $value = shift @line;
 
513
        $keys -> make($word=>$value);
 
514
        next KEYWORDS;
 
515
      };
 
516
                                ## three vectors, take the next three
 
517
                                ## words literally
 
518
      (grep(/\b$word\b/, @threevecs)) && do {
 
519
        my @value;
 
520
        $value[0] = shift @line;
 
521
        $value[1] = shift @line;
 
522
        $value[2] = shift @line;
 
523
        ($word eq "dafs") && ($word = "qvec");
 
524
        ($word eq "reflection") && ($word = "qvec");
 
525
 
 
526
        ## ----- evaluate the coordinates safely
 
527
        ## see `perldoc Safe' for details
 
528
        ## my $message = $$messages{'tainted_vector'} . $/;
 
529
        ## $message .= $$messages{'file'} . ": $file, ";
 
530
        ## $message .= $$messages{'line'} . ": $.$/";
 
531
        ## my $cpt = new Safe;
 
532
        ## ##$cpt->share('@value');
 
533
        ## $ {$cpt->varglob('x')} = $cpt->reval($value[0]);
 
534
        ## $ {$cpt->varglob('y')} = $cpt->reval($value[1]);
 
535
        ## $ {$cpt->varglob('z')} = $cpt->reval($value[2]);
 
536
        ## @value = ($ {$cpt->varglob('x')}, $ {$cpt->varglob('y')},
 
537
        ##        $ {$cpt->varglob('z')} );
 
538
        ## test_safe_return($message, @value);
 
539
 
 
540
        ## this is a much weaker form of security, but it works with
 
541
        ## PerlApp
 
542
        @value = map {number($_,$die,$keys)} @value;
 
543
        ## ----- end of safe evaluation
 
544
 
 
545
        $keys -> make($word=>$value[0],$value[1],$value[2]);
 
546
        next KEYWORDS;
 
547
      };
 
548
                                ## logical flags and diagnostics take
 
549
                                ## boolean values, on if next word is
 
550
                                ## yes, true, or on
 
551
      ##(grep(/\b$word\b/, @logicals)) && do {
 
552
      ##  my $value = shift @line;
 
553
      ##  $keywords{$word} = ($value =~ /^(t|y|on)/) ? 1 : 0;
 
554
      ##  next KEYWORDS;
 
555
      ##};
 
556
                                ## output styles take filenames
 
557
      ($word =~ /^outp/) && do {
 
558
        my $type = shift @line;
 
559
        my $value = shift @line;
 
560
        #push @$outputs, $type;
 
561
        if ($keys->{'program'} eq 'dafs') {
 
562
          undef $keys->{atp}; undef $keys->{files};
 
563
        };
 
564
        $keys -> make('atp'   => $type);
 
565
        $keys -> make('files' => $type, $value);
 
566
        $keys -> make("found_output" => 1);
 
567
        next KEYWORDS;
 
568
      };
 
569
                                ## backwards compatability
 
570
      (grep(/\b$word\b/, @output_types)) && do {
 
571
        my $value = shift @line;
 
572
        if ($word eq "out") {
 
573
          $keys -> make('atp'=>'feff', 'feff'=>$value);
 
574
          #push @$outputs, "feff";
 
575
        } else {
 
576
          my $set = ($value =~ /^(t|y|on)/);
 
577
          if ($set) {
 
578
            $keys -> make('atp'=>$word);
 
579
            ## ($word eq "feff")  && ($keys -> make('feff' => "feff.inp"));
 
580
            ## ($word eq "feff8") && ($keys -> make('feff8'=> "feff.inp"));
 
581
            ## ($word eq "geom")  && ($keys -> make('geom' => "geom.dat"));
 
582
            ## ($word eq "p1")    && ($keys -> make('p1'   => "p1.inp"  ));
 
583
            ## ($word eq "unit")  && ($keys -> make('unit' => "unit.dat"));
 
584
            #push @$outputs, $word;
 
585
          };
 
586
        };
 
587
        next KEYWORDS;
 
588
      };
 
589
                                ## deprecated keywords
 
590
      (grep(/\b$word\b/, @deprecated)) && do {
 
591
        shift @line;
 
592
        my $mess = "\"$word\": " . $$messages{'deprecated'} . ", " .
 
593
          $$messages{'line'} . " $..$/";
 
594
        warn $mess;
 
595
        ($word =~ /dafs|reflections|qvec/) && do {
 
596
          shift @line; shift @line;
 
597
        };
 
598
        next KEYWORDS;
 
599
      };
 
600
                                ## space group symbol
 
601
      ($word =~ /^spa/) && do {
 
602
        my $lline = lc($line);
 
603
        my $space = substr($line, index($lline,"space")+6);
 
604
        $space =~ s/^[\s=,]+//;
 
605
        $space =  substr($space, 0, 10); # next 10 characters
 
606
        $space =~ s/[!\#%*].*$//;  # trim off comments
 
607
        $keys -> make('space'=>$space);
 
608
        foreach my $x (split " ", $space) {
 
609
          shift @line;
 
610
        };
 
611
        next KEYWORDS;
 
612
      };
 
613
                                ## a,b,c,alpha,beta,gamma
 
614
      (grep(/\b$word\b/, @cellwords)) && do {
 
615
        my $value = shift @line;
 
616
        $value = sprintf "%11.7f", $value;
 
617
        my $attribute = lc $word;
 
618
        $keys -> make($attribute=>$value);
 
619
        ## the angle attribute carries the most recently set angle
 
620
        ## this is needed to correctly interpret monoclinic cells
 
621
        ## with all angles equal to 90 (pathology!!)
 
622
        ##($attribute =~ /(alpha|beta|gamma)/i) &&
 
623
        ##  ($cell -> make( "Angle"=>$1 ) );
 
624
        next KEYWORDS;
 
625
      };
 
626
                                ## site methods
 
627
      ($word =~ /^dop/) && do {
 
628
        my $mess = $$messages{'dopants'} . ", " . $$messages{'line'} . " $..$/";
 
629
        $keys -> warn_or_die($mess, $die);
 
630
        shift @line; shift @line; shift @line;
 
631
        next KEYWORDS;
 
632
      };
 
633
      ($word =~ /^bas/) && do {
 
634
        croak $$messages{'basis'}, $/;
 
635
        next KEYWORDS;
 
636
      };
 
637
      ($word =~ /^ato/) && do {
 
638
        my $nip = 0;
 
639
      ATOMS: while (1) {
 
640
          $_ = <$fh>;
 
641
          (! $_) && last KEYWORDS;
 
642
          ++$iline;
 
643
          last KEYWORDS if /---/;
 
644
          next ATOMS if /^\s*$/;          # skip blanks
 
645
          next ATOMS if /^\s*[!\#%*]/;    # skip comment lines
 
646
          chomp;
 
647
          my @line = split;
 
648
          my $save = $line[0];
 
649
          $line[0] = get_symbol($line[0]);
 
650
          unless ($line[0]) {
 
651
            if ($die == 0) {
 
652
              my $mess = join(" ", $save, $$messages{not_an_element},
 
653
                              $$messages{at_line}, $iline, $/);
 
654
              die $mess;
 
655
            } else {
 
656
              $line[0] = "??";
 
657
              ##my $mess = join(" ", $save, "is not an element symbol", $/);
 
658
              ##$keys -> warn_or_die($mess, $die);
 
659
            };
 
660
          };
 
661
          ## ----- evaluate the coordinates safely
 
662
          ## see `perldoc Safe' for details
 
663
 
 
664
          ## this is a much weaker form of security, but it works with
 
665
          ## PerlApp
 
666
          my @xyz = map {number($_, $die, $keys)} ($line[1], $line[2], $line[3]);
 
667
 
 
668
          ## my $message = $$messages{'tainted_atoms'} . $/;
 
669
          ## $message .= $$messages{'file'} . ": $file, ";
 
670
          ## $message .= $$messages{'line'} . ": $.$/";
 
671
          ## my $cpt = new Safe;
 
672
          ## ##$cpt->share('@xyz');
 
673
          ## $ {$cpt->varglob('x')} = $cpt->reval($xyz[0]);
 
674
          ## $ {$cpt->varglob('y')} = $cpt->reval($xyz[1]);
 
675
          ## $ {$cpt->varglob('z')} = $cpt->reval($xyz[2]);
 
676
          ## @xyz = ($ {$cpt->varglob('x')}, $ {$cpt->varglob('y')},
 
677
          ##      $ {$cpt->varglob('z')} );
 
678
          ## test_safe_return($message, @xyz);
 
679
          # @xyz = map {sprintf "%11.7f", $_} @xyz;
 
680
          ## ----- end of safe evaluation
 
681
 
 
682
          if (defined $line[4]) {
 
683
            ## if the fourth column matches a C float, assume it is an
 
684
            ## occupancy and that the tag should be ""
 
685
            if ($line[4] =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/) {
 
686
              $line[5] = $line[4];
 
687
              $line[4] = "";
 
688
            };
 
689
          } else {
 
690
            ($line[4] = "");
 
691
          };
 
692
          ## test fifth column to see if it is an occupancy
 
693
          (defined $line[5]) || ($line[5] = 1);
 
694
          if ($line[5] =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/) {
 
695
            $line[5] = number($line[5], $die, $keys);
 
696
            ($line[5] =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/)
 
697
              || ($line[5] = 1);
 
698
          } else {
 
699
            $line[5] = 1;
 
700
          };
 
701
          $keys -> make('sites'=>
 
702
                        $line[0], $xyz[0], $xyz[1], $xyz[2], $line[4], $line[5]);
 
703
          ++$nsites;
 
704
          my $this = lc($line[0]);
 
705
        };
 
706
      };
 
707
                                ## unknown keyword, store it anyway
 
708
      ($word) && do {
 
709
        my $value = shift @line;
 
710
        $keys -> make($word=>$value);
 
711
        ##      my $message = "\"$word\": " . $$messages{'unknown_keyword'} .
 
712
        ##        ", " . $$messages{'line'} . "$..$/";
 
713
        ##      warn $message;
 
714
        ##      shift @line;
 
715
        next KEYWORDS;
 
716
      };
 
717
    };
 
718
  };
 
719
  close $fh;
 
720
};
 
721
 
 
722
 
 
723
=head2 C<cif>
 
724
 
 
725
This method reads from a CIF file and loads the data into keywords
 
726
object.
 
727
 
 
728
=cut
 
729
 
 
730
 
 
731
## this is ungainly due to the mismatch in nomenclature and verbosity
 
732
## of my stuff and the STAR stuff.
 
733
sub cif {
 
734
  my ($keys, $file, $which) = @_;
 
735
  my @datablocks = STAR::Parser->parse($file);
 
736
  my $datablock = $datablocks[$which];
 
737
  ##    if STAR::Checker->check(-datablock=>$datablocks[0]);
 
738
 
 
739
  my @item;
 
740
 
 
741
  ## titles: consider various common title-like entries, strip white
 
742
  ## space characters and stuff them into the title attribute
 
743
  @item = $datablock->get_item_data(-item=>"_chemical_name_mineral");
 
744
  $item[0] ||= ""; $item[0] =~ s/
 
 
b'//g; chomp $item[0];'
 
745
  $keys->make(title=>$item[0]) unless ($item[0] =~ /^\s*$/);
 
746
  @item = $datablock->get_item_data(-item=>"_chemical_name_systematic");
 
747
  $item[0] ||= ""; $item[0] =~ s/
 
 
b'//g; chomp $item[0];'
 
748
  $keys->make(title=>$item[0]) unless ($item[0] =~ /^\s*$/);
 
749
  @item = $datablock->get_item_data(-item=>"_chemical_formula_structural");
 
750
  $item[0] ||= ""; $item[0] =~ s/
 
 
b'//g; chomp $item[0];'
 
751
  $keys->make(title=>$item[0]) unless ($item[0] =~ /^\s*$/);
 
752
  @item = $datablock->get_item_data(-item=>"_chemical_formula_sum");
 
753
  $item[0] ||= ""; $item[0] =~ s/
 
 
b'//g; chomp $item[0];'
 
754
  $keys->make(title=>$item[0]) unless ($item[0] =~ /^\s*$/);
 
755
  @item = $datablock->get_item_data(-item=>"_publ_author_name");
 
756
  $item[0] ||= ""; $item[0] =~ s/
 
 
b'//g; chomp $item[0];'
 
757
  $keys->make(title=>$item[0]) unless ($item[0] =~ /^\s*$/);
 
758
  @item = $datablock->get_item_data(-item=>"_citation_journal_abbrev");
 
759
  $item[0] ||= ""; $item[0] =~ s/
 
 
b'//g; chomp $item[0];'
 
760
  $keys->make(title=>$item[0]) unless ($item[0] =~ /^\s*$/);
 
761
  @item = $datablock->get_item_data(-item=>"_publ_section_title");
 
762
  $item[0] ||= ""; $item[0] =~ s/
 
 
b'//g; chomp $item[0];'
 
763
  $keys->make(title=>$item[0]) unless ($item[0] =~ /^\s*$/);
 
764
 
 
765
  ## space group: try the number then the NM symbol and canonicalize it
 
766
  @item = $datablock->get_item_data(-item=>"_symmetry_Int_Tables_number");
 
767
  my @sg = Xray::Xtal::Cell::canonicalize_symbol($item[0]);
 
768
  unless ($sg[0]) {
 
769
    @item = $datablock->get_item_data(-item=>"_symmetry_space_group_name_H-M");
 
770
    @sg = Xray::Xtal::Cell::canonicalize_symbol($item[0]);
 
771
  };
 
772
  $keys->make(space=>$sg[0]) if $sg[0];
 
773
 
 
774
  ## lattic parameters
 
775
  my $min = 100000;   # use lattice constants to compute default for Rmax
 
776
  foreach my $k (qw(a b c)) {
 
777
    @item = $datablock->get_item_data(-item=>"_cell_length_$k");
 
778
    (my $this = $item[0]) =~ s/\(\d+\)//;
 
779
    #print "$k $this\n";
 
780
    $keys->make($k=>$this);
 
781
    $min = 1.1*$this if ($min > 1.1*$this);
 
782
  };
 
783
  $min = 7 if ($min > 11);
 
784
  $keys->make(rmax=>$min);
 
785
  foreach my $k (qw(alpha beta gamma)) {
 
786
    @item = $datablock->get_item_data(-item=>"_cell_angle_$k");
 
787
    (my $this = $item[0]) =~ s/\(\d+\)//;
 
788
    #print "$k $this\n";
 
789
    $keys->make($k=>$this);
 
790
  };
 
791
 
 
792
  ## load up and clean up the atom positions
 
793
  my @tag = $datablock->get_item_data(-item=>"_atom_site_label");
 
794
  my @el  = $datablock->get_item_data(-item=>"_atom_site_type_symbol");
 
795
  my @x   = $datablock->get_item_data(-item=>"_atom_site_fract_x");
 
796
  my @y   = $datablock->get_item_data(-item=>"_atom_site_fract_y");
 
797
  my @z   = $datablock->get_item_data(-item=>"_atom_site_fract_z");
 
798
  my @occ = $datablock->get_item_data(-item=>"_atom_site_occupancy");
 
799
  foreach my $i (0 .. $#tag) {
 
800
    my $ee = $el[$i] || $tag[$i];
 
801
    $ee = get_elem($ee);
 
802
    (my $xx = $x[$i]) =~ s/\(\d+\)//; # remove parenthesized error bars
 
803
    (my $yy = $y[$i]) =~ s/\(\d+\)//;
 
804
    (my $zz = $z[$i]) =~ s/\(\d+\)//;
 
805
    (my $oo = $occ[$i]||1) =~ s/\(\d+\)//;
 
806
    ##print "$ee, $xx, $yy, $zz, $tag[$i], $oo\n";
 
807
    $keys -> make('sites'=> $ee, $xx, $yy, $zz, $tag[$i], $oo);
 
808
  };
 
809
};
 
810
 
 
811
## Tags in the cif file seem to mostly be the two letter element
 
812
## symbol concatinated with a number (possibly concatinated with a
 
813
## non-alphanumeric, such as a plus sign or parens).  Also "Wat" and "OH"
 
814
## indicate oxygen sites (with one or two associated hydrogens) and D
 
815
## means deuterium.
 
816
sub get_elem {
 
817
  my $elem = $_[0];
 
818
  ($elem =~ /Wat/) and return "O";
 
819
  ($elem =~ /OH/)  and return "O";
 
820
  ($elem =~ /^D$/) and return "H";
 
821
  ## snip off the last character until an element symbol is found
 
822
  while ($elem) {
 
823
    return $elem if ($elem =~ /^$elem_match$/o);
 
824
    chop $elem;
 
825
  };
 
826
  return "??";
 
827
};
 
828
 
 
829
 
 
830
sub test_safe_return {
 
831
  my $message = shift(@_);
 
832
  my @list = @_;
 
833
  foreach my $item (@list) {
 
834
    (defined $item) || die $message . $/;
 
835
  };
 
836
  return 1;
 
837
};
 
838
 
 
839
=head2  C<verify_keywords>
 
840
 
 
841
This method is used to perform some sanity checks on the keyword
 
842
values after reading in the input data and populating the sites and the
 
843
cell.  It is a good idea to call this before building a cluster or
 
844
starting some other calculation.
 
845
 
 
846
  $keywords -> verify_keywords($cell, \@sites, 0, 0);
 
847
 
 
848
The first two arguments are a Cell object and a reference to a
 
849
list of Site objects.  The third argument is the same as the second
 
850
argument to the C<parse_input> method.  The last argument should be
 
851
non-zero only for a calculation that does not need to know which is
 
852
the central atom (e.g. a powder diffraction simulation).
 
853
 
 
854
=cut
 
855
 
 
856
sub verify_keywords {
 
857
  my $keys = shift;
 
858
  my ($cell,$r_sites,$die,$no_core) = @_;
 
859
  my $def = 7;
 
860
  $keys ->{dopant_core} = "";
 
861
  $keys -> set_rmax($cell, $def, $die);
 
862
  unless ($no_core) {
 
863
    my $checkit = $keys -> set_core($r_sites, $die);
 
864
    return 1 if ($checkit eq '-1');
 
865
    #$keys -> check_core($r_sites, $die);
 
866
    $keys -> set_edge($cell,$die);
 
867
  };
 
868
  foreach my $word (qw/argon krypton nitrogen/) {
 
869
    if (($keys->{$word} < 0) || ($keys->{$word} > 1)) {
 
870
      $keys->make($word=>'0');
 
871
      my $message = "\"$word\": $$messages{'one_gas'}$/";
 
872
      $keys -> warn_or_die($message, $die);
 
873
    };
 
874
  };
 
875
  if (($keys->{nitrogen} + $keys->{argon} + $keys->{krypton}) > 1) {
 
876
    my $message = $$messages{'all_gases'} . $/;
 
877
    $keys->make('nitrogen'=> '0');
 
878
    $keys->make('argon'=>    '0');
 
879
    $keys->make('krypton'=>  '0');
 
880
    $keys -> warn_or_die($message, $die);
 
881
    return 1;
 
882
  };
 
883
  if ($keys->{dopant_core}) {
 
884
    my $z = &get_Z($keys->{dopant_core});
 
885
    unless ($z) {
 
886
      my $message = $$::messages{dopant_core} . $/;
 
887
      $keys -> warn_or_die($message, $die);
 
888
      return 1;
 
889
    };
 
890
  };
 
891
};
 
892
# perhaps take as arguments refs to subroutines for further tests
 
893
 
 
894
## the next several subroutines are used internally, thus not pod
 
895
## docmented.
 
896
 
 
897
## reference to a cell and default rmax value (defaults to 7)
 
898
sub set_rmax {
 
899
  my $keys = shift;
 
900
  my ($cell, $def, $die) = @_;
 
901
  my $message = $$messages{'rmax'} . $/;
 
902
  $def ||= 7;
 
903
  my $rm = number($keys->{'rmax'}, $die, $keys);
 
904
  if (($rm != 0) && ($rm < EPSILON)) {
 
905
    $rm = 0;
 
906
    $keys -> warn_or_die($message, $die);
 
907
  };
 
908
  ($rm > EPSILON) || do {
 
909
    ## rmax is the larger of (7 and 1.1 times the shortest axis)
 
910
    my @list = sort ($cell->attributes('A','B','C'));
 
911
    @list = sort ($list[0], $def/1.1);
 
912
    $keys->{"rmax"} = 1.1*$list[1];
 
913
  };
 
914
  return $keys->{"rmax"};
 
915
};
 
916
 
 
917
## reference to an array of sites
 
918
sub set_core {
 
919
  my $keys = shift;
 
920
  my $sites = $_[0];
 
921
  my $die = $_[1];
 
922
  #unless ($keys->{"core"}) {
 
923
  if ($#{$sites} == 0) {
 
924
    my ($t) = $$sites[0] -> attributes('Tag');
 
925
    $keys->make("core", $t);
 
926
    return $keys->{"core"};
 
927
  } elsif (($#{$sites} != 0) and ($keys->{"core"} =~ /^\s*$/)) {
 
928
    $keys -> warn_or_die($$messages{'no_core'} . $/, $die);
 
929
    my ($t) = $$sites[0] -> attributes('Tag');
 
930
    $keys->make("core", $t);
 
931
    return $keys->{"core"};
 
932
  } elsif ($keys -> check_core($sites, $die)) {
 
933
    return $keys->{"core"};
 
934
  } else {
 
935
    $keys -> warn_or_die($$messages{'no_core'} . $/, $die);
 
936
  };
 
937
  #return $keys->{"core"};
 
938
  #};
 
939
};
 
940
 
 
941
sub check_core {
 
942
  my $keys = shift;
 
943
  my ($sites, $die) = @_;
 
944
  my $c = lc($keys->{'core'});
 
945
  my $found = 0;
 
946
  foreach my $site (@$sites) {
 
947
    my ($t) = $site -> attributes('Tag');
 
948
    $t = lc($t);
 
949
    $found = 1, last if ($t eq $c);
 
950
  };
 
951
  $| = 1;
 
952
  ($found) ||
 
953
    $keys -> warn_or_die("\"" . $keys->{'core'} . "\" " .
 
954
                         $$messages{'unknown_core'} . $/,
 
955
                         $die);
 
956
  (not $found) and ($die == 0) and die "\n";
 
957
  #$::help{'check_core'};
 
958
  return $found;
 
959
};
 
960
 
 
961
sub set_edge {
 
962
  my $keys = shift;
 
963
  my ($cell,$die) = @_;
 
964
  if ($cell->{Contents}) {
 
965
    unless ($keys->{'edge'}) { # default based on Z number
 
966
      my ($central, $xcenter, $ycenter, $zcenter) =
 
967
        $cell -> central($keys->{"core"});
 
968
      my $z = &get_Z($central);
 
969
      $keys->{'edge'} = 'K';
 
970
      if ($z > 57) {
 
971
        $keys->{'edge'} = 'L3';
 
972
      };
 
973
 
 
974
    };
 
975
    ($keys->{"edge"} =~ /k|l[123]|m[1-5]|n[1-7]|o[1-7]|p[1-3]/i) || do {
 
976
      my $message = $$messages{'bad_edge'} . $/;
 
977
      ($keys->{"edge"} = 'k');
 
978
      $keys -> warn_or_die($message, $die);
 
979
      return 0;
 
980
    };
 
981
  };
 
982
  return $keys->{'edge'};
 
983
};
 
984
 
 
985
 
 
986
sub atp_selected {
 
987
  my $keys = shift;
 
988
  my $atp = $_[0];
 
989
  return $keys->{'atp'}{$atp};
 
990
};
 
991
sub toggle_atp {
 
992
  my $keys = shift;
 
993
  my $atp = $_[0];
 
994
  if ($keys->{'atp'}{$atp}) {
 
995
    $keys->{'atp'}{$atp} = 0;
 
996
  } else {
 
997
    $keys->{'atp'}{$atp} = 1;
 
998
  };
 
999
};
 
1000
 
 
1001
=head2 C<warn_or_die>
 
1002
 
 
1003
This is a subroutine rather than a method, so that is can be used even
 
1004
without an active keywords object.  It is a wrapper for generating
 
1005
error and warning methods that will work in command-line, GUI, and CGI
 
1006
environments.
 
1007
 
 
1008
   $keywords -> warn_or_die("This is a warning message", $die);
 
1009
 
 
1010
The first argument is the actual message.  The C<die> argument is 0,
 
1011
1, or 2 as described for C<parse_input>.  The final argument is a
 
1012
keywords object and is only used in the CGI environment.  In that
 
1013
case, here are no convenient output or error channels, so messages are
 
1014
stored in the keywords hash as a single string under the key
 
1015
"www_warn".
 
1016
 
 
1017
=cut
 
1018
 
 
1019
sub warn_or_die {
 
1020
  my $keys = shift;
 
1021
  my ($message, $die) = @_;
 
1022
  if ($die == 1) {              # Tk
 
1023
    ##my $temp = MainWindow->new;
 
1024
    ##$temp -> withdraw;
 
1025
    $::top -> messageBox(-icon    => 'error',
 
1026
                         -message => $message,
 
1027
                         -title   => 'Atoms: Error',
 
1028
                         -type    => 'OK');
 
1029
    ##undef $temp;
 
1030
    return -1;
 
1031
  } elsif ($die == 2) {         # CGI
 
1032
    $keys -> {www_warn} .= $message;
 
1033
  } elsif ($die == 0) {         # CLI
 
1034
    warn $message;
 
1035
    $keys -> {cli_warn} .= $message if $keys;
 
1036
  } elsif ($die == 4) {         # ignore
 
1037
    return -1;
 
1038
  } else {
 
1039
    die "Programmer error!  Invalid run level $die in warn_or_die!\n";
 
1040
  };
 
1041
};
 
1042
 
 
1043
## =head2 C<available_languages>
 
1044
##
 
1045
## This subroutine takes no arguments and returns a list of the languages
 
1046
## for which translations of the language data exist.
 
1047
##
 
1048
## =cut
 
1049
 
 
1050
sub available_languages {
 
1051
  return @available_languages;
 
1052
};
 
1053
 
 
1054
 
 
1055
=head2 C<parse_atp>
 
1056
 
 
1057
This subroutine is the output engine.  It parses the appropriate atp
 
1058
file, interprets its content, and generates output data.  This one
 
1059
subroutine handles the creation of F<feff.inp> and the other sorts of
 
1060
lists generated by Atoms.  It also generates reports for the
 
1061
Absorption notecard in TkAtoms and for the lists describing the DAFS
 
1062
calculation as a function of energy and the powder calculation as a
 
1063
function of angle.  Essentially all output from all programs that come
 
1064
with the Atoms package is generated here.
 
1065
 
 
1066
  my ($default_name, $is_feff) =
 
1067
    &parse_atp($atp, $cell, $keywords, \@cluster, \@neutral, \$contents);
 
1068
  open (INP, ">".$default_name) or die $!;
 
1069
  print INP $contents;
 
1070
  close INP;
 
1071
 
 
1072
The arguments are
 
1073
 
 
1074
=over 4
 
1075
 
 
1076
=item 1.
 
1077
 
 
1078
A string specifying the atp file to read.  The actual atp file either
 
1079
lives in the F<atp/> subdirectory of the Atoms installation or in the
 
1080
F<.atoms/> directory of the individual user.
 
1081
 
 
1082
=item 2.
 
1083
 
 
1084
A Cell object.
 
1085
 
 
1086
=item 3.
 
1087
 
 
1088
A keywords object.
 
1089
 
 
1090
=item 4.
 
1091
 
 
1092
A reference to the list generated by the C<build_cluster> subroutine,
 
1093
to the list containing a calculation such as the one in DAFS or
 
1094
Powder, or 'whatever the thing is that I did to make atoms.inp output
 
1095
work.'
 
1096
 
 
1097
=item 5.
 
1098
 
 
1099
A reference to a list.  This is just a place holder and is not
 
1100
currently used for anything.
 
1101
 
 
1102
=item 6.
 
1103
 
 
1104
A scalar reference.  This is assumed to be empty on entry, and is
 
1105
filled with contents intended for the output file on exit.
 
1106
 
 
1107
=back
 
1108
 
 
1109
Typically the C<contents> variables is the things written to the
 
1110
output channel.  The two return values are the default name for the
 
1111
output file and a flag indicating whether the output is intended to
 
1112
run FEFF.  Both of these pieces of information are read from the
 
1113
C<meta> line of the atp file.
 
1114
 
 
1115
 
 
1116
 
 
1117
=head1 EXPORTED SUBROUTINES
 
1118
 
 
1119
=head2 C<absorption>
 
1120
 
 
1121
This subroutine calculates the total absorption (or one over the
 
1122
e-fold absorption length), the delta_mu across the edge (or one over
 
1123
the unit edge step length) and the specific gravity of the crystal.
 
1124
 
 
1125
  ($total_absorption, $delta_mu, $specific_gravity) =
 
1126
       &absorption(\$cell, $central, $edge);
 
1127
 
 
1128
The input arguments are a populated cell, the tag of the central atom,
 
1129
and the alphanumeric (i.e. K, L1, etc) symbol of the absorption edge.
 
1130
 
 
1131
Note that the values returned depend on the data resource used.  See
 
1132
L<Xray::Absorption>.
 
1133
 
 
1134
 
 
1135
=cut
 
1136
 
 
1137
 
 
1138
## this reproduces well the results from the Fortran atoms.
 
1139
sub absorption {
 
1140
  my ($cell, $central, $edge) = @_;
 
1141
  $central = lc($central);
 
1142
  my $specified = ($edge =~ /\b\d+\b/);
 
1143
  my $energy     = ($edge =~ /\b\d+\b/) ?
 
1144
    $edge : Xray::Absorption -> get_energy($central, $edge);
 
1145
  my ($contents) = $cell -> attributes("Contents");
 
1146
  my ($bravais)  = $cell -> attributes("Bravais");
 
1147
  my $brav       = ($#{$bravais}+4) / 3;
 
1148
  my ($volume, $occ)   = $cell -> attributes("Volume", "Occupancy");
 
1149
  my ($mass, $xsec, $delta_mu, $den, $conv) = (0,0,0,0,0);
 
1150
  my %cache = ();   # memoize and call cross_section less often
 
1151
  foreach my $atom (@{$contents}) {
 
1152
    my ($element, $this_occ) =
 
1153
      $ {$$atom[3]} -> attributes("Element", "Occupancy");
 
1154
    # print join(" ", $element, $this_occ, $/);
 
1155
    $element = lc($element);
 
1156
    my $factor = $this_occ;  # $occ ? $this_occ : 1; # consider site occupancy??
 
1157
    my $weight = Xray::Absorption -> get_atomic_weight($element);
 
1158
    $mass += $weight*$factor;
 
1159
    $cache{lc($element)} ||=
 
1160
      scalar Xray::Absorption -> cross_section($element, $energy+50);
 
1161
    $xsec += $cache{lc($element)} * $factor;
 
1162
    if (($central eq $element) and not $specified) {
 
1163
      $delta_mu +=
 
1164
        ($factor/$brav) *
 
1165
          ( $cache{lc($central)} -
 
1166
            scalar Xray::Absorption -> cross_section($central, $energy-50) );
 
1167
    };
 
1168
  };
 
1169
  $mass     *= 1.66053/$volume; ## atomic mass unit = 1.66053e-24 gram
 
1170
  $xsec     /= $volume;
 
1171
  $delta_mu /= $volume;
 
1172
  return ($xsec, $delta_mu, $mass);
 
1173
};
 
1174
 
 
1175
 
 
1176
=head2 C<xsec>
 
1177
 
 
1178
This subroutine calculates the total absorption (or one over the
 
1179
e-fold absorption length) and the specific gravity of the crystal at a
 
1180
specified energy (rather than around the edge energy of an element).
 
1181
 
 
1182
In scalar context:
 
1183
 
 
1184
  ($total_absorption, $specific_gravity) =
 
1185
      &xsec($cell, $central, $energy);
 
1186
 
 
1187
In list context:
 
1188
 
 
1189
  (\@total_absorption, $specific_gravity) =
 
1190
      &xsec($cell, $central, \@energies);
 
1191
 
 
1192
The input arguments are a populated cell and the tag of the central
 
1193
atom.  The third argument is either the desired energy in eV or a
 
1194
reference to a list of energies in eV.
 
1195
 
 
1196
Note that the values returned depend on the data resource used.  See
 
1197
L<Xray::Absorption>.
 
1198
 
 
1199
 
 
1200
=cut
 
1201
 
 
1202
sub xsec {
 
1203
  my ($cell, $central, $energy) = @_;
 
1204
  $central = lc($central);
 
1205
  my ($contents) = $cell -> attributes("Contents");
 
1206
  my ($bravais)  = $cell -> attributes("Bravais");
 
1207
  my $brav       = ($#{$bravais}+4) / 3;
 
1208
  my ($volume, $occ)   = $cell -> attributes("Volume", "Occupancy");
 
1209
  my ($xsec, $delta_mu, $den, $conv) = (0,0,0,0);
 
1210
  my @xsec = ();
 
1211
  my %cache = ();   # memoize and call cross_section less often
 
1212
  foreach my $atom (@{$contents}) {
 
1213
    my ($element, $this_occ) =
 
1214
      $ {$$atom[3]} -> attributes("Element", "Occupancy");
 
1215
    $element = lc($element);
 
1216
    my $factor = $this_occ;  # $occ ? $this_occ : 1; # consider site occupancy??
 
1217
    if (wantarray) {
 
1218
      exists $cache{$element} or
 
1219
        @{$cache{$element}} =
 
1220
          Xray::Absorption -> cross_section($element, $energy);
 
1221
      foreach (0 .. $#{$energy}) {
 
1222
        $xsec[$_] += $ {$cache{$element}}[$_] * $factor;
 
1223
      };
 
1224
    } else {
 
1225
      $cache{$element} ||=
 
1226
        scalar Xray::Absorption -> cross_section($element, $energy);
 
1227
      $xsec += $cache{$element} * $factor;
 
1228
    };
 
1229
  };
 
1230
  $xsec /= $volume;
 
1231
  return wantarray ? @xsec : $xsec;
 
1232
};
 
1233
 
 
1234
sub density {
 
1235
  my $cell = $_[0];
 
1236
  my ($contents, $bravais) = $cell -> attributes("Contents", "Bravais");
 
1237
  my $brav       = ($#{$bravais}+4) / 3;
 
1238
  my ($volume, $occ)   = $cell -> attributes("Volume", "Occupancy");
 
1239
  my ($mass, $den, $conv) = (0,0,0);
 
1240
  foreach my $atom (@{$contents}) {
 
1241
    my ($element, $this_occ) =
 
1242
      $ {$$atom[3]} -> attributes("Element", "Occupancy");
 
1243
    $element = lc($element);
 
1244
    my $factor = $this_occ;  # $occ ? $this_occ : 1; # consider site occupancy??
 
1245
    my $weight = Xray::Absorption -> get_atomic_weight($element);
 
1246
    $mass += $weight*$factor;
 
1247
  };
 
1248
  $mass *= 1.66053/$volume;     ## atomic mass unit = 1.66053e-24 gram
 
1249
  return $mass;
 
1250
};
 
1251
 
 
1252
 
 
1253
sub mcmaster_pre_edge {
 
1254
  my ($central, $edge) = @_;
 
1255
  $edge = lc($edge);
 
1256
  my $emin = Xray::Absorption -> get_energy($central, $edge) - 10;
 
1257
  ## find the pre-edge line
 
1258
  my %next_e = ("k"=>"l1", "l1"=>"l2", "l2"=>"l3", "l3"=>"m");
 
1259
  my $ebelow;
 
1260
  if (exists $next_e{$edge}) {
 
1261
    $ebelow = Xray::Absorption -> get_energy($central, $next_e{$edge}) + 10;
 
1262
    $ebelow = (($emin - $ebelow) > 100) ? $emin - 100 : $ebelow;
 
1263
  } else {
 
1264
    $ebelow = $emin - 100;
 
1265
  };
 
1266
  my $delta  = ($emin - $ebelow)/10;;
 
1267
  my @i=(0..9);                 # load the pre edge energies/sigmas
 
1268
  my @energy = map {$ebelow + $delta*$_} @i;
 
1269
  my @sigma  = Xray::Absorption -> cross_section($central, \@energy);
 
1270
                                #  and fit 'em
 
1271
  my $pre_edge = Statistics::Descriptive::Full->new();
 
1272
  $pre_edge -> add_data(@sigma);
 
1273
  my ($bpre, $slope) = $pre_edge -> least_squares_fit(@energy);
 
1274
  $bpre ||= 0; $slope ||= 0;
 
1275
  return ($bpre, $slope);
 
1276
};
 
1277
 
 
1278
=head2 C<mcmaster>
 
1279
 
 
1280
This is called C<mcmaster> for historical reasons.  It calculates the
 
1281
normalization correcion for a given central atom.
 
1282
 
 
1283
  $sigma_mm = &mcmaster($central, $edge);
 
1284
 
 
1285
It takes the central atoms tag and the alphanumeric edge symbol as
 
1286
arguments and returns the normalization correction in units of
 
1287
Angstrom squared.
 
1288
 
 
1289
Note that the values returned depend on the data resource used.  See
 
1290
L<Xray::Absorption>.
 
1291
 
 
1292
=cut
 
1293
 
 
1294
 
 
1295
## $span in the fortran version was 500eV and the regression was
 
1296
## performed with a square term.
 
1297
## Statistics::Descriptive::least_squares_fit only fits a line, so I
 
1298
## drew the #span back to 300 volts.  This gives the "canonical"
 
1299
## 0.00052 for copper.
 
1300
sub mcmaster {
 
1301
  my ($central, $edge) = @_;
 
1302
  $edge = lc($edge);
 
1303
  my $emin = Xray::Absorption -> get_energy($central, $edge) + 10 ;
 
1304
  my ($emax, $e_to_k, $span, $npost) = (0, 0.2624682917, 300, 20);
 
1305
 
 
1306
  ## get energy range for above edge fit
 
1307
  my %next_e = ("l1"=>"k", "l2"=>"l1", "l3"=>"l2", "m"=>"l3");
 
1308
  if (exists $next_e{$edge}) {
 
1309
    $emax = Xray::Absorption -> get_energy($central, $next_e{$edge}) - 10;
 
1310
    $emax = (($emax - $emin) > $span) ? $emin + $span : $emax;
 
1311
  } else {
 
1312
    $emax = $emin + $span;
 
1313
  };
 
1314
 
 
1315
  ## need to show some care with the Chantler data
 
1316
  (Xray::Absorption -> current_resource() =~ /chantler/i) and
 
1317
    $emin += 50;
 
1318
  ($emin >= $emax) and ($emin = $emax - 20); # whatever!
 
1319
 
 
1320
  my ($bpre, $slope) = mcmaster_pre_edge($central, $edge);
 
1321
  my $delta  = ($emax - $emin)/$npost;
 
1322
  my @i=(0..$npost-1);          # load the post edge energies and sigmas
 
1323
  my @energy = map {$emin + $delta*$_} @i;
 
1324
  ## and some more care...
 
1325
  (Xray::Absorption -> current_resource() =~ /chantler/i) and do {
 
1326
    shift @energy; shift @energy;
 
1327
  };
 
1328
  return 0 if ($bpre <= 0);
 
1329
  my @sigma  = Xray::Absorption -> cross_section($central, \@energy);
 
1330
  @sigma = map {$sigma[$_] - ($bpre+$energy[$_]*$slope)} (0 .. $#energy);
 
1331
  ##map {printf "      %9.3f %9.3f\n", $energy[$_], $sigma[$_]} (0 .. $#energy);
 
1332
  @energy    = map {$e_to_k * ($_-$emin)} @energy; # convert to k
 
1333
  my $any_neg = grep {$_ <= 0} @sigma;
 
1334
  return 0 if $any_neg;
 
1335
  @sigma     = map {log($_)} @sigma;       # take logs of xsecs
 
1336
 
 
1337
  my $stat = Statistics::Descriptive::Full->new(); # fit the post edge
 
1338
  $stat -> add_data(@sigma);
 
1339
  my @a = $stat -> least_squares_fit(@energy);
 
1340
  return ($a[1] < 0) ? -$a[1]/2 : 0;
 
1341
};
 
1342
 
 
1343
 
 
1344
=head2 C<i_zero>
 
1345
 
 
1346
This calculates the correcion due to the I0 fill gases in a
 
1347
fluorescence experiment.
 
1348
 
 
1349
  $sigma_i0 = &i_zero($central, $edge, $nitrogen, $argon, $krypton);
 
1350
 
 
1351
It takes the central atoms tag, the alphanumeric edge symbol, and the
 
1352
volume percentages of the three gases as arguments.  It assumes that
 
1353
any remaining volume is filled with helium and it correctly accounts
 
1354
for the fact that nitrogen is a diatom.  It returns the I0 correction
 
1355
in units of Angstrom squared.
 
1356
 
 
1357
Note that the values returned depend on the data resource used.  See
 
1358
L<Xray::Absorption>.
 
1359
 
 
1360
=cut
 
1361
 
 
1362
sub i_zero {
 
1363
  my ($central, $edge, $nitrogen, $argon, $krypton) = @_;
 
1364
 
 
1365
  ##   convert from pressure percentages to number of absorbers.
 
1366
  ## nitrogen is diatomic
 
1367
  my $helium = 1 - $nitrogen - $argon - $krypton;
 
1368
  my $norm   = $helium + 2*$nitrogen + $argon + $krypton;
 
1369
  $nitrogen  = 2*$nitrogen / $norm;
 
1370
  $argon     = $argon      / $norm;
 
1371
  $krypton   = $krypton    / $norm;
 
1372
 
 
1373
  my $emin = Xray::Absorption -> get_energy($central, $edge) ;
 
1374
  my ($emax, $e_to_k, $span, $npost) = (0, 0.2624682917, 500, 20);
 
1375
  ## careful not to run a gas edge
 
1376
  my ($el, $ed, $en) = Xray::Absorption ->
 
1377
    next_energy($central, $edge, "ar", "n", "kr");
 
1378
  if (not defined $en) {
 
1379
    $emax = $emin + $span;
 
1380
  } else {
 
1381
    $emax = ( ($en - 10) < ($emin + $span) ) ? ($en - 10) : ($emin + $span);
 
1382
  };
 
1383
  ## need to show some care with the Chantler data
 
1384
  (Xray::Absorption -> current_resource() =~ /chantler/i) and
 
1385
    $emin += 50;
 
1386
  ($emin >= $emax) and ($emin = $emax - 20); # whatever!
 
1387
 
 
1388
  my @i=(0..$npost-1);          # load the post edge energies and sigmas
 
1389
  my $delta  = ($emax - $emin)/$npost;
 
1390
  my @energy = map {$emin + $delta*$_} @i;
 
1391
  my @s_n = Xray::Absorption -> cross_section("n",  \@energy);
 
1392
  my @s_a = Xray::Absorption -> cross_section("ar", \@energy);
 
1393
  my @s_k = Xray::Absorption -> cross_section("kr", \@energy);
 
1394
  my @sigma  = map
 
1395
  {$nitrogen*$s_n[$_] + $argon*$s_a[$_] + $krypton*$s_k[$_]} (0 .. $#energy);
 
1396
  @energy    = map {$e_to_k * ($_-$emin)} @energy; # convert to k
 
1397
  @sigma     = map {log($_)} @sigma;       # take logs of xsecs
 
1398
 
 
1399
  my $stat = Statistics::Descriptive::Full->new(); # fit the post edge
 
1400
  $stat -> add_data(@sigma);
 
1401
  my @a = $stat -> least_squares_fit(@energy);
 
1402
  return -$a[1]/2;
 
1403
};
 
1404
 
 
1405
=head2 C<self>
 
1406
 
 
1407
This calculates the correcion due to self-absorption fluorescence
 
1408
experiment.  It assumes that the sample is infinately thick and that
 
1409
the entry and exit angles of the photons are the same.
 
1410
 
 
1411
  $sigma_i0 = &self($central, $edge, $cell);
 
1412
 
 
1413
It takes the central atoms tag, the alphanumeric edge symbol, and a
 
1414
populated cell.  It returns a list whose zeroth element is the
 
1415
multiplicative amplitude correction and whose first element is the a
 
1416
correction in units of Angstrom squared.
 
1417
 
 
1418
Note that the values returned depend on the data resource used.  See
 
1419
L<Xray::Absorption>.
 
1420
 
 
1421
=cut
 
1422
 
 
1423
sub self {
 
1424
  my ($central, $edge, $cell) = @_;
 
1425
  my @list = ();
 
1426
  my ($contents) = $cell -> attributes("Contents");
 
1427
  my ($occ) = $cell -> attributes("Occupancy");
 
1428
  foreach my $atom (@{$contents}) {
 
1429
    push @list, $ {$$atom[3]} -> attributes("Element");
 
1430
  };
 
1431
 
 
1432
  my $emin = Xray::Absorption -> get_energy($central, $edge) ;
 
1433
  my ($emax, $e_to_k, $span, $npost) = (0, 0.2624682917, 500, 20);
 
1434
  my ($el, $ed, $en) = Xray::Absorption -> next_energy($central, $edge, @list);
 
1435
  if (not defined $en) {
 
1436
    $emax = $emin + $span;
 
1437
  } else {
 
1438
    $emax = ( ($en - 10) < ($emin + $span) ) ? ($en - 10) : ($emin + $span);
 
1439
  };
 
1440
  ## need to show some care with the Chantler data
 
1441
  (Xray::Absorption -> current_resource() =~ /chantler/i) and
 
1442
    $emin += 50;
 
1443
  ($emin >= $emax) and ($emin = $emax - 20); # whatever!
 
1444
 
 
1445
  ## calculate total absorption at the fluorescence energy and 10
 
1446
  ## volts below the edge
 
1447
  my $xmuf = 0;
 
1448
  my $fline   = substr($edge, 0, 1) . "alpha";
 
1449
  my $e_fluor = Xray::Absorption -> get_energy($central, $fline);
 
1450
  #my $e_below = $emin - 10;
 
1451
  foreach my $atom (@{$contents}) {
 
1452
    my ($element, $this_occ) = $ {$$atom[3]} -> attributes("Element", "Occupancy");
 
1453
    my $factor = $this_occ ; # $occ ? $this_occ : 1; # consider site occupancy??
 
1454
    $xmuf += scalar Xray::Absorption -> cross_section($element, $e_fluor) * $factor;
 
1455
    #$xmub += cross_section($element, $e_below);
 
1456
  };
 
1457
 
 
1458
  ## load the post edge energies and sigmas
 
1459
  #my @xmu = ();
 
1460
  #my @xmu_core = ();
 
1461
  my @i=(0..$npost-1);
 
1462
  my $delta  = ($emax - $emin)/$npost;
 
1463
  my @energy = map {$emin + $delta*$_} @i;
 
1464
 
 
1465
  my @sigma = ();
 
1466
  foreach my $j (@i) {
 
1467
    my $xmu = 0;
 
1468
    my $xmu_core = 0;
 
1469
    my %cache = ();   # memoize and call cross_section less often
 
1470
    foreach my $atom (@{$contents}) {
 
1471
      my ($element, $this_occ) = $ {$$atom[3]} -> attributes("Element", "Occupancy");
 
1472
      if (lc($element) eq lc($central)) {
 
1473
        my $factor = $this_occ; # $occ ? $this_occ : 1;
 
1474
        $cache{lc($element)} ||=
 
1475
          Xray::Absorption -> cross_section($element, $energy[$j]) * $factor;
 
1476
        $xmu_core += $cache{lc($element)};
 
1477
      } else {
 
1478
        my $factor = $this_occ;
 
1479
        $cache{lc($element)} ||=
 
1480
          Xray::Absorption -> cross_section($element, $energy[$j]) * $factor;
 
1481
        $xmu += $cache{lc($element)};
 
1482
      };
 
1483
    };
 
1484
    $sigma[$j] = ($xmuf+$xmu+$xmu_core)/($xmuf+$xmu);
 
1485
  };
 
1486
 
 
1487
  @energy = map {$e_to_k * ($_-$emin)} @energy; # convert to k
 
1488
  @sigma  = map {log($_)} @sigma;
 
1489
 
 
1490
  my $stat = Statistics::Descriptive::Full->new(); # fit the post edge
 
1491
  $stat -> add_data(@sigma);
 
1492
  my @a = $stat -> least_squares_fit(@energy);
 
1493
  return (exp($a[0]), -$a[1]/2);
 
1494
};
 
1495
 
 
1496
 
 
1497
## =over 4
 
1498
##
 
1499
## =item &bravais_string
 
1500
##
 
1501
## Returns the peculiar array from &bravais as a pretty-printed string.
 
1502
## This is not method of the Cell class, it is just a normal function.
 
1503
## This demonstrates the differnt return values of bravais and
 
1504
## bravais_string
 
1505
##
 
1506
##   @list = Xray::Xtal::Cell::bravais("F m 3 m", 0);
 
1507
##   print Xray::Xtal::Cell::bravais_string(@list), "\n",
 
1508
##         join(", ", @list), "\n":
 
1509
##
 
1510
##   |-> (0, 0, 0), (0, 1/2, 1/2), (1/2, 0, 1/2), (1/2, 1/2, 0)
 
1511
##       (0, 0, 0, 0, 0.5, 0.5, 0.5, 0, 0.5, 0.5, 0.5, 0)
 
1512
##
 
1513
## =back
 
1514
##
 
1515
## =cut
 
1516
 
 
1517
sub bravais_string {
 
1518
  my ($bravais, $gnxas) = @_;
 
1519
  return "" unless ($bravais);
 
1520
  my @temp = @$bravais;
 
1521
  length(join("", @temp)) || return ($gnxas) ? "1$/0,0,0"  : "(0, 0, 0)";
 
1522
  @temp = map {
 
1523
    if      (abs($_ - 0.5) < EPSILON) {
 
1524
      ($_ = "1/2")
 
1525
    } elsif (abs($_ - 2/3) < EPSILON) {
 
1526
      ($_ = "2/3")
 
1527
    } elsif (abs($_ - 1/3) < EPSILON) {
 
1528
      ($_ = "1/3")
 
1529
    } else {
 
1530
      ($_ = "0")
 
1531
    } }
 
1532
    @temp;
 
1533
  my $string = "";
 
1534
  if ($gnxas) {
 
1535
    ($#temp == 2) and $string = "2$/";
 
1536
    ($#temp == 5) and $string = "3$/";
 
1537
    ($#temp == 8) and $string = "4$/";
 
1538
    $string .= "0.0000,0.0000,0.0000";
 
1539
    while (@temp) {
 
1540
      $string .= sprintf("$/%6.4f,%6.4f,%6.4f",
 
1541
                         eval(shift @temp), eval(shift @temp), eval(shift @temp));
 
1542
    };
 
1543
  } else {
 
1544
    $string = "(0, 0, 0), (";
 
1545
    $string .= join(", ", $temp[0], $temp[1], $temp[2]);
 
1546
    ($#{temp} > 2) &&
 
1547
      ($string .= "), (" . join(", ", $temp[3], $temp[4], $temp[5]));
 
1548
    ($#{temp} > 5) &&
 
1549
      ($string .= "), (" . join(", ", $temp[6], $temp[7], $temp[8]));
 
1550
    $string .= ")";
 
1551
  };
 
1552
  return $string;
 
1553
};
 
1554
 
 
1555
=head2 C<build_cluster>
 
1556
 
 
1557
This exported routine builds a spherical cluster from a populated unit
 
1558
cell by constructing a rhomboid of an intger number of complete cells
 
1559
which fully encloses the sphere.  The spherical cluster is probably
 
1560
not stoichiometric.
 
1561
 
 
1562
     build_cluster($cell, $keywords, \@cluster, \@neutral);
 
1563
 
 
1564
The first two arguments are cell and keywords objects.  The cell
 
1565
should already have been populated by the C<populate> method. The
 
1566
thrid argument is a reference to an array that will contain the
 
1567
cluster.  The fourth argument is an unused placeholder.
 
1568
 
 
1569
The C<@cluster> array is an anonymous array or arrays.  Each entry in
 
1570
C<@cluster> is an array containing x, y, z, r, r squared, x, y, z, the
 
1571
formula for x, the formula for y , and the formula for z.  The first
 
1572
three arguments are the coordinates to full precision.  The fourth
 
1573
through seventh arguments are of limited precision and are used
 
1574
internally for sorting the cluster.
 
1575
 
 
1576
=cut
 
1577
 
 
1578
 
 
1579
# cell, rmax, position of central atom
 
1580
# return cluster and neutral cluster
 
1581
sub build_cluster {
 
1582
  use POSIX qw(ceil);
 
1583
  my ($cell, $keys, $r_cluster, $r_neutral) = @_;
 
1584
  my $rmax = $keys->{'rmax'};
 
1585
  @$r_cluster = ();
 
1586
  @$r_neutral = ();
 
1587
  my ($central, $xcenter, $ycenter, $zcenter) =
 
1588
    $cell -> central($keys->{"core"});
 
1589
  my $setting       = $cell->{Setting};
 
1590
  my $crystal_class = $cell -> crystal_class();
 
1591
  my $do_tetr       = ($crystal_class eq "tetragonal" )   && ($setting);
 
1592
  ##print join(" ", $central, $xcenter, $ycenter, $zcenter), $/;
 
1593
  my ($aa, $bb, $cc) = $cell -> attributes("A", "B", "C");
 
1594
  my $xup = ceil($rmax/$aa - 1 + $xcenter);
 
1595
  my $xdn = ceil($rmax/$aa - $xcenter);
 
1596
  my $yup = ceil($rmax/$bb - 1 + $ycenter);
 
1597
  my $ydn = ceil($rmax/$bb - $ycenter);
 
1598
  my $zup = ceil($rmax/$cc - 1 + $zcenter);
 
1599
  my $zdn = ceil($rmax/$cc - $zcenter);
 
1600
  ##print join(" ", "up,dn", $xup, $xdn, $yup, $ydn, $zup, $zdn), $/;
 
1601
  #my $num_z = int($rmax/$cc) + 1; # |
 
1602
  my $rmax_squared = $rmax**2; # (sprintf "%9.5f", $rmax**2);
 
1603
  my ($contents) = $cell -> attributes("Contents");
 
1604
  foreach my $nz (-$zdn .. $zup) {
 
1605
    foreach my $ny (-$ydn .. $yup) {
 
1606
      foreach my $nx (-$xdn .. $xup) {
 
1607
        foreach my $pos (@{$contents}) {
 
1608
          my ($x, $y, $z) = ($$pos[0]+$nx, $$pos[1]+$ny,  $$pos[2]+$nz);
 
1609
          ($x, $y, $z) = ($x-$xcenter, $y-$ycenter, $z-$zcenter);
 
1610
          ($x, $y, $z) =  $cell -> metric($x, $y, $z);
 
1611
          ($do_tetr) and ($x, $y) = (($x+$y)/sqrt(2), ($x-$y)/sqrt(2));
 
1612
          #printf "in:  %25s %25s %25s %3d %3d %3d\n", @$pos[4..6],$nx,$ny,$nz;
 
1613
          my ($fx, $fy, $fz) = &rectify_formula(@$pos[4..6], $nx, $ny, $nz);
 
1614
          #printf "out: %25s %25s %25s\n\n", $fx, $fy, $fz;
 
1615
          my $r_squared = (sprintf "%9.5f", $x**2 + $y**2 + $z**2);
 
1616
          my $this_site = [$x, $y, $z, $$pos[3],
 
1617
                           $r_squared,             # cache the
 
1618
                           (sprintf "%11.7f", $x), # stuff needed
 
1619
                           (sprintf "%11.7f", $y), # for sorting
 
1620
                           (sprintf "%11.7f", $z),
 
1621
                           $fx, $fy, $fz ];
 
1622
          ($r_squared < $rmax_squared) && # keep the ones within rmax
 
1623
            (push @$r_cluster, $this_site);
 
1624
          ## (push @$r_neutral, $this_site);
 
1625
        };
 
1626
      };
 
1627
    };
 
1628
  };
 
1629
 
 
1630
  ## =============================== sort the cluster (& neutral clus.)
 
1631
  foreach my $r_list ($r_cluster) { ##, $r_neutral) {
 
1632
    @$r_list = sort {
 
1633
      ($a->[4] cmp $b->[4])     # sort by distance squared or ...
 
1634
        or
 
1635
      ($ {$a->[3]}->{Element} cmp $ {$b->[3]}->{Element})
 
1636
        or
 
1637
      ($a->[7] cmp $b->[7])     # by z value or ...
 
1638
        or
 
1639
      ($a->[6] cmp $b->[6])     # by y value or ...
 
1640
        or
 
1641
      ($a->[5] cmp $b->[5]);    # by x value
 
1642
      ##        or
 
1643
      ## ($ {$b->[3]}->{Host} <=> $ {$a->[3]}->{Host}); # hosts before dopants
 
1644
    } @$r_list;
 
1645
  };
 
1646
 
 
1647
  ## final adjustment to the formulas, store the formulas for the
 
1648
  ## central atom ...
 
1649
  $keys -> {cformulas} =
 
1650
    [$$r_cluster[0][8], $$r_cluster[0][9], $$r_cluster[0][10]];
 
1651
  ##   ## ... subtract the central atom coordinates from each site ...
 
1652
  ##   foreach my $site (reverse(@$r_cluster)) {
 
1653
  ##     (@$site[8..10]) =
 
1654
  ##       ($$site[8] . " - Xc", $$site[9] . " - Yc", $$site[10] . " - Zc");
 
1655
  ##   };
 
1656
  ##   ## ... and set the central atom to an empty string
 
1657
  ##   ($$r_cluster[0][8], $$r_cluster[0][9], $$r_cluster[0][10]) = ("", "", "");
 
1658
 
 
1659
  ## if this is a tetragonal crystal in the C or F setting , rotate
 
1660
  ## all the coordinates back to the original setting
 
1661
  if ($do_tetr) {
 
1662
    my ($a, $b) = ($cell->{A}, $cell->{B});
 
1663
    $cell -> make(A=>$a*sqrt(2), B=>$b*sqrt(2));
 
1664
  };
 
1665
};
 
1666
 
 
1667
 
 
1668
sub rectify_formula {
 
1669
  my ($fx, $fy, $fz, $nnx, $nny, $nnz, $r_cformula) = @_;
 
1670
  my $debug_formulas = 1;
 
1671
  ##open DEBUG, '>debug.formulas' or die $!;
 
1672
  #warn "input: ", join(" | ",$fx, $fy, $fz, $nnx, $nny, $nnz, $/);
 
1673
  ## apply bravais translation
 
1674
  map {
 
1675
    if    ($_ < 0)  { $_ = ' + '.$_ }
 
1676
    elsif ($_ == 0) { $_ = '' }
 
1677
    else            { $_ = ' + '.$_ };
 
1678
  } ($nnx, $nny, $nnz);
 
1679
  ($fx, $fy, $fz) = ($fx . $nnx, $fy . $nny, $fz . $nnz);
 
1680
  ## convert fractions to decimal
 
1681
  map { s|(\d)/(\d)|$1/$2|ge } ($fx, $fy, $fz);
 
1682
  # collapse the numeric fields
 
1683
  map {
 
1684
    my @list = reverse(split(/\s*\+\s*/, $_));
 
1685
    #print join('|', @list, $/);
 
1686
    my $sum = 0;
 
1687
  LIST: while (@list) {
 
1688
      my $this = shift @list;
 
1689
      #print $this, " ";
 
1690
      if ($this =~ /^-?[xyz]/) {        # whoops! put the variable back on
 
1691
        unshift @list, $this;
 
1692
        last LIST;
 
1693
      };
 
1694
      $sum = eval '$sum + $this';       # eval the constants
 
1695
    };
 
1696
    if ($sum) {                         # prettify the number and unshift it
 
1697
      $sum = sprintf("%10.6f", $sum);
 
1698
      $sum =~ s/\.?0+$//;
 
1699
      $sum =~ s/^\s+//;
 
1700
      unshift @list, $sum;
 
1701
    };
 
1702
    @list = reverse(@list);
 
1703
    $_ = join("+", @list);              # make a math expression
 
1704
    s/\+-/-/g;                          # finally clean up the + and - signs
 
1705
    s/([+-])/ $1 /g;
 
1706
    s|^ - |-|;
 
1707
  } ($fx, $fy, $fz);
 
1708
  # all done!
 
1709
  #warn "output: ", join(" ", $fx, $fy, $fz, $/ x 2);
 
1710
  return ($fx, $fy, $fz);
 
1711
};
 
1712
 
 
1713
=head2 C<rcfile_name>
 
1714
 
 
1715
This takes no arguments and returns the name of the Atoms runtime
 
1716
configuration file belonging to the user.  This does the ``right
 
1717
thing'' on the different platforms.
 
1718
 
 
1719
=cut
 
1720
 
 
1721
sub rcfile_name {
 
1722
  return "???" if (&rcdirectory() eq '???');
 
1723
  return File::Spec -> catfile(&rcdirectory(), "tkatoms.ini")
 
1724
    if (($^O eq 'MSWin32') or ($^O eq 'cygwin'));
 
1725
  return File::Spec -> catfile(&rcdirectory(), "atomsrc");
 
1726
};
 
1727
sub rcdirectory {
 
1728
  if ($^O eq 'VMS') {
 
1729
    return "???";
 
1730
  } elsif ($^O eq 'os2') {
 
1731
    return "???";
 
1732
  } elsif ($^O eq 'MacOS') {
 
1733
    return $lib_dir;
 
1734
  } elsif (($^O eq 'MSWin32') or ($^O eq 'cygwin')) {
 
1735
    return File::Spec->catfile($ENV{FEFFIT_DIR}, "horae") if defined($ENV{FEFFIT_DIR});
 
1736
    return "???";
 
1737
  } else {
 
1738
    my $home;
 
1739
    eval '$home = $ENV{"HOME"} || $ENV{"LOGDIR"} || (getpwuid($<))[7];' or
 
1740
      $home = "";
 
1741
    return $home . "/.horae/";
 
1742
  };
 
1743
};
 
1744
 
 
1745
sub read_rc {
 
1746
  open RC, $_[0] or die "could not open $_[0] as a configuration file\n";
 
1747
  my $mode = "";
 
1748
  while (<RC>) {
 
1749
    next if (/^\s*$/);
 
1750
    next if (/^\s*\#/);
 
1751
    if (/\[(\w+)\]/) {
 
1752
      $mode = lc($1);
 
1753
      next;
 
1754
    };
 
1755
    chomp;
 
1756
    s/^\s+//;
 
1757
    unless ($_ =~ /\$c_/) {s/\s*\#.*$//;};
 
1758
    my @line = split(/\s*[ \t=]\s*/, $_);
 
1759
    (defined $line[1]) and
 
1760
      (($line[1] eq "''") or ($line[1] eq '""')) and $line[1] = '';
 
1761
  MODE: {
 
1762
      ($line[0] =~ /^\s*\$([a-zA-Z_]+)/) and do {
 
1763
        my $var = $1;
 
1764
        $line[1] =~ s/[;\'\"]//g;
 
1765
        if ($var =~ /^c_/) {    # colors
 
1766
          ##      my $v = substr($var, 2);
 
1767
          ##      $colors{$v} = $line[1];
 
1768
          ##      if (($colors{$v} =~ /^[0-9a-fA-F]{6}$/) or
 
1769
          ##          ($colors{$v} =~ /^[0-9a-fA-F]{12}$/)) {
 
1770
          ##        $colors{$v} = '#'.$line[1];
 
1771
          ##      };
 
1772
        } elsif ($var =~ /^f_/) { # fonts
 
1773
          ##      my $v = substr($var, 2);
 
1774
          ##      $fonts{$v} = join(" ", @line[1..$#line]);
 
1775
          ##      $fonts{$v} =~ s/[;\'\"]//g;
 
1776
        } else {                # meta
 
1777
          $meta{$var} = $line[1];
 
1778
        };
 
1779
        last MODE;
 
1780
      };
 
1781
      ($mode eq 'meta') and do {
 
1782
        $meta{$line[0]} = $line[1];
 
1783
        last MODE;
 
1784
      };
 
1785
      ### don't need to parse colors and fonts here
 
1786
      ## ($mode eq 'colors') and do {
 
1787
      ##        $colors{$line[0]} = $line[1];
 
1788
      ##        (($colors{$line[0]} =~ /[0-9a-fA-F]{6}/) or
 
1789
      ##         ($colors{$line[0]} =~ /[0-9a-fA-F]{12}/)) and
 
1790
      ##           $colors{$line[0]} = '#'.$line[1];
 
1791
      ##        last MODE;
 
1792
      ## };
 
1793
      ## ($mode eq 'fonts') and do {
 
1794
      ##        $fonts{$line[0]} = join(" ", @line[1..$#line]);
 
1795
      ##        last MODE;
 
1796
      ## };
 
1797
    };
 
1798
  };
 
1799
};
 
1800
 
 
1801
=head2 C<rcvalues>
 
1802
 
 
1803
This takes no argmuents and returns the hash containing the values of
 
1804
variables (but not of fonts or colors) read from the rc file.
 
1805
 
 
1806
=cut
 
1807
 
 
1808
sub rcvalues {
 
1809
  shift;
 
1810
  return %meta;
 
1811
##   ##            0                  1                  2
 
1812
##   return ($always_write_feff,   $atoms_language,    $write_to_pwd,
 
1813
##   ##            3                  4                  5
 
1814
##        $prefer_feff_eight,   $absorption_tables, $dafs_default,
 
1815
##   ##            6                  7                  8
 
1816
##        $plotting_hook,       $default_filepath,  $display_balloons,
 
1817
##   ##            9                 10                 11
 
1818
##        $no_crystal_warnings, $one_frame,         $convolve_dafs,
 
1819
##   ##           12                 13
 
1820
##        $never_ask_to_save,   $ADB_location)
 
1821
};
 
1822
 
 
1823
=head2 C<number>
 
1824
 
 
1825
This takes a text string and attempts to evaluate it as a number.  It
 
1826
uses eval and so allows for simple math expressions, but it tries to
 
1827
be safe and not eval just any old math expression.  You can use this
 
1828
if you want to evaluate numbers in the same manner as Atoms.
 
1829
 
 
1830
    $x = number(1/3);
 
1831
    printf "%7.5f\n", $x;
 
1832
      |--> 0.33333
 
1833
 
 
1834
=cut
 
1835
 
 
1836
## this returns a text string as a proper number
 
1837
sub number {
 
1838
  my $num = $_[0];
 
1839
  (my $input = $num) =~ s/\s//g; # trim blanks
 
1840
  my $die = $_[1] || 0;
 
1841
  my $keys = $_[2] || Xray::Atoms->new();
 
1842
  ##my $m = $num+0;
 
1843
  my $num_match = '([+-]?(\d+\.?\d*|\.\d+))';
 
1844
  ($num =~ /^\s*$/) && return 0;       # null value
 
1845
  ##if ($num == $m) {
 
1846
  if ($num =~ /^\s*$num_match\s*$/) {  # floating point number
 
1847
    ($num = sprintf "%9.5f", $num) =~ s/^\s+//;
 
1848
    return $num;
 
1849
  };                            # simple binary operation
 
1850
  if ($num =~ /^\s*($num_match)\s*(\-|\+|\/|\*)\s*($num_match)\s*$/) {
 
1851
    $num = eval $num;
 
1852
    ($num = sprintf "%9.5f", $num) =~ s/^\s+//;
 
1853
    return $num;
 
1854
  };
 
1855
  #(abs($num) < $Xray::Atoms::epsilon) && ($num = 0);
 
1856
  ## interpret angle as 80.23'45"
 
1857
  my $message = join("", "The string \"", $num, "\" was found among the atom coordinates.  Atoms doesn't know what to do with it.  The parameter was set to zero.  You might want to verify your input data.", $/);
 
1858
  $keys -> warn_or_die($message, $die);
 
1859
  print caller;
 
1860
  return 0;
 
1861
};
 
1862
 
 
1863
# Autoload methods go after =cut, and are processed by the autosplit program.
 
1864
 
 
1865
1;
 
1866
__END__
 
1867
 
 
1868
 
 
1869
 
 
1870
=head1 MORE INFORMATION
 
1871
 
 
1872
There is more information available in the Atoms document.  There you
 
1873
will find complete descriptions of atp files, calculations using the
 
1874
Xray::Absorption package, keywords in atoms input files and lots of
 
1875
other topics.
 
1876
 
 
1877
 
 
1878
=head1 AUTHOR
 
1879
 
 
1880
  Bruce Ravel <ravel@phys.washington.edu>
 
1881
  Atoms URL: http://feff.phys.washington.edu/~ravel/software/atoms/
 
1882
 
 
1883
 
 
1884
=cut
 
1885
 
 
1886
## Local Variables:
 
1887
## time-stamp-line-limit: 25
 
1888
## End: