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

« back to all changes in this revision

Viewing changes to Atoms/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: