# -*- perl -*-
#
# $Id: cosmo.pd,v 0.8 2000/05/26 02:27:06 burke Exp $
#
# Check in with:
#   ci -u<revision> cosmo.pd
#   (rcs -U cosmo.pd to ensure no locking)
#
# To do:
#   .) why does it core dump (during loading) if we 
#      the $VERSION variable in the PM code?
#   .) would be nice to allow lookback_time() to go to infinity
#   .) add ability to define return units - eg Mpc or cm for distances
#      this would make luminosity/flux code a lot nicer, for instance
#
# Notes:
#   .) perhaps should replace distance code (MATTER_OPEN|CLOSED) by
#      dl = (cz/H0) * ( 1 + z(1-q0)/[ sqrt(1+2 q0 z) + 1 + q0 z] )
#      { well, something like this anyway }
#      which is apparently better behaved at low q0z
#

use strict;

# set up the constants used below (in both the PM and C code)
#
# - perhaps should do it with bitwise manipulation?
use constant UNKNOWN       =>  0;
use constant EMPTY         =>  1;
use constant MATTER_FLAT   =>  2;
use constant MATTER_OPEN   =>  3;
use constant MATTER_CLOSED =>  4;
use constant LAMBDA_FLAT   => 10;
use constant LAMBDA_OPEN   => 11;
use constant LAMBDA_CLOSED => 12;

# For some reason, if the VERSION lines are included in the pp_addpm()
# bit, the module causes perl to core dump when it is loaded.
# (also seen with a trial version of this package, using different
# pp_def's, so I must have messed it up really well)
#
# left in so that Makefile.PL picks up a value for the version number,
# although I don't think it's much use!
#
# format got from ExtUtils::MakeMaker page
use vars qw( $VERSION );
( $VERSION ) = '$Revision: 0.8 $ ' =~ /\$Revision:\s+([^\s]+)/;

