#!/usr/bin/env perl

# --8<--8<--8<--8<--
#
# Copyright (C) 2006-2010 Smithsonian Astrophysical Observatory
#
# This file is part of trace-nest
#
# trace-nest is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# -->8-->8-->8-->8--

use v5.10.0;

use strict;
use warnings;

# use our Perl libraries first.  May need to override
# system Perl's
use lib '/build/phase1/lib/perl5';

use File::Basename;
use File::Spec::Functions qw( catfile catpath splitpath rel2abs catdir);
use Text::ParseWords;
use File::Temp;

use Config::PFiles::Path;
use Config::Wild;

use Set::IntSpan;

use CIAO::Lib::Param qw/ pquery pset /;

use IPC::PrettyPipe::DSL qw( :all );
use MST::Raytrace qw( read_tot_wt write_tot_wt read_focus
  write_summary read_summary );

# load MST_Env, if available
if ( eval "use MST_Env" ) {
    # save PFILES; don't screw over user
    my %EXPORT = MST::envs();
    delete $EXPORT{PFILES};
    %ENV = ( %ENV, %EXPORT );
}

our $VERSION = '2.2.10';

our ( %param, %oparam, %dbflag, @clean_files, $cfg );

our $prog = basename( $0, '.pl' );
our $logpfx = "# $$ $prog: ";

our $traceshell = 'trace-shell';

use constant BP2FITS_CONFIG_DIR => catdir($ENV{SAOTRACE_DATADIR} // '/build/phase1/share', 'bp2fits');

eval { main() };
my $error = $@;
clean_up();
if ( $error ) {
    print STDERR ( $logpfx, $error );
    exit( 1 );
}

exit( 0 );

sub main {
    get_parameters();

    help( 2 ) if $param{help};

    do { print "$prog $VERSION\n"; return }
      if $param{version};

    {
        # don't leave parameter files from the raytrace pipelines lying around
        # by creating a temp directory where they will be stored
        local $ENV{PFILES} = $ENV{PFILES};
        my $tmp_pfiles_dir = File::Temp->newdir();

        Config::PFiles::Path->prepend( RW => $tmp_pfiles_dir->dirname );

        if ( $param{shells}->size > 1 ) {

            my $output = trace_shells();

            mix_shells( $output )
              unless $dbflag{reuse};
        }

        else {
            trace_shell();
        }
    }

    summarize();

    # update parameter file
    pset( $prog, %oparam );
}


sub trace_shells {
    my %tot_wt_in = ( n => 0, wt => 0, n_ghosts => 0, wt_ghosts => 0 );

    my @output;

    while ( my $shell = $param{shells}->next ) {
        my $tag = sprintf( "%s_%03d", $param{tag}, $shell );
        my $output = $tag . '.bp';

        push @output, $output;

        my $pipe = ppipe;
        $pipe |= ppcmd(
            $traceshell,
            argsep( '=' ),
            [
                tag           => $tag,
                src           => $param{src},
                srcpars       => $param{srcpars},
                output        => 'OUTPUT',
                output_fmt    => 'bp',
                output_coord  => 'osac',
                output_fields => $param{output_fields},
                shell         => $shell,
                seed1         => $param{seed1},
                seed2         => $param{seed2},
                block         => $param{block},
                block_inc     => $param{block_inc},
                tstart        => $param{tstart},
                limit_type    => $param{limit_type},
                limit         => $param{limit}->value( $shell ),
                focus         => $param{focus} ? 'yes' : 'no',
                z             => $param{z}->value( $shell ),
                tally         => $param{tally},
                config_dir    => $param{config_dir},
                config_db     => $param{config_db},
                debug         => $param{debug},
                mode          => 'H',
            ] );

        $param{block} += 100 * $param{block_inc};

        clean_trace_files( $tag, qw( .bp ) )
          unless $dbflag{reuse};

        $pipe->valsubst( qr/^OUTPUT$/, 'stdout', lastvalue => $output );
        print STDERR $pipe->render, "\n"
          if $dbflag{'pcomm'};

        unless ( exists $dbflag{'noexec'} ) {

            $pipe->run or die "error in raytrace pipe\n";

            my $summary = read_summary( $tag . '.summary' )->{$shell};
            die( "couldn't find summary for shell $shell\n" )
              unless defined $summary;

            $tot_wt_in{$_} += $summary->{tot_wt_in}{$_} foreach keys %tot_wt_in;

            $param{block} = $summary->{block};
        }
        else {
            $param{block} += 100 * $param{block_inc};
        }

    }

    write_tot_wt( "$param{tag}.tot_wt.in.log", \%tot_wt_in )
      unless defined $dbflag{noexec} || $dbflag{reuse};

    return \@output;
}

sub mix_shells {

    my ( $output ) = @_;

    # now, mix rays from the different shells

    my $pipe = ppipe;
    $pipe |= ppcmd(
        mergerays => argsep( '=' ),
        [
            input   => join( ',', @$output ),
            output  => 'OUTPUT',
            method  => 'time',
            seed1   => $param{seed1},
            seed2   => $param{seed2},
            block   => $param{block},
            version => 'no',
            xfer    => 'nrings',
            mode    => 'H',
        ] );

    $param{block} += $param{block_inc};

    # if focus, need to do some magic
    if ( $param{focus} ) {
        $pipe |= ppcmd(
            saofocus => argsep( '=' ),
            [
                input        => 'stdin',
                output       => 'OUTPUT',
                onlygoodrays => 'yes',
                gi_filename =>
                  sprintf( "%s_%03d.gi", $param{tag}, $param{shells}->first ),
                logfile => $param{tag} . '.focus.log',
                help    => 'no',
                mode    => 'H',
            ] );

        # saofocus adds a bunch of fields for some reason.  trim them
        output_fields( $pipe, $param{output_fields} );

        # focus sets z to zero (bogus!). must correct
        $pipe |= ppcmd(
            photz => argsep( '=' ),
            [
                input  => 'stdin',
                output => 'OUTPUT',
                z      => $param{z}->value( 99999 ),
                mode   => 'H',
            ] );

    }

    # if focus isn't used, must project rays to requested axial position
    if ( !$param{focus} && !defined $dbflag{noproject} ) {

        $pipe |= ppcmd(
            projray => argsep( '=' ),
            [
                input  => 'stdin',
                output => 'OUTPUT',
                z      => $param{z}->value( 99999 ),
                mode   => 'H',
            ] );
    }

    my $logfile = $param{tag} . '.tot_wt.out.log';
    $pipe |= ppcmd(
        tot_wt => argsep( '=' ),
        {
            input   => 'stdin',
            output  => 'OUTPUT',
            logfile => $logfile,
            mode    => 'H',
        } );

    push @clean_files, $logfile;

    $pipe |= throttle() if $param{throttle};
    $pipe |= coord_xfrm( $param{output_coord} );

    $pipe |= reformat( $param{output_fmt} );

    $pipe->valsubst( qr/^OUTPUT$/, 'stdout', lastvalue => $param{output} );
    print STDERR $pipe->render, "\n"
      if $dbflag{'pcomm'};

    unless ( defined $dbflag{noexec} ) {

        $pipe->run or die "error in raytrace pipe\n";

        open( BLOCK, ">$param{tag}.block" )
          or die( "unable to create $param{tag}.block\n" );

        print BLOCK $param{block}, "\n";

        close BLOCK;

        clean_trace_files( $param{tag} );
    }
}

sub trace_shell {
    my $shell = $param{shells}->first;

    my $pipe = ppipe;
    $pipe |= ppcmd(
        $traceshell => argsep( '=' ),
        [
            tag           => $param{tag},
            src           => $param{src},
            srcpars       => $param{srcpars},
            output        => 'OUTPUT',
            output_fmt    => 'bp',
            output_coord  => 'osac',
            output_fields => $param{output_fields},
            shell         => $shell,
            seed1         => $param{seed1},
            seed2         => $param{seed2},
            block         => $param{block},
            block_inc     => $param{block_inc},
            tstart        => $param{tstart},
            limit_type    => $param{limit_type},
            limit         => $param{limit}->value( $shell ),
            focus         => $param{focus} ? 'yes' : 'no',
            z             => $param{z}->value( $shell ),
            tally         => $param{tally},
            config_dir    => $param{config_dir},
            config_db     => $param{config_db},
            debug         => $param{debug},
            mode          => 'H',
        ] );

    clean_trace_files( $param{tag} )
      unless $dbflag{reuse};

    $pipe |= throttle() if $param{throttle};

    $pipe |= coord_xfrm( $param{output_coord} );

    $pipe |= reformat( $param{output_fmt} );

    $pipe->valsubst( qr/^OUTPUT$/, 'stdout', lastvalue => $param{output} );

    print STDERR $pipe->render, "\n"
      if $dbflag{'pcomm'};

    unless ( defined $dbflag{'noexec'} ) {
        $pipe->run or die "error in raytrace pipe\n";

        # grab last block used
        open( BLOCK, "$param{tag}.block" )
          or die( "unable to open $param{tag}.block\n" );
        chomp( $param{block} = <BLOCK> );
        close BLOCK;
    }
    else {
        $param{block} += 100 * $param{block};
    }

}

sub throttle {
    my $pipe = ppipe;

    $pipe |= ppcmd(
        mxwldmn => argsep( '=' ),
        [
            input  => 'stdin',
            output => 'OUTPUT',
            seed1  => $param{seed1},
            seed2  => $param{seed2},
            block  => $param{block},
            mode   => 'H',
        ] );

    $param{block} += $param{block_inc};

    if ( $param{throttle} > 0 ) {
        $pipe |= ppcmd(
            throttle => argsep( '=' ),
            [
                input   => 'stdin',
                output  => 'OUTPUT',
                n       => $param{throttle},
                poisson => $param{throttle_poisson} ? 'yes' : 'no',
                seed1   => $param{seed1},
                seed2   => $param{seed2},
                block   => $param{block},
                abort   => $param{throttle_poisson} ? 'lt' : 'none',
                mode    => 'H',
            ] );

        $param{block} += $param{block_inc};
    }

    my $logfile = $param{tag} . '.tot_wt.throttle.log';


    $pipe |= ppcmd(
        tot_wt => argsep( '=' ),
        [
            input   => 'stdin',
            output  => 'OUTPUT',
            logfile => $logfile,
            mode    => 'H',
        ] );

    push @clean_files, $logfile;

    return $pipe;
}


sub output_fields {

    my ( $pipe, $fields ) = @_;

    my @include;
    my @exclude;

    my @fields = split( ',', $fields );
    @include = grep { !/^-/ } @fields;
    @exclude = map { s/-//; $_ } grep { /^-/ } @fields;

    @include = () if grep { $_ eq 'all' } @include;
    @include
      = map { $_ eq 'min' ? qw[ position direction weight energy time ] : $_ }
      @include;

    if ( @include or @exclude ) {
        my $cmd = $pipe->add(
            bpmanip => [
                '--input' => 'stdin',
                @include ? ( '--include' => join( ',', @include ) ) : (),
                @exclude ? ( '--exclude' => join( ',', @exclude ) ) : (),
                '--output' => 'OUTPUT',
            ] );
        $cmd->argsep( ' ' );
    }

}

sub coord_xfrm {

    my ( $coord ) = @_;

    my $cmd;

    if ( $coord eq 'xrcf' ) {
        $cmd = ppcmd(
            osac2xrcf => argsep( '=' ),
            [
                input  => 'stdin',
                output => 'OUTPUT',
                mode   => 'H',
            ] );
    }
    elsif ( $coord eq 'hrma' ) {

        $cmd = ppcmd(
            osac2hrma => argsep( '=' ),
            [
                input  => 'stdin',
                output => 'OUTPUT',
                mode   => 'H',
            ] );
    }

    elsif ( $coord eq 'osac' ) {
        $cmd = ppipe;
    }

    else {
        die( "unknown output coordinate system: $coord\n" );
    }

    return $cmd;
}

# convert to final output format
sub reformat {
    my ( $fmt ) = @_;

    my $ext;
    my $cmd;

    # native is bpipe; do nothing
    if ( $fmt =~ /^bp(ipe)?$/ ) {
        $ext = 'bp';
        $cmd = ppipe;
    }

    # old fullray style
    elsif ( $fmt =~ /^fr|fullray$/ ) {
        $ext = 'fr';
        $cmd = ppcmd(
            bp2fr => argsep( '=' ),
            [
                input  => 'stdin',
                output => 'OUTPUT',
                mode   => 'H',
            ] );
    }

    # rdb
    elsif ( $fmt eq 'rdb' ) {
        $ext = 'rdb';
        $cmd = ppcmd(
            bp2rdb => argsep( '=' ),
            [
                input  => 'stdin',
                output => 'OUTPUT',
                mode   => 'H',
            ] );
    }

    # FITS of some flavor
    elsif ( $fmt =~ /^fits(?:-(?<subtype>.*))?$/ ) {
        $ext = 'fits';
        my $trans = 'default';

        if ( defined $+{subtype} ) {
            $trans = catfile( BP2FITS_CONFIG_DIR, "$+{subtype}.rdb" );
            die(
                "unknown fits output format: $+{subtype} ($trans doesn't exist)\n"
            ) unless -f $trans;
        }

        $cmd = ppcmd(
            bp2fits => argsep( '=' ),
            [
                input      => 'stdin',
                output     => 'OUTPUT',
                trans      => $trans,
                longstrn   => 'yes',
                reserved   => 'yes',
                doublepass => 'yes',
                vectors    => 'no',
                verbose    => 0,
                mode       => 'H',
            ] );
    }

    else {
        die( "unknown output format: $fmt\n" );
    }


    # create output file name if requested
    $param{output} = sprintf( "%s.%s", $param{tag}, $ext )
      if $param{output} eq 'default';

    return $cmd;
}

sub summarize {

    return if $dbflag{noexec};


    return if $param{shells}->size == 1;

    my @summary;

    $param{shells}->start;

    my $calculate_ea = $param{limit_type} =~ m{^r/};

    my $ea      = 0;
    my $ea_err2 = 0;
    while ( my $shell = $param{shells}->next ) {
        my $summary
          = read_summary( sprintf( "%s_%03d.summary", $param{tag}, $shell ) )
          ->{$shell};
        push @summary, $summary;

        if ( $calculate_ea && exists $summary->{ea} ) {
            $ea += $summary->{ea}{ea};
            $ea_err2 += $summary->{ea}{err}**2;
        }

        else {
            $calculate_ea = 0;
        }
    }

    my %summary = (
        shell      => 99999,
        tot_wt_in  => scalar read_tot_wt( "$param{tag}.tot_wt.in.log" ),
        tot_wt_out => scalar read_tot_wt( "$param{tag}.tot_wt.out.log" ),
        param      => \%oparam,
    );

    if ( $calculate_ea ) {

        $summary{ea}{ea}  = $ea;
        $summary{ea}{err} = sqrt( $ea_err2 );
        $summary{ea}{n}   = $summary{tot_wt_out}{n};
    }

    $summary{focus} = read_focus( "$param{tag}.focus.log" )
      if $param{focus};

    push @summary, \%summary;

    write_summary( "$param{tag}.summary", @summary );
}



# calling out the names is pretty icky.  instead, should blow away
# everything BUT the files that should be saved.  the problem with
# that is there may be pre-existing files in the directory and it
# would be uncool to delete those.

sub clean_trace_files {
    my ( $tag, @files ) = @_;

    push @clean_files, map { $tag . $_ } @files, qw(
      .block
      .gi
      .tot_wt.in.log
      .tot_wt.out.log
      .aperture.cap.lua
      .aperture.cap.log
      .aperture.ghostbaffle.lua
      .aperture.ghostbaffle.in.log
      .aperture.ghostbaffle.out.log
      .aperture.postcollimator.lua
      .aperture.postcollimator.log
      .aperture.precollimator.lua
      .aperture.precollimator.log
      .focus.log
      .summary.yml
      .raygen.srcpars.lua
      .raygen.cfgpars.lua
      .raygen.log
      _cap-aperture.log
      .intercept.h.log
      .intercept.p.log
      .reflect.h.log
      .reflect.p.log
      .scatter.h.log
      .scatter.p.log
    );

    return;
}

# clean up intermediate files
sub clean_up {
    if ( exists $param{clean} && $param{clean} =~ /all|rays/ ) {
        @clean_files = grep ( !/$param{'output'}/, @clean_files );

        if ( 'rays' eq $param{clean} ) {
            unlink grep( /\.bp$/, @clean_files );
        }
        else {
            unlink @clean_files;
        }
    }
}

sub get_parameters {

    %param = %oparam = pquery( $prog, \@ARGV );

    return 1 if $param{version};
    return 1 if $param{help};

    # debug flags
    foreach ( quotewords( ',', 0, $param{debug} ) ) {
        if ( /([\w-]+)\s*=\s*(.*)/ ) {
            $dbflag{$1} = $2;
        }
        else {
            $dbflag{$_}++;
        }
    }

    die( "block_inc must be greater than 100\n" )
      if ( $param{block_inc} < 100 );

    # add suffix if necessary
    $param{config_db} .= '.cnf' unless $param{config_db} =~ /[.]cnf$/;

    $cfg = Config::Wild->new( {
            path  => [ split( ':', $param{config_dir} ) ],
            UNDEF => sub {
                die( "$param{config_db}: ", "undefined keyword: $_[0]\n" );
            }
        } );

    $cfg->load( $param{config_db} );

    $param{shells} = parse_shells( $param{shells} );
    die( "no shells specified?\n" )
      if $param{shells}->empty;

    # ensure that rays are not specified as a limit type
    die( "the parameter `limit_type' cannot be a ray count\n" )
      if $param{limit_type} =~ /rays/;

    $param{z}     = ShellData->new( $param{z},     'z' );
    $param{limit} = ShellData->new( $param{limit}, 'limit' );

}

sub help {
    my ( $verbose ) = @_;

    require Pod::Usage;
    Pod::Usage::pod2usage( {
        -exitval => 0,
        -verbose => $verbose
    } );
}

sub parse_shells {

    my ( $shells ) = @_;

    my @shells;

    if ( $shells eq 'all' ) {
        @shells = read_shells_from_geometry( $cfg->mirror_geo_db );
    }

    elsif ( $shells eq 'active' ) {

        @shells = read_shells_from_geometry(
            $cfg->mirror_geo_db,
            sub {
                exists $_[0]->{active}
                  ? $_[0]->{active}
                  : 1;
            } );
    }

    else {
        # Set::IntSpan requires ordered spans.  make it easier on the user
        @shells = split( '[,]', $shells );

        my @bad = grep { !Set::IntSpan->valid( $_ ) } @shells;

        die( "unrecognized shell specification(s): ", join( ', ', @bad ), "\n" )
          if @bad;
    }

    # @shells may have duplicate entries; Set::IntSpan takes care of that
    return Set::IntSpan->new( @shells );
}

sub read_shells_from_geometry {

    my ( $file, $filter ) = @_;

    my @shells;

    my $rdb = RDB->new( $file )
      or die( "error opening $file\n" );

    my $have_shell = $rdb->type( 'shell' );

    my %row;
    while ( $rdb->read( \%row ) ) {
        @row{qw[ geo shell]} = $row{mirror} =~ /^(\D)+(\d)+$/
          unless $have_shell;

        next if $filter && !$filter->( \%row );

        push @shells, $row{shell};
    }

    return \@shells;
}


{
    package ShellData;

    use RDB;
    use Scalar::Util qw[ looks_like_number ];

    sub new {

        my ( $class, $value, $col ) = @_;

        my $self = bless {}, $class;

        if ( !looks_like_number( $value ) ) {

            $self->{file} = $value;
            $self->{col}  = $col;
            my $rdb = RDB->new( $self->{file} )
              or die( "error opening $self->{file}\n" );

            my %row;
            while ( $rdb->read( \%row ) ) {
                $self->{value}{ $row{shell} } = $row{$col};
            }
        }

        else {

            $self->{value} = $value;

        }

        return $self;
    }

    sub value {

        my ( $self, $shell ) = @_;

        if ( 'HASH' eq ref $self->{value} ) {

            die(
                sprintf(
                    "%s: no %s value for shell %d\n",
                    $self->{file}, $self->{col}, $shell
                ) ) unless exists $self->{value}{$shell};

            return $self->{value}{$shell};
        }

        else {

            return $self->{value};
        }
    }
}


__END__

=pod

=head1 NAME

trace-nest - ray trace a nest of shells, hey!

=head1 SYNOPSIS

B<trace-nest> I<options>

=head1 ARGUMENTS

B<trace-nest> uses an IRAF-compatible parameter interface. See the
section on L</Setup> below.  The available parameters are:

=over 8

=item B<tag>

A prefix to be used on all intermediate files created.  There are lots
of intermediate files; see the section on L<Intermediate Files>.

=item B<src>

The location of a B<raygen> compatible source script.  If it is the
string C<default>, the value of the C<source_spec> keyword in the
B<trace-nest> configuration file is used.

=item B<srcpars>

Extra parameters to be passed to the source script.  See the
documentation for the source script for information on which
parameters are available.  script accepts

These are passed directly to B<raygen> via the C<source_override>
parameter.

=item B<shells>

The shells that should be raytraced and merged.  The value may be take
one of the following values:

=over

=item C<all>

All of the shells defined in the mirror geometry database will be
raytraced.

=item C<active>

All of the shells defined in the mirror geometry database which are
marked as C<active> will be raytraced.  If the geometry database does not
provide the C<active> attribute, all of the shells are raytraced.

=item I<list>

A comma delimited list of individual shell ids or ranges
(I<min>-I<max>).  For example:

  shells=1-10,23,45

=back

=item B<output>

The output stream to which to write the rays.  It may be a filename,
or the string C<stdout>, in which case rays will be written to the
standard output stream.  If it is the string C<default>, a file name
will be created by appending the B<output_fmt> to the B<tag> (with an
intervening period).

=item B<output_fmt>

The output format of the rays.  May be one of C<fr>, C<bp>, C<bpipe>, C<rdb>,
or a C<fits> variant.  See L<Output Formats> for more information.

=item B<output_coord>

The output coordinate system of the rays.  May be one of C<osac>,
C<hrma>, C<xrcf>.

=item B<output_fields>

Which data fields to output for each ray.  The value may be one of

=over

=item C<all>

A rather large amount of information.

=item <field names>

A comma delimited list of field names to output.  Field names may be
prefixed with C<->, indicating that they are to be I<removed> from the
list of output fields.  If the only fields specified are those to be
removed, the initial output list contains all of the fields in the data.

The field name C<min> is an alias for specifying the following fields:

  position direction weight energy time

The order of additive and subtractive fields is unimportant; all additive
fields are inserted into the list before the subtractive fields are removed.

=back

=item B<seed1>

The first seed for the random number generator.  It must be in the range
[1,2147483562].

=item B<seed2>

The second seed for the random number generator.  It must be in the range
[1,214748339]

=item B<block>

The random number block to start at.  It must be in the range [0,1048575].

=item B<block_inc>

The spacing between random number blocks for each random process.  100
is a good number.

=item B<tstart>

The start time of the observation in seconds.  If less than zero and
jitter is turned on, the start of the valid jitter time range is used.


=item B<limit>

The numerical value of the limit at which to stop generating rays at
the telescope entrance aperture. The number of rays which reach the
focal plane are typically lower than this.  The B<limit_type>
parameter specifies the units for this value.

B<limit> may be either a floating point number, in which it is used for
all shells, or the name of a file containing I<limit> values for all
shells.  The file must be an B<RDB> formatted file with columns
I<shell> and I<limit>.

If C<limit_type> is a unit of time, this is added to the
start time (see C<tstart>) to determine the stop time of the
simulation. If jitter is on and this is set to C<0>, then the stop
time is set equal to the end of the valid jitter time range.

=item B<limit_type>

The units of the limit at which to stop generating rays.

=over 8

=item C<ksec>

kiloseconds of observation time

=item C<sec>

seconds of observation time

=item C<r/mm2>

a ray density at the entrance aperture in rays / mm^2

=item C<r/cm2>

a ray density at the entrance aperture in rays / cm^2

=back

=item B<ray_dist>

The ray distribution at the entrance aperture.  It may be one of C<random> or
C<ringspoke>.  I<Currently,> C<ringspoke> I<is ignored.>

=item B<nrings>

The number of rings to use if the B<ray_dist> parameter is C<ringspoke>.

=item B<nspokes>

The number of spokes to use if the B<ray_dist> parameter is C<ringspoke>.

=item B<focus>

A boolean parameter indicating that the focus of the system is to be
determined.  See the L</Focus> section for more details.

=item B<z>

The position along the I<Z> (optical) axis at which to leave the rays.
This is in the OSAC coordinate system.

B<z> may be either a floating point number, in which it is used for
all shells, or the name of a file containing I<Z> values for all
shells.  The file must be an B<RDB> formatted file with columns
I<shell> and I<z>.  The I<Z> value for the combined shells should have
a I<shell> value of C<99999>.

=item B<tally>

If non-zero, a tally of rays will be written to the standard
error stream every C<tally> rays.  This is useful if you're wondering
why it's taking so long to run the raytrace.  This tallies the number
of rays which make it out of the nest, after all of the post-optic
apertures.

=item B<throttle>

If non-zero, this specifies the number of rays which should be
output after the shells are merged.  Rays are first eliminated
based upon their probability of reflection, and then only the
requested number are passed through through (provided enough are
available).  The number may be varied in a Poisson fashion by setting
B<throttle_poisson>.

If negative, rays will be eliminated by their probability of
reflection, but no total limit to their number will be set.

A side effect of this option is that all rays will have a weight of one.

=item B<throttle_poisson>

If true, and if B<throttle> is non-zero, then the number specified by
B<throttle> is treated as the mean of a Poisson distribution, and the
number of rays actually output will be drawn from that distribution.

=item B<clean>

Which intermediate files to delete.  It may be C<none>, in which case
nothing is removed, C<rays> in which case intermediate ray files are
removed, or C<all> in which everything is removed.

=item B<config_dir>

The directory containing the B<trace-nest> configuration file.

=item B<config_db>

The name of the configuration file which provides the details of the
HRMA configuration.  See the L<Configuration File> section below.

=item B<version>

Print out the version information and exit.

=item B<help>

Print out this message and exit.

=item B<debug>

A comma separated list of debugging options.  See the L</Debugging>
section for more information.

=back

=head1 DESCRIPTION

B<trace-nest> raytraces a nest of Wolter type I X-ray telescope
shells with various apertures and baffles.  It was designed around the
AXAF HRMA, but may be used for other systems.  It works by using
B<trace-shell> to raytrace each shell, finally merging them into a
single file.  It traces each shell sequentially, storing each shells'
rays on disk; it ends up using twice as much disk space as you think.
It'll clean up after itself, though (see the B<clean> parameter).

B<trace-nest> uses a variety of programs to accomplish the raytrace.
To see the actual raytrace command pipeline, use the B<debug> C<pcomm>
option.

=head2 Setup

B<trace-nest> uses an IRAF compatible parameter interface.  Because
it calls many other programs, you will actually need to have parameter
files for all of them handy.

To simplify things, there is a command (B<trace-nest_setup>) which
creates copies in the current directory of the all of the required
parameter files.

=head2 Configuration File

The B<trace-nest> configuration file (specified by the B<config_dir>
and B<config_db> parameters) describes the telescope
configuration. Before you create your own, look at
F</proj/axaf/simul/databases/ts_config/00Index.html> and see if
there's one to suit your fancy.  Also, note that B<trace-nest> can
only use configuration files with a C<.cnf> suffix.  For more
information on raytrace configuration files, etc. see L<ts_config>.

=head2 Intermediate Files

B<trace-nest> produces a few intermediate files. Each file is given a
prefix which consists of the value of the B<tag> parameter followed
the shell number.  For the final set of merged rays, the shell number
is left off. For example, if B<tag> is C<foo>, you'll get files of

	foo_1.tot_wt.in.log foo_2.tot_wt.in.log

and

	foo.tot_wt.in.log

If B<trace-nest> is only tracing one shell, it doesn't include the shell
number in the file name.

=over 8

=item B<tag.bp>

The rays for the particular shell, in B<bpipe> format.

=item B<tag.gi>

This is a rather arcanely formatted file required by B<SAOdrat>.  It's
not of much general interest.

=item B<tag.tot-wt.in.log>

This file contains the number and weight of the rays at the entrance
aperture.  It is produced by B<tot_wt>.

=item B<tag.tot_wt.out.log>

This file contains the number and weight of the rays which have made
it through the entire configuration.  It is produced by B<tot_wt>.

=item B<tag.tot_wt.throttle.log>

This file contains the number and weight of the rays which have made
it through the entire configuration, after getting throttled.  It is
produced by B<tot_wt>.

=item B<tag.focus.log>

This is created during a focus run by B<saofocus>.

=back

=head2 Output Formats

B<trace-nest> outputs one of the following formats, specified by the
B<output_fmt> parameter:

=over 8

=item C<fr>

The C<fr> format has no header.  Each ray is in a C<fullray>
structure.  See F</proj/axaf/simul/include/fullray.h> for the formats
of the ray structure.

=item C<bp> or C<bpipe>

The rays are in C<bpipe> format. See the B<bpipe> documentation for
more information on this.

=item C<rdb>

The rays are written as an RDB table.

=item a C<fits> variant

Various FITS formatted outputs may be specified.  In all cases the
output must be to a file.

=over

=item C<fits> or C<fits-std>

The rays are written in the uncommon and seldom used AXAF Photon FITS
standard.

=item C<fits-events>

The rays are written in the much more common "events" format.  It
differs from the AXAF FITS Photon Standard in that the binary table is
named C<EVENTS>, the C<rt_> prefix is removed from the column names,
and the energy column is named C<energy> and is in units of eV.  Most
X-ray Astronomy software uses this convention.

=back


=back


=head2 Focus

If you wish to determine where the focal point for a given
configuration is, set the B<focus> parameter to C<yes>.  However,
because of bad interactions between the focus algorithm and wildly
scattered rays, micro-roughness induced ray scattering and ghost-ray
tracking is turned off when focussing. Additionally, the source is
forced to be the default source specified by the configuration file,
which should be a point source. The default source requires at least
one parameter, namely C<energy>. The focus procedure is carried out by
B<saofocus> which leaves its results in a file called
C<tag.focus.lis>, (where you've specified B<tag>).  This file is
pretty arcane; generally to extract the focus from there, run the
script F<getfocus> on it:

  getfocus tag.focus.lis

which will write out the focal position (in OSAC coordinates) to the
UNIX standard output stream.  Note that you'll get the focus of all of
the individual shells as well as the nest (unless you set B<clean> to
C<all>).

All of the shells' focal distances, including the focal distance
for the combined shells, is written to the file C<tag.focus.rdb>.
The combined shells are assigned a shell number of C<99999>.

=head2 Debugging

There are several B<debug> options available:

=over 8

=item C<pcomm>

Print out the raytrace command before executing it.  This gives you
some idea of which programs are running and what their inputs are.

=item C<noexec>

Generate the raytrace command and any required intermediate files, but
do not execute it.  Most useful with the B<pcomm> B<debug> option.

=item C<reuse>

Reuse the raytrace output from a previous B<identical> run to
regenerate the summary information.  C<noexec> must I<not> be
specified simultaneously.  The raytrace parameters should be
identical except for the addition of this flag.

=item C<noproject>

Do not project the rays to the value specified by the B<z> parameter.
This is a temporary kludge, and will probably not survive into the
next version of B<trace-nest>.

=item C<noghosts>

Ghost rays will not be propagated through the system.

=item C<saveblock>

The next unused random number block is written to F<tag.block>.

=back

=head1 SEE ALSO

B<trace-shell>, B<ts_config>

=head1 COPYRIGHT AND LICENSE

This software is Copyright The Smithsonian Astrophysical Observatory
and is released under the GNU General Public License.  You may find a
copy at: L<http://www.fsf.org/copyleft/gpl.html>

=head1 Author

Diab Jerius ( djerius@cfa.harvard.edu )