# PM stuff
pp_addpm('
use strict;

# use PDL;
use PDL::Astro::Constants  qw( LIGHT PARSEC PI FOURPI YEAR_TROPICAL );

## Variables

# used in determining what sort of a cosmology we have

use constant UNKNOWN       =>  ' . UNKNOWN . ';
use constant EMPTY         =>  ' . EMPTY . ';
use constant MATTER_FLAT   =>  ' . MATTER_FLAT . ';
use constant MATTER_OPEN   =>  ' . MATTER_OPEN . ';
use constant MATTER_CLOSED =>  ' . MATTER_CLOSED . ';
use constant LAMBDA_FLAT   => ' . LAMBDA_FLAT . ';
use constant LAMBDA_OPEN   => ' . LAMBDA_OPEN . ';
use constant LAMBDA_CLOSED => ' . LAMBDA_CLOSED . ';

');

pp_addpm( << 'EOD' );

####################################################################
####################################################################

## Public routines:

sub new {
    my $this  = shift;
    my $class = ref($this) || $this;

    # class structure
    my $self = {};

    # set up the default options, and apply any that are given
    $self->{options} = 
	new PDL::Options { 
	    OMEGA_MATTER => 1.0, 
	    OMEGA_LAMBDA => 0.0, 
	    H0           => 50.0,
	    ABSTOL       => 1.0e-5
	};
    $self->{options}->synonyms( { MATTER => 'OMEGA_MATTER', LAMBDA => 'OMEGA_LAMBDA', HO => 'H0' } );
    $self->{options}->incremental( 1 );

    bless $self, $class;

    # check values are sensible, sort out ancillary variables
    $self->setvars( @_ );

    # return the object
    return $self;

} # sub: new()

use overload ("\"\""   =>  \&PDL::Astro::Cosmology::string);
sub string {
    my $self = shift;
    my $om   = $self->matter();
    my $ol   = $self->lambda();
    my $h0   = $self->h0();
    return "[ Omega_matter = $om  Omega_lambda = $ol  H0 = $h0 km/s/Mpc ]";
} # sub: string()

sub setvars ($$) {
    my $self = shift;

    if ( ref($_[0]) eq "HASH" ) {
	$self->{options}->options( shift );
    } else {
	my %opts = ( @_ );
	$self->{options}->options( \%opts );
    }	

    my $dataref = $self->{options}->current();

    my $matter  = $self->{OMEGA_MATTER} = $$dataref{OMEGA_MATTER}; 
    my $lambda  = $self->{OMEGA_LAMBDA} = $$dataref{OMEGA_LAMBDA}; 
    my $h0      = $self->{H0}           = $$dataref{H0}; 
    my $abstol  = $self->{ABSTOL}       = abs( $$dataref{ABSTOL} ); 

    $self->{COSMOLOGY} = UNKNOWN; 

    # sensible values
    barf "ERROR: H0 must be >= 0.0.\n" unless $h0 >= 0.0;

    # conversion values (distance/volume/time calculations)
    # perhaps I should have called them HUBBLE_DIST/VOL/TIME ?
    if ( $h0 > $abstol ) {
	$self->{DCONV} = LIGHT * 1.0e-3 / $h0;
	$self->{VCONV} = LIGHT * LIGHT * LIGHT * 1.0e-9 / ( $h0 * $h0 * $h0);
	$self->{TCONV} = PARSEC * 1.0e3 / (YEAR_TROPICAL * $h0);
    } else {
	$self->{DCONV} = 1.0;
	$self->{VCONV} = 1.0;
	$self->{TCONV} = 1.0;
    }

    # just to ensure that it's defined
    my $kappa  = 1.0 - $matter - $lambda;
    $self->{OMEGA_KAPPA} = $kappa;
    $self->{KAPPA} = sqrt( abs($kappa) );

    # special case ?
    #
    if ( abs( $lambda ) <= $abstol ) {
	# lambda == 0.0

	if ( abs($matter) <= $abstol ) { 
	    $self->{COSMOLOGY} = EMPTY; 
	} elsif ( abs($kappa) <= $abstol ) { 
	    $self->{COSMOLOGY} = MATTER_FLAT; 
	} elsif ( $matter < 1.0 ) { 
	    $self->{COSMOLOGY} = MATTER_OPEN; 
	} else {
	    $self->{COSMOLOGY} = MATTER_CLOSED; 
	}

    } else {
	# lambda != 0.0

	if ( $kappa < -$abstol ) {
	    # Closed
	    $self->{COSMOLOGY} = LAMBDA_CLOSED;
	} elsif ( $kappa > $abstol ) {
	    # Open
	    $self->{COSMOLOGY} = LAMBDA_OPEN;
	} else {
	    $self->{COSMOLOGY} = LAMBDA_FLAT;
	}
    }

    # this would indicate a coding error!
    barf "Unknown cosmology - omega_matter = $matter  omega_lambda = $lambda.\n"
	if $self->{COSMOLOGY} == UNKNOWN;

} # sub: setvars()

sub omega_matter ($;$) {
    my $self = shift;
    $self->setvars( OMEGA_MATTER => $_[0] ) if @_;
    return $self->{OMEGA_MATTER};
}
*matter = \&omega_matter;

sub omega_lambda ($;$) {
    my $self = shift;
    $self->setvars( OMEGA_LAMBDA => $_[0] ) if @_;
    return $self->{OMEGA_LAMBDA};
}
*lambda = \&omega_lambda;

sub h0 ($;$) {
    my $self = shift;
    $self->setvars( H0 => $_[0] ) if @_;
    return $self->{H0};
}
*hO = \&h0;

# we ignore the need for K/evolutionary corrections
# in the following
#
# NOTE:
#   correct use of units is rather poor
#
sub abs_mag ($$$) {
    my ( $self, $apparent, $z ) = @_;

    return ( $apparent - 25 - 5 * $self->lum_dist($z)->log10() );

} # sub: abs_mag()
*absolute_magnitude = \&abs_mag;

sub app_mag ($$$) {
    my ( $self, $absolute, $z ) = @_;

    return ( $absolute + 25 + 5 * $self->lum_dist($z)->log10() );

} # sub: app_mag()
*apparent_magnitude = \&app_mag;

sub luminosity ($$$) {
    my ( $self, $flux, $z ) = @_;

    my $dl = $self->lum_dist($z) * 1.0e8 * PARSEC;  # convert to cm
    return ( FOURPI * $dl * $dl * $flux );

} # sub: luminosity()

sub flux ($$$) {
    my ( $self, $luminosity, $z ) = @_;

    my $dl = $self->lum_dist($z) * 1.0e8 * PARSEC;  # convert to cm
    return ( $luminosity / ( FOURPI * $dl * $dl ) );

} # sub: flux()

# note: 
#  one parameter:  age of z = 0  -> $1
#  two parameters: age of z = $1 -> $2
#
# ie lookback_time ( [ $z_low ], $z_high )
#
# note: we call the C code directly
#
sub lookback_time ($$;$) {
    my $self   = shift;
    my $z_low  = $#_ == 1 ? shift : 0;   # let PDL do the threading if z_high is a piddle
    my $z_high = shift;

    return _lookback_time( $z_low, $z_high, 
			   $self->{OMEGA_MATTER}, $self->{OMEGA_LAMBDA}, 
			   $self->{ABSTOL}, $self->{TCONV} );

} # sub: lookback_time

EOD

####################################################################
#
# distance measures: PM code
#
####################################################################

pp_addpm( << 'EOD' );

sub lum_dist ($$) {
    my $self = shift;
    my $z    = shift;

##    &PDL::__' . $name . '_dist_int( $z, ( my $answer = PDL->nullcreate($z) ),
    return _lum_dist( $z, $self->{COSMOLOGY}, $self->{OMEGA_MATTER}, $self->{OMEGA_LAMBDA}, 
		      $self->{KAPPA}, $self->{ABSTOL}, $self->{DCONV} );
    
} # sub: lum_dist()
*luminosity_distance = \&lum_dist;

sub pmot_dist ($$) {
    my $self = shift;
    my $z    = shift;

    return _lum_dist( $z, $self->{COSMOLOGY}, $self->{OMEGA_MATTER}, $self->{OMEGA_LAMBDA}, 
		      $self->{KAPPA}, $self->{ABSTOL}, $self->{DCONV} ) / 
			  (1.0+$z);
    
} # sub: pmot_dist()
*proper_motion_distance = \&pmot_dist;

sub adiam_dist ($$) {
    my $self = shift;
    my $z    = shift;

    return _lum_dist( $z, $self->{COSMOLOGY}, $self->{OMEGA_MATTER}, $self->{OMEGA_LAMBDA}, 
		      $self->{KAPPA}, $self->{ABSTOL}, $self->{DCONV} ) / 
			  ( (1.0+$z) * (1.0+$z) );
    
} # sub: adiam_dist()
*angular_diameter_distance = \&adiam_dist;

sub comov_dist ($$) {
    my $self = shift;
    my $z    = shift;
    
    return _comov_dist( $z, $self->{OMEGA_MATTER}, $self->{OMEGA_LAMBDA}, 
			$self->{ABSTOL}, $self->{DCONV} );
    
} # sub: comov_dist()
*comoving_distance = \&comov_dist;

####################################################################
#
# volume measures: PM code
#
####################################################################

sub comov_vol ($$) {
    my $self = shift;
    my $z    = shift;

    # we want dm to be in units of the hubble distance, which
    # means that we need DCONV == 1 
    my $_dconv = $self->{DCONV};
    $self->{DCONV} = 1.0;
    my $dm = $self->pmot_dist( $z );
    $self->{DCONV} = $_dconv;

    return _comov_vol( $dm, $self->{COSMOLOGY}, 
		       $self->{OMEGA_MATTER}, $self->{OMEGA_LAMBDA}, $self->{OMEGA_KAPPA}, 
		       $self->{KAPPA}, $self->{VCONV} );
    
} # sub: comov_vol()
*comoving_volume = \&comov_vol;

sub dcomov_vol ($$) {
    my $self = shift;
    my $z    = shift;

    # we want dm to be in units of the hubble distance, which
    # means that we need _DCONV == 1 
    my $_dconv = $self->{DCONV};
    $self->{DCONV} = 1.0;
    my $dm = $self->pmot_dist( $z );
    $self->{DCONV} = $_dconv;

    # calculate the differential proper motion distance
    my $ddmdz = _dpmot( $z, $dm, $self->{COSMOLOGY},
			$self->{OMEGA_MATTER}, $self->{OMEGA_LAMBDA}, $self->{OMEGA_KAPPA} );

    return _dcomov_vol( $dm, $ddmdz, $self->{OMEGA_KAPPA}, $self->{VCONV} );
    
} # sub: dcomov_vol()
*differential_comoving_volume = \&dcomov_vol;

EOD

####################################################################

## XS stuff
#
pp_addhdr('
#include <math.h>   /* for sqrt(), sin(), sinh(), asinh(), fabs() */

/* declare external C code */
#include "romberg.h"
#include "utils.h"

/* used in determining what sort of a cosmology we have */

#define UNKNOWN        ' . UNKNOWN . '
		      
#define EMPTY          ' . EMPTY . '
#define MATTER_FLAT    ' . MATTER_FLAT . '
#define MATTER_OPEN    ' . MATTER_OPEN . '
#define MATTER_CLOSED  ' . MATTER_CLOSED . '
		      
#define LAMBDA_FLAT   ' . LAMBDA_FLAT . '
#define LAMBDA_OPEN   ' . LAMBDA_OPEN . '
#define LAMBDA_CLOSED ' . LAMBDA_CLOSED . '

');

####################################################################
#
# distance measures: PP code
#
####################################################################

# not convinced what the correct thing to do with OtherPars and float/double
# parameters
#
#my $opar = "$TFD(float,double)";  # NO - unsurprisingly, OtherPars is not checked for macros
#my $opar = "double";
my $opar = "float";

pp_def( '_lum_dist',
        Doc => undef,
        Pars => 'z(); [o] d();',
        OtherPars => "int p_method; $opar p_om; $opar p_ol; $opar p_kappa; $opar p_abstol; $opar p_convert;",
        GenericTypes => ['D', 'F'],                
        Code => '$GENERIC() zz, zzp1;
                 $GENERIC() int_fn, int_fn_err;
 
                 $GENERIC() om      = $COMP( p_om ); 
                 $GENERIC() ol      = $COMP( p_ol ); 
                 $GENERIC() kappa   = $COMP( p_kappa );
                 $GENERIC() abstol  = $COMP( p_abstol );
                 $GENERIC() convert = $COMP( p_convert ); 

                 int method  = $COMP( p_method ); 

                 switch( method ) {
                   case EMPTY:
                     threadloop %{
                        zz   = $z();
                        $d() = 0.5 * convert * zz * ( 2.0 +  zz );
                     %}
                     break;

                   case MATTER_FLAT:
                     threadloop %{
                        zzp1 = 1.0 + $z();
                        $d() = 2.0 * convert * ( zzp1 - sqrt( zzp1 ) );
                     %}
                     break;

                   case MATTER_OPEN:
                   case MATTER_CLOSED:
                     threadloop %{
                        zz   = $z();
                        $d() = 2.0 * convert * 
                              ( om * zz + (om - 2.0) * ( sqrt( 1.0 + om * zz ) - 1.0 ) ) / 
                              ( om * om );
                     %}
                     break;

                   case LAMBDA_CLOSED:
                     threadloop %{
                        zz   = $z();
                        zzp1 = 1.0 + zz;
                        if ( ! $TFD(romberg_f,romberg_d) (
                                 $TFD(comov_dist_fn_f,comov_dist_fn_d),
                                 om, ol, 0.0, zz, abstol, &int_fn, &int_fn_err ) ) {
			    croak( "Unable to integrate comoving distance.\n" );
			}
                        $d() = convert * zzp1 * sin( kappa * int_fn ) / kappa;
                     %}
                     break;
                     
                   case LAMBDA_OPEN:
                     threadloop %{
                        zz   = $z();
                        zzp1 = 1.0 + zz;
                        if ( ! $TFD(romberg_f,romberg_d) (
                                 $TFD(comov_dist_fn_f,comov_dist_fn_d),
                                 om, ol, 0.0, zz, abstol, &int_fn, &int_fn_err ) ) {
			    croak( "Unable to integrate comoving distance.\n" );
			}
                        $d() = convert * zzp1 * sinh( kappa * int_fn ) / kappa;
                     %}
                     break;
                     
                   case LAMBDA_FLAT:
                     threadloop %{
                        zz   = $z();
                        zzp1 = 1.0 + zz;
                        if ( ! $TFD(romberg_f,romberg_d) (
                                 $TFD(comov_dist_fn_f,comov_dist_fn_d),
                                 om, ol, 0.0, zz, abstol, &int_fn, &int_fn_err ) ) {
			    croak( "Unable to integrate comoving distance.\n" );
			}
                        $d() = convert * zzp1 * int_fn;
                     %}
                     break;
                     
                   default:
                     croak( "Currently can not handle method == %2d\n", method ); /* should not occur */

                 } /* end of switch */'
        );


# could be clever here -- ie make use of _COSMOLOGY value -- but I haven't the energy
pp_def( '_comov_dist',
        Doc => undef,
        Pars => 'z(); [o] d();',
        OtherPars => "$opar p_om; $opar p_ol; $opar p_abstol; $opar p_convert;",
        GenericTypes => ['D', 'F'],                
        Code => '$GENERIC() int_fn, int_fn_err;
 
                 $GENERIC() om      = $COMP( p_om ); 
                 $GENERIC() ol      = $COMP( p_ol ); 
                 $GENERIC() abstol  = $COMP( p_abstol );
                 $GENERIC() convert = $COMP( p_convert ); 

                 threadloop %{
                    if ( ! $TFD(romberg_f,romberg_d) (
                             $TFD(comov_dist_fn_f,comov_dist_fn_d),
                             om, ol, 0.0, $z(), abstol, &int_fn, &int_fn_err ) ) {
		        croak( "Unable to integrate comoving distance.\n" );
		    }
                    $d() = convert * int_fn;
                 %}'
        );

# the differential proper motion distance stuff is currently only used
# by differential_comoving_volume()
#
pp_def( '_dpmot',
        Doc => undef,
        Pars => 'z(); dm(); [o] answer();',
        OtherPars => "$opar p_cosmo; $opar p_om; $opar p_ol; $opar p_ok;",
        GenericTypes => ['D', 'F'],                
        Code => '$GENERIC() cosmo   = $COMP( p_cosmo );
                 $GENERIC() om      = $COMP( p_om ); 
                 $GENERIC() ol      = $COMP( p_ol ); 
                 $GENERIC() absok   = fabs( $COMP( p_ok ) ); 

                 $GENERIC() ddm;

                 threadloop %{
                    $answer() = $TFD(comov_dist_fn_f,comov_dist_fn_d) ( $z(), om, ol );
                 %}

                 if ( cosmo != MATTER_FLAT && cosmo != LAMBDA_FLAT  ) {
                    threadloop %{
                       ddm        = $dm();
                       $answer() *= sqrt( 1.0 + absok * ddm * ddm );
                    %}
                 }'
        );

####################################################################
#
# volume measures: PP code
#
####################################################################

pp_def( '_comov_vol',
     Doc => undef,
     Pars => 'dm(); [o] v();',
     OtherPars => "int p_method; $opar p_om; $opar p_ol; $opar p_ok; $opar p_kappa; $opar p_convert;",
     GenericTypes => ['D', 'F'],                
     Code => '$GENERIC() ddm;

              $GENERIC() om      = $COMP( p_om ); 
              $GENERIC() ol      = $COMP( p_ol ); 
              $GENERIC() ok      = $COMP( p_ok ); 
              $GENERIC() kappa   = $COMP( p_kappa );
              $GENERIC() convert = $COMP( p_convert ); 

              int    method  = $COMP( p_method ); 

              /* NOTE: here dm is in units of the hubble distance */

              switch( method ) {
                case MATTER_FLAT:
                case LAMBDA_FLAT:
                  convert /= 3.0;
                  threadloop %{
                     ddm  = $dm();
                     $v() = convert * ddm * ddm * ddm;
                  %}
                  break;

                case EMPTY:
                case MATTER_OPEN:
                case LAMBDA_OPEN:
                  convert /= ( 2.0 * ok );
                  threadloop %{
                     ddm  = $dm();
                     $v() = convert * 
                            ( ddm * sqrt(1.0 + ok*ddm*ddm) - asinh( ddm * kappa ) / kappa );
                  %}
                  break;

                case MATTER_CLOSED:
                case LAMBDA_CLOSED:
                  convert /= ( 2.0 * ok );
                  threadloop %{
                     ddm  = $dm();
                     $v() = convert *
                            ( ddm * sqrt(1.0 + ok*ddm*ddm) - asin( ddm * kappa ) / kappa );
                  %}
                  break;

                default:
                  croak( "Currently can not handle method == %2d\n", method ); /* should not occur */

              } /* end of switch */'
     );

pp_def( '_dcomov_vol',
     Doc => undef,
     Pars => 'dm(); ddmdz(); [o] dv();',
     OtherPars => "$opar p_ok; $opar p_convert;",
     GenericTypes => ['D', 'F'],                
     Code => '$GENERIC() ddm;

              $GENERIC() ok      = $COMP( p_ok );
              $GENERIC() convert = $COMP( p_convert ); 

              threadloop %{
                 ddm   = $dm();
                 $dv() = convert * ddm * ddm * $ddmdz() / sqrt( 1.0 + ok*ddm*ddm );
              %}'
     );

####################################################################
#
# time measures: PP code
#
####################################################################

pp_def( '_lookback_time',
        Doc => undef,
        Pars => 'zl(); zh(); [o] t();',
        OtherPars => "$opar p_om; $opar p_ol; $opar p_abstol; $opar p_convert;",
        GenericTypes => ['D', 'F'],
        Code => '
                 $GENERIC() om      = $COMP( p_om ); 
                 $GENERIC() ol      = $COMP( p_ol ); 
                 $GENERIC() abstol  = $COMP( p_abstol );
                 $GENERIC() convert = $COMP( p_convert );

                 $GENERIC() int_fn, int_fn_err;

                 threadloop %{
                    if ( ! $TFD(romberg_f,romberg_d) (
                                $TFD(lookback_time_fn_f,lookback_time_fn_d),
                                om, ol, $zl(), $zh(), abstol, &int_fn, &int_fn_err ) ) {
                       croak( "ERROR: Unable to integrate lookback time.\n" );
                    }
                    $t() = convert * int_fn;
                 %}'
        );

# PM stuff
pp_addpm(<<'EOD');

__END__

=head1 NAME

PDL::Astro::Cosmology - cosmology routines 

=head1 DESCRIPTION

An object-orientated interface to simple cosmology routines,
such as:
distance, volume, and look-back time calculations.

The default cosmology is Omega_matter = 1.0, Omega_lambda = 0.0 and 
H0 = 50 km/s/Mpc.

Partly derived from: 

  Distance measures in cosmology, Hogg, D.W., astro-ph/9905116
  Perlmutter et al. 1997, ApJ, 483, 565
  Carroll, Press & Turner 1992, ARAA, 30, 499
  Weinberg, S., sections 14.6.8, 15.3.25
  Sandage, A. 1961, ApJ, 133, 355-392

=head1 WARNING

Whilst I believe the results are accurate, I do not guarantee this.
Caveat emptor, as the Romans used to say...

=head1 SYNOPSIS

  use PDL::Astro::Cosmology;
  use PDL::Astro::Constants  qw( PARSEC );

  # what is the luminosity distance, in metres, for
  # a couple of cosmologies
  #
  my $z   = sequence(10) / 10;
  my $eds = PDL::Astro::Cosmology->new;
  my $sn  = PDL::Astro::Cosmology->new( matter => 0.3, lambda => 0.7 );

  my $de  = 1.0e6 * PARSEC * $eds->lum_dist($z);
  my $ds  = 1.0e6 * PARSEC * $sn->lum_dist($z);

=head1 UNITS

If H0 is set to 0, then the units used are the Hubble
distance, volume per steradian, or time. If greater than zero, 
distances are measured in Mpc, volumes in Mpc^3/steradian, and time in years.

=head1 NOTES

=over 4

=item *

The comoving volume routine gives a slightly smaller answer than 
Figure 6 of Carroll, Press & Turner for z ~ 100. It could be due to
differences in the numerical methods, but I've not yet investigated
it thoroughly.

=item *

The function descriptions do not explicitly say what variables are
perl scalars and which can be piddles, but hopefully it's obvious:
redshifts can be given as a piddle, everything else is a perl scalar.

=item *

Requires PDL::Astro::Constants.

=item *

A year is defined to be PDL::Astro::Constants::YEAR_TROPICAL seconds.
Let me know if this is wrong.

=item *

There is no method to calculate the age of the universe at a given 
redshift.

=back

=head1 SUMMARY

=head2 Utility routines

=over 4

=item * new

=item * setvars

=item * matter/omega_matter, lambda/omega_lambda, h0/hO

=back

=head2 Distance measures

=over 4

=item * lum_dist/luminosity_distance 

=item * adiam_dist/angular_diameter_distance 

=item * pmot_dist/proper_motion_distance 

=item * comov_dist/comoving_distance 

=back

=head2 Volume measures

=over 4

=item * comov_vol/comoving_volume

=item * dcomov_vol/differential_comoving_volume

=back

=head2 Time measures

=over 4

=item * lookback_time

=back

=head1 ROUTINES

=head2 new

=for usage

  my $cosmo = PDL::Astro::Cosmology->new( 
                matter => 0.3, lambda => 0.7 );
  my $cosmo = PDL::Astro::Cosmology->new(  
                { matter => 0.3, lambda => 0.7 } );

=for ref

Create the object with the required cosmological parameters. 
Case does not matter and you can use the minimum number of 
letters which remain unique.

Options are:

  OMEGA_MATTER or MATTER  - default 1.0
  OMEGA_LAMBDA or LAMBDA  - default 0.0
  H0           or HO      - default 50.0
  ABSTOL                  - default 1.0e-5

If H0 is set to 0, then answers are returned in units of the Hubble
distance, volume, or time, otherwise in Mpc, Mpc^3/steradian, or
years.

C<ABSTOL> (absolute tolerance) is used as a convergence criteria when 
integrating functions as well as whether values are close enough to 0.
You should not have to worry about it.

=head2 string

=for usage

  print $cosmo;

=for ref

Returns a string representation of the object. The operator "" is
overloaded by this function, so that C<print $cosmo> gives a
readable answer.

=head2 setvars

=for usage

  $cosmo->setvars( matter => 0.3, lambda => 0.7 );

=for ref

Change the cosmological parameters of the current object. 
The options are the same as for L<new|/new>.

=head2 omega_matter or matter

=for usage

  $cosmo->omega_matter( 1.0 );
  my $omega = $cosmo->omega_matter;

=for ref

If supplied with an argument, sets the value of C<Omega_matter>.
Returns the current value of the parameter.

=head2 omega_lambda or lambda

=for usage

  $cosmo->omega_lambda( 0.8 ); 
  my $lambda = $cosmo->omega_lambda;

=for ref

If supplied with an argument, sets the value of C<Omega_lambda>.
Returns the current value of the parameter.

=head2 h0 or hO

=for usage

  $cosmo->h0( 75 ); 
  my $cosmo->$h0 = h0;

=for ref

If supplied with an argument, sets the value of C<H0>.
Returns the current value of the parameter.

=head2 lum_dist or luminosity_distance

=for usage

  my $dl = $cosmo->lum_dist( $z );

=for ref

returns the luminosity distance, for a given redshift, C<$z>,
for the current cosmology.

=head2 adiam_dist or angular_diameter_distance

=for usage

  my $da = $cosmo->adiam_dist( $z );

=for ref

returns the angular diameter distance, for a given redshift, C<$z>, 
for the current cosmology.

=head2 pmot_dist or proper_motion_distance

=for usage

  my $dm = $cosmo->pmot_dist( $z );

=for ref

returns the proper motion distance, for a given redshift, C<$z>, 
for the current cosmology.

=head2 comov_dist or comoving_distance

=for usage

  my $dc = $cosmo->comov_dist( $z );

=for ref

returns the line-of-sight comoving distance, for a given redshift, 
C<$z>, for the current cosmology.

=head2 comov_vol or comoving_volume

=for usage

  my $dv = $cosmo->comov_vol( $z );

=for ref

returns the comoving volume out to a given redshift, 
C<$z>, for the current cosmology. Does not work if
C<omega_matter> and C<omega_lambda> are both 0.0.

=head2 dcomov_vol or differential_comoving_volume

=for usage

  my $ddv = $cosmo->dcomov_vol( $z );

=for ref

returns the differential comoving volume at a given redshift, 
C<$z>, for the current cosmology. Does not work if
C<omega_matter> and C<omega_lambda> are both 0.0.

=head2 lookback_time

=for usage

  my $delta_t = $cosmo->lookback_time( [$z_min,] $z_max );

=for ref

Returns the lookback time between C<$z_min> and C<$z_max>. If
C<$z_min> is not supplied it defaults to 0.0.

=head2 abs_mag or absolute_magnitude

=for usage

  my $absolute_mag = $cosmo->abs_mag( $apparent_mag, $z );

=for ref

Returns the absolute magnitude - excluding K and evolutionary
corrections - for the given apparent magnitude.

=head2 app_mag or apparent_magnitude

=for usage

  my $apparent_mag = $cosmo->app_mag( $absolute_mag, $z );

=for ref

Returns the apparent magnitude for a given absolute magnitude.
As with abs_mag, the K- and evolutionary-corrections are left 
up to you.

=head2 luminosity

=for usage

  my $lumin = $cosmo->luminosity( $flux, $z );

=for ref

Returns the luminosity of a source of a given flux.
As with abs_mag, the K- and evolutionary-corrections are left 
up to you.

=head2 flux

=for usage

  my $flux = $cosmo->flux( $lumin, $z );

=for ref

Returns the flux of a source of a given luminosity.
As with abs_mag, the K- and evolutionary-corrections are left 
up to you.

=head1 TODO

Add ability to request a particular unit; for example
have C<$cosmo-E<gt>lum_dist()> return I<cm> rather than
I<Mpc>.

=head1 ACKNOWLEDGEMENTS

The cosmology routines make use of code based on routines from

  NUMERICAL METHODS: FORTRAN Programs, (c) John H. Mathews 1994
  NUMERICAL METHODS for Mathematics, Science and Engineering, 2nd Ed, 1992
  Prentice Hall, Englewood Cliffs, New Jersey, 07632, U.S.A.

=head1 AUTHOR

Copyright (C) Douglas Burke <dburke@cfa.harvard.edu> 
1999, 2000, 2001.

All rights reserved. There is no warranty. 
This program is free software; you can redistribute it
and/or modify it under the same terms as Perl itself.

=cut

EOD

# end

pp_export_nothing();
pp_done();
