aboutsummaryrefslogtreecommitdiffstats
path: root/astro/spectrum
diff options
context:
space:
mode:
authorAaron LI <aly@aaronly.me>2019-10-16 13:22:53 +0800
committerAaron LI <aly@aaronly.me>2019-10-16 13:22:53 +0800
commitcce8928d5f42b20348b4a3812c0d48a7541cbe2a (patch)
tree5be26ab1f81b891a1920546c41d5ed4695cac6e8 /astro/spectrum
parentf55d3b51de559ea68f5d63bbf1a3caf92e1eb931 (diff)
downloadatoolbox-cce8928d5f42b20348b4a3812c0d48a7541cbe2a.tar.bz2
astro: Add sfr_chandra.pl (version: 2015/03/04)
This script is used to do Chandra automatic spectrum extraction and fitting. It was written by Junhua GU and has been significantly updated by me.
Diffstat (limited to 'astro/spectrum')
-rwxr-xr-xastro/spectrum/sfr_chandra.pl686
1 files changed, 686 insertions, 0 deletions
diff --git a/astro/spectrum/sfr_chandra.pl b/astro/spectrum/sfr_chandra.pl
new file mode 100755
index 0000000..a6f982c
--- /dev/null
+++ b/astro/spectrum/sfr_chandra.pl
@@ -0,0 +1,686 @@
+#!/usr/bin/env perl
+#
+use strict;
+use warnings;
+use File::Basename;
+#
+# Performs automatically spectral fitting with XSPEC 12.
+# For Chandra data only.
+#
+# Reference:
+# [1] Running Multiple Copies of a Tool - CIAO
+# http://cxc.harvard.edu/ciao/ahelp/parameter.html#Running_Multiple_Copies_of_a_Tool
+#
+# Author: Junhua GU <tompkins@sjtu.edu.cn>
+# 2007
+#
+# Updated by: Weitian LI <liweitianux@gmail.com>
+my $version = "2015/03/04";
+#
+# ChangeLogs:
+# 2015/03/04: Weitian LI
+# Totally rewrite of this script:
+# * add use strict, warnings, many fixes to the syntax
+# * add sub 'usage'
+# * rewrite sub 'update_backscale' -> 'rescale'
+# * update sub 'extract_spec' -> 'extract_spectrum'
+# * remove sub 'extract_back'
+# * replace 'grppha' with 'dmgroup'
+# * add sub 'determine_bkg' to determine the type of background
+# * fix the problem of when background set to 'none'
+# * copy CIAO tools' parameter files to local, avoiding conflicts when
+# running multiple instances of a CIAO tool
+# * allow comments in fit config file (starting with '//')
+# * update sub 'parse_result' to accept the parameter list and hash,
+# for better/simple parse process
+# * update example fit config file
+# * update generation of xspec fit script
+# * update config file syntax: merged '#PARA' and '#ERROR'
+# * use backtics to get the output of command within perl
+# * !! INCOMPATIBLE !! improve/change output format (columns adjustments)
+# 2015/02/05: Weitian LI
+# * fix the wrong use of chomp.
+# 2015/01/27: Weitian LI
+# * check 'cnt_spec' value before adjust the backscale
+# 2015/01/21:
+# * add 'query yes' to temp_fit_script
+#
+
+## usage {{{
+sub usage {
+ my $prog = basename($0);
+ my $text = qq(SFR - Spectral Fitting Robot
+Usage:
+ $prog <region_list> <evt> <bkgd> <output> <rmf> <arf> <fit_config>
+
+Version: $version
+);
+ print $text;
+}
+## usage }}}
+
+# Example fit config {{{
+#----------------------------------------------------------------
+# abund grsa
+# weight gehrels
+#
+# model wabs*apec & <nh> & 1.0 & 0.5 & <redshift> & 1.0 &
+#
+# freeze 1
+# thaw 3
+#
+# ignore **:0.0-0.7,7.0-**
+#
+# fit
+# fit
+# fit
+#
+# // Do not adjust BACKSCAL of background spectrum
+# #NORESCALE
+#
+# #PARA kT 2
+#
+# // Also calculate the errors of kT with XSPEC error command
+# #PARA kT 2 ERROR 1.0
+# #PARA Abund 3
+# #STAT
+# #FLUX 0.7 7.0
+#
+# // Specify the parameters for dmgroup
+# #DMGROUP NUM_CTS 20
+# #DMGROUP BIN 1:128:2,129:256:4,257:512:8,513:1024:16
+#----------------------------------------------------------------
+# example config }}}
+
+## variables {{{
+# Background types
+use constant {
+ NO_BKG => 0, # do not use a background
+ BLANKSKY => 1, # blanksky fits file
+ STOW_BKG => 2, # stowed background
+ LBKG_REG => 3, # region file of a local background
+ BKG_SPEC => 4, # background spectrum file
+};
+
+# Threshold value for the 'reduced chi-squared'
+my $rechisq_threshold = 1.4;
+# Number of bad fitting (If fitted rechisq > $rechisq_threshold)
+my $bad_fit_num = 0;
+
+# Whether to rescale the background (default yes)
+my $rescale = 1;
+
+# Energy range selected to adjust the BACKSCAL of the background spectra.
+# PI = [ energy(eV) / 14.6 eV + 1 ]
+# Chandra particle background energy range:
+# 9.5-12.0 keV (channel: 651-822)
+my $pb_chn_lo = 651;
+my $pb_chn_hi = 822;
+
+# Source spectrum particle background counts ($cnt_spec,
+# within channel back_lo -> back_hi) may be too small, even ZERO,
+# thus cause error of zero division.
+# If '$cnt_spec' too small (say 20), then skip adjusting the BACKSCAL.
+my $pb_cnt_threshold = 20;
+
+# 'dmgroup' parameters
+my $grouptype = "NUM_CTS";
+#my $grouptype="BIN"
+my $grouptypeval = "20";
+my $binspec = "1:128:2,129:256:4,257:512:8,513:1024:16";
+## variables }}}
+
+# NOTE: @ARGV does NOT include $0, the name of the program.
+#my $argc = @ARGV;
+my $argc = $#ARGV + 1;
+#print "argc: $argc\n";
+if ($argc != 7) {
+ usage();
+ exit 1;
+}
+
+## Setup local PFILES environment {{{
+# Allow running multiple copies of a CIAO tool.
+# Reference: http://cxc.harvard.edu/ciao/ahelp/parameter.html#Running_Multiple_Copies_of_a_Tool
+#
+# Make a local copy of necessary parameter files.
+print "Setup local minimal environment for CIAO to avoid pfile conflicts.\n";
+print "Copy necessary parameter files ...\n";
+my @ciao_tools = ("dmstat", "dmkeypar", "dmhedit", "dmextract", "dmgroup");
+foreach (@ciao_tools) {
+ my $tool = $_;
+ my $tool_par = `paccess $tool`;
+ chomp($tool_par);
+ #print "${tool}.par: $tool_par.\n";
+ system("punlearn $tool && cp -Lv $tool_par .");
+}
+
+# Set environment variable PFILES
+$ENV{PFILES} = "./:$ENV{PFILES}";
+#system("echo PFILES: \$PFILES");
+## PFILES }}}
+
+## process arguments {{{
+# File contains the list of fitting regions
+my $region_list_fn = $ARGV[0];
+# Event file (clean)
+my $evt2_fn = $ARGV[1];
+# Background file (blanksky.fits / bkg.pi)
+my $bkg_fn = $ARGV[2];
+# Output file to save fitting results
+my $output_fn = $ARGV[3];
+my $rmf_fn = $ARGV[4];
+my $arf_fn = $ARGV[5];
+# Fit config file for this tool
+my $config_fn = $ARGV[6];
+
+print "====================================================\n";
+print "region_list: $region_list_fn\n";
+print "evt2: $evt2_fn\n";
+print "bkg: $bkg_fn \n";
+print "output: $output_fn\n";
+print "rmf: $rmf_fn\n";
+print "arf: $arf_fn\n";
+print "config: $config_fn\n";
+
+# Parse evt, back, rmf, arf ... {{{
+my @evt_list = split(/[,\ ]+/, $evt2_fn);
+my $evt_num = @evt_list;
+my @bkg_list = split(/[,\ ]+/, $bkg_fn);
+my $bkg_num = @bkg_list;
+my @rmf_list = split(/[,\ ]+/, $rmf_fn);
+my $rmf_num = @rmf_list;
+my @arf_list = split(/[,\ ]+/, $arf_fn);
+my $arf_num = @arf_list;
+
+print "====================================================\n";
+print "evt_num: $evt_num, bkg_num: $bkg_num, rmf_num: $rmf_num, arf_num: $arf_num\n";
+if (! (($evt_num == $bkg_num) && ($bkg_num == $rmf_num) && ($rmf_num == $arf_num))) {
+ print "ERROR: number of evt, bkg, rmf or arf NOT match!\n";
+ exit 12;
+}
+
+foreach my $i (0 .. $#evt_list) {
+ printf "Data group %d: ", ($i+1);
+ printf "%s, %s, %s, %s\n", $evt_list[$i], $bkg_list[$i], $rmf_list[$i], $arf_list[$i];
+}
+print "====================================================\n";
+# parse evt, bkg ... }}}
+
+# Backup the output file if it exists
+if ( -e $output_fn ) {
+ print "WARNING: Output file '$output_fn' already exists!\n";
+ system("mv -fv $output_fn ${output_fn}_bak");
+}
+
+# Determine the type of each background file
+my @bkg_type = ();
+foreach my $bkgd (@bkg_list) {
+ my $bkgd_type = NO_BKG;
+ if ($bkgd =~ /^(no|null|none)$/i) {
+ print "WARNING: does not use background!\n";
+ $bkgd_type = NO_BKG;
+ } elsif (! -e $bkgd) {
+ print "ERROR: bkg file '$bkgd' does not exists!\n";
+ exit 11;
+ } else {
+ $bkgd_type = determine_bkg($bkgd);
+ }
+ push(@bkg_type, $bkgd_type);
+}
+## arguments }}}
+
+## Fitting related variables {{{
+my $fit_reg_fn = "_fit.reg";
+my $fit_script_fn = "_fit.tcl";
+my $fit_result_fn = "_fit.txt";
+if (-e $fit_result_fn) {
+ unlink($fit_result_fn);
+}
+
+my @fit_spec_list = ();
+my @fit_grp_spec_list = ();
+my @fit_bkg_spec_list = ();
+foreach my $i (0 .. $#evt_list) {
+ my $ii = $i + 1;
+ push (@fit_spec_list, "_fit_${ii}.pi");
+ push (@fit_grp_spec_list, "_fit_${ii}_grp.pi");
+ push (@fit_bkg_spec_list, "_fit_${ii}_bkg.pi");
+}
+## varaibles }}}
+
+## Subroutines {{{
+# Determine the type of the given background file {{{
+# Usage: determine_bkg($bkg_filename)
+sub determine_bkg {
+ my $argc = @_;
+ if ($argc != 1) {
+ print "ERROR: sub determine_bkg() requires 1 argument!\n";
+ exit 21;
+ }
+ my $bkgd = $_[0];
+ my $filetype = `file -bL $bkgd`;
+ if ($filetype =~ /^ASCII text/) {
+ print "determine_bkg: '$bkgd' is ASCII text\n";
+ return LBKG_REG;
+ } elsif ($filetype =~ /^FITS/) {
+ print "determine_bkg: '$bkgd' is FITS\n";
+ my $hduclas1 = `punlearn dmkeypar && dmkeypar $bkgd HDUCLAS1 echo=yes`;
+ chomp($hduclas1);
+ if ($hduclas1 =~ /^EVENTS$/) {
+ print "determine_bkg: '$bkgd' is FITS of EVENTS\n";
+ my $bkg_obj = `punlearn dmkeypar && dmkeypar $bkgd OBJECT echo=yes`;
+ chomp($bkg_obj);
+ if ($bkg_obj =~ /^BACKGROUND DATASET$/) {
+ print "determine_bkg: '$bkgd' is background dataset\n";
+ return BLANKSKY;
+ } elsif ($bkg_obj =~ /^ACIS STOWED$/) {
+ print "determine_bkg: '$bkgd' is stowed background\n";
+ return STOW_BKG;
+ } else {
+ print "ERROR: determine_bkg: unknown EVENTS type\n";
+ exit 22;
+ }
+ } elsif ($hduclas1 =~ /^SPECTRUM$/) {
+ print "determine_bkg: '$bkgd' is FITS of SPECTRUM\n";
+ return BKG_SPEC;
+ } else {
+ print "ERROR: determine_bkg: unknown FITS type\n";
+ exit 23;
+ }
+ } else {
+ print "ERROR: determine_bkg: unknown file type: $filetype\n";
+ exit 24;
+ }
+}
+# determine_bkg }}}
+
+# Adjust BACKSCALE of background spectrum to match PB fluxes {{{
+# Usage: bkg_rescale($src_spec, $bkg_spec)
+sub bkg_rescale {
+ my $argc = @_;
+ if ($argc != 2) {
+ print "ERROR: sub bkg_rescale() requires 2 arguments!\n";
+ exit 22;
+ }
+ my $src_spec = $_[0];
+ my $bkg_spec = $_[1];
+
+ # PB counts of source & background spectrum
+ my $src_pb_cnt = `punlearn dmstat && dmstat infile="$src_spec\[channel=$pb_chn_lo:$pb_chn_hi\]\[cols COUNTS\]" >/dev/null 2>&1 && pget dmstat out_sum`;
+ chomp($src_pb_cnt);
+ # PB counts of background spectrum
+ my $bkg_pb_cnt = `punlearn dmstat && dmstat infile="$bkg_spec\[channel=$pb_chn_lo:$pb_chn_hi\]\[cols COUNTS\]" >/dev/null 2>&1 && pget dmstat out_sum`;
+ chomp($bkg_pb_cnt);
+ #print "src_pb_cnt: $src_pb_cnt\n";
+ #print "bkg_pb_cnt: $bkg_pb_cnt\n";
+ #exit 233;
+
+ # BACKSCAL & EXPOSURE of source spectrum
+ my $src_backscal = `punlearn dmkeypar && dmkeypar $src_spec BACKSCAL echo=yes`;
+ chomp($src_backscal);
+ my $src_exposure = `punlearn dmkeypar && dmkeypar $src_spec EXPOSURE echo=yes`;
+ chomp($src_exposure);
+ # BACKSCAL & EXPOSURE of background spectrum
+ my $bkg_backscal = `punlearn dmkeypar && dmkeypar $bkg_spec BACKSCAL echo=yes`;
+ chomp($bkg_backscal);
+ my $bkg_exposure = `punlearn dmkeypar && dmkeypar $bkg_spec EXPOSURE echo=yes`;
+ chomp($bkg_exposure);
+ #print "src_backscal: $src_backscal\n";
+ #print "src_exposure: $src_exposure\n";
+ #print "bkg_backscal: $bkg_backscal\n";
+ #print "bkg_exposure: $bkg_exposure\n";
+
+ if ($src_pb_cnt <= $pb_cnt_threshold) {
+ # Source spectrum particle background counts too small.
+ print "WARNING: too small counts of source spectrum within 9.5-12 keV: $src_pb_cnt!\n";
+ print "Skipped BACKSCAL adjustment of background spectrum!\n";
+ } else {
+ # Adjust BACKSCALE of background spectrum to match PB flux:
+ # src_pb_cnt / src_exposure / src_backscal = bkg_pb_cnt / bkg_exposure / bkg_backscal
+ my $bkg_backscal_new = $src_backscal * $src_exposure * $bkg_pb_cnt
+ / ($src_pb_cnt * $bkg_exposure);
+ my $cmd_str = "punlearn dmhedit && "
+ . "dmhedit infile=\"$bkg_spec\" filelist=none operation=add "
+ . "key=BACKSCAL value=$bkg_backscal_new "
+ . "comment='old_value: $bkg_backscal'";
+ system($cmd_str);
+ print "Updated BACKSCAL: $bkg_backscal -> $bkg_backscal_new\n";
+ }
+}
+# rescale }}}
+
+# Extract spectrum from fits {{{
+# Usage: extract_spectrum($input_fits, $output_spec, $region_file)
+sub extract_spectrum {
+ my $argc = @_;
+ if ($argc != 3) {
+ print "ERROR: sub extract_spec() requires 3 arguments!\n";
+ exit 31;
+ }
+ my $infile = $_[0];
+ my $outfile = $_[1];
+ my $regfile = $_[2];
+ my $cmd_str = "punlearn dmextract && "
+ . "dmextract infile=\"$infile\[sky=region\($regfile\)\]\[bin pi\]\" "
+ . "outfile=\"$outfile\" wmap=\"\[bin det=8\]\" clobber=yes";
+ system($cmd_str);
+}
+# extract spectrum }}}
+
+# group spectrum with 'dmgroup' {{{
+# Usage: group_spectrum($in_spec, $grp_spec, $grouptype, $grouptypeval, $binspec)
+sub group_spectrum {
+ my $argc = @_;
+ if ($argc != 5) {
+ print "ERROR: sub group_spec() requires 5 arguments!\n";
+ exit 41;
+ }
+ my $infile = $_[0];
+ my $outfile = $_[1];
+ my $grp_type = $_[2];
+ my $grp_type_val = $_[3];
+ my $bin_spec = $_[4];
+
+ my $cmd_str = "punlearn dmgroup && "
+ . "dmgroup infile=\"$infile\" outfile=\"$outfile\" "
+ . "grouptype=\"$grp_type\" grouptypeval=$grp_type_val "
+ . "binspec=\"$bin_spec\" xcolumn=CHANNEL ycolumn=COUNTS clobber=yes";
+ #print $cmd_str;
+ system($cmd_str);
+}
+# group spectrum }}}
+
+# Parse the fitted result line {{{
+# Usage: parse_result($result_line, @para_name_list, %para_num)
+sub parse_result ($\@\%) {
+ my $argc = @_;
+ if ($argc != 3) {
+ print "ERROR: sub parse_result() requires 3 argument!\n";
+ exit 51;
+ }
+ my $line=$_[0];
+ my @para_name_list = @{$_[1]};
+ my %para_num= %{$_[2]};
+
+ my @parsed_results = ();
+
+ foreach my $para_name (@para_name_list) {
+ # Number of values for this parameter
+ my $num = $para_num{$para_name};
+ if ($line =~ /^$para_name:/) {
+ my @items = split(/\s+/, $line);
+ foreach my $i (0 .. $num) {
+ # items[0] is the '$para_name:'
+ push(@parsed_results, $items[$i]);
+ }
+ if ($para_name =~ /red.*chi.*sq/i) {
+ my $current_rechisq = $items[1];
+ if ($current_rechisq > $rechisq_threshold) {
+ print "WARNING: bad quality of spectral fittings!\n";
+ $bad_fit_num ++;
+ }
+ }
+ }
+ }
+
+ return @parsed_results;
+}
+# parse result }}}
+## subroutines }}}
+
+## Generate the fit script for XSPEC {{{
+if (! -e $config_fn) {
+ print "ERROR: config file '$config_fn' does not exist!\n";
+ exit 14;
+}
+open(my $FIT_SCRIPT, ">", $fit_script_fn)
+ or die "ERROR: Could not open file '$fit_script_fn', $!";
+
+# Part of load data to XSPEC
+foreach my $i (0 .. $#evt_list) {
+ my $ii = $i + 1;
+ print $FIT_SCRIPT "data ${ii}:${ii} $fit_grp_spec_list[$i]\n";
+ print $FIT_SCRIPT "response 1:${ii} $rmf_list[$i]\n";
+ print $FIT_SCRIPT "arf 1:${ii} $arf_list[$i]\n";
+ print $FIT_SCRIPT "backgrnd ${ii} $fit_bkg_spec_list[$i]\n";
+}
+
+# Automatically answer XSPEC query with 'yes'
+print $FIT_SCRIPT "query yes\n";
+print $FIT_SCRIPT "set ff \[ open $fit_result_fn w \]\n";
+
+open(my $CONFIG, "<", $config_fn)
+ or die "ERROR: Counld not open file '$config_fn', $!";
+my @config_lines = <$CONFIG>;
+close($CONFIG);
+
+# Record the name of specified parameters in the config file.
+my @para_name_list = ();
+# Record the number of output values of above parameters. (use hash)
+my %para_num;
+
+# Parse the fitting config file {{{
+foreach my $line (@config_lines) {
+ chomp($line);
+ if ($line =~ /^\s*$/) {
+ # Blank line
+ print "Skipped blank line.\n";
+ next;
+ } elsif ($line =~ /^\/\//) {
+ # Comment line
+ print "Skipped comment line.\n";
+ next;
+ } elsif ($line =~ /^\#PARA/) {
+ # Define fitting parameter, together with settings error calculation.
+ # Config: '#PARA para_name para_index [ ERROR conf_level ]'
+ # Output:
+ # 'para_name: value sigma'
+ # 'para_name_err: error_lower error_upper'
+ my @para = split(/\s+/, $line);
+ my $para_name = $para[1];
+ my $para_idx = $para[2];
+ push(@para_name_list, $para_name);
+ $para_num{$para_name} = 2;
+ print $FIT_SCRIPT "tclout para $para_idx\n";
+ print $FIT_SCRIPT "scan \$xspec_tclout \"%f\" value\n";
+ print $FIT_SCRIPT "tclout sigma $para_idx\n";
+ print $FIT_SCRIPT "scan \$xspec_tclout \"%f\" sigma\n";
+ print $FIT_SCRIPT "puts \$ff \"$para_name: \$value \$sigma\"\n";
+ # Check whether to calculate the errors
+ if ($line =~ /ERR(OR|)\s+\d*\.\d*\s*$/) {
+ # Calculate errors with XSPEC 'error' command
+ my $conf_level = $para[4];
+ my $para_err_name = "${para_name}_err";
+ push(@para_name_list, $para_err_name);
+ $para_num{$para_err_name} = 2;
+ print $FIT_SCRIPT "fit\n";
+ print $FIT_SCRIPT "error $conf_level $para_idx\n";
+ print $FIT_SCRIPT "tclout error $para_idx\n";
+ print $FIT_SCRIPT "scan \$xspec_tclout \"%f %f\" err_lower err_upper\n";
+ print $FIT_SCRIPT "puts \$ff \"${para_name}_err: \$err_lower \$err_upper\"\n";
+ }
+ } elsif ($line =~ /^\#STAT/) {
+ # Whether output the statistic information
+ # Config: '#STAT'
+ # Output:
+ # 'stat_value statistic'
+ # 'dof_value dof'
+ # 'rechisq_value Reduced_chisq'
+ my $stat_name = "stat";
+ push(@para_name_list, $stat_name);
+ $para_num{$stat_name} = 1;
+ my $dof_name = "dof";
+ push(@para_name_list, $dof_name);
+ $para_num{$dof_name} = 1;
+ # Reduced chi-squared
+ my $rechisq_name = "reduced_chisq";
+ push(@para_name_list, $rechisq_name);
+ $para_num{$rechisq_name} = 1;
+ print $FIT_SCRIPT "tclout $stat_name\n";
+ print $FIT_SCRIPT "scan \$xspec_tclout \"%f\" stat_val\n";
+ print $FIT_SCRIPT "puts \$ff \"$stat_name: \$stat_val\"\n";
+ print $FIT_SCRIPT "tclout $dof_name\n";
+ print $FIT_SCRIPT "scan \$xspec_tclout \"%d\" dof_val\n";
+ print $FIT_SCRIPT "puts \$ff \"$dof_name: \$dof_val\"\n";
+ print $FIT_SCRIPT "set rechisq_cmd \{ format \"%.5f\" \[ expr \{ \$stat_val / \$dof_val \} \] \}\n";
+ print $FIT_SCRIPT "set rechisq \[ eval \$rechisq_cmd \]\n";
+ print $FIT_SCRIPT "puts \$ff \"$rechisq_name: \$rechisq\"\n";
+ } elsif ($line =~ /^\#FLUX/) {
+ # Whether output flux information
+ # Config: '#FLUX start_keV end_keV'
+ my @para = split(/\s+/, $line);
+ my $energy_low = $para[1];
+ my $energy_high = $para[2];
+ my $para_name = "flux";
+ push(@para_name_list, $para_name);
+ $para_num{$para_name} = 1;
+ print $FIT_SCRIPT "flux $energy_low $energy_high\n";
+ print $FIT_SCRIPT "tclout $para_name\n";
+ print $FIT_SCRIPT "scan \$xspec_tclout \"%f\" flux_val\n";
+ print $FIT_SCRIPT "puts \$ff \"$para_name: \$flux_val\"\n";
+ } elsif ($line =~ /^\#DMGROUP/) {
+ # dmgroup parameters
+ my @para = split(/\s+/, $line);
+ $grouptype = $para[1];
+ if ($grouptype eq "NUM_CTS") {
+ $grouptypeval = $para[2];
+ print "dmgroup: $grouptype, $grouptypeval\n";
+ } elsif ($grouptype eq "BIN") {
+ $binspec = $para[2];
+ print "dmgroup: $grouptype, $binspec\n";
+ } else {
+ print "ERROR: unknown grouptype '$grouptype'!\n";
+ print "Only 'NUM_CTS' and 'BIN' supported for dmgroup.\n";
+ exit 16;
+ }
+ } elsif ($line =~ /^\#NORESCALE/) {
+ $rescale = 0;
+ } elsif ($line =~ /^\#/) {
+ # Other unsupported lines starting with '#'
+ print "WARNING: unsupported config line:\n '$line'\n";
+ next;
+ } else {
+ # XSPEC commands/lines
+ print $FIT_SCRIPT "$line\n";
+ }
+}
+# Parse config file }}}
+
+print $FIT_SCRIPT "close \$ff\n";
+print $FIT_SCRIPT "tclexit\n";
+close($FIT_SCRIPT);
+#exit 233;
+## Generate XSPEC script }}}
+
+## Perform spectral fittings {{{
+open(my $REGION_LIST, "<", $region_list_fn)
+ or die "ERROR: Could not open file '$region_list_fn', $!";
+my @region_lines = <$REGION_LIST>;
+close($REGION_LIST);
+
+open(my $OUTFILE, ">", $output_fn)
+ or die "ERROR: Could not open file '$output_fn', $!";
+
+my $reg_total = @region_lines;
+my $n = 0;
+
+# Loop over the region list to perform spectral fitting for each region
+foreach my $line (@region_lines) {
+ chomp($line);
+ $n++;
+ print "### $n of $reg_total ###\n";
+
+ # Skip the null/none region
+ if ($line =~ /^\#(null|none)/i) {
+ print $OUTFILE "$n ";
+ foreach my $para_name (@para_name_list) {
+ # Number of values for this parameter
+ my $num = $para_num{$para_name};
+ print $OUTFILE "${para_name}: ";
+ foreach my $i (1 .. $num) {
+ print $OUTFILE "-1 ";
+ }
+ }
+ print $OUTFILE "\n";
+ print "Skipped the null/none region.\n";
+ next;
+ }
+
+ # Save the current region to a temporary region file
+ unlink($fit_reg_fn);
+ open(my $FIT_REG, ">", $fit_reg_fn)
+ or die "ERROR: Could not open file '$fit_reg_fn', $!";
+ my @cur_regs = split(/\s+/, $line);
+ foreach my $reg (@cur_regs) {
+ print $FIT_REG "$reg\n"
+ }
+ close($FIT_REG);
+
+ # Prepare source and background spectra {{{
+ print "Prepare source and background spectra \.\.\.\n";
+ foreach my $i (0 .. $#evt_list) {
+ # Prepare source spectrum
+ extract_spectrum($evt_list[$i], $fit_spec_list[$i], $fit_reg_fn);
+ group_spectrum($fit_spec_list[$i], $fit_grp_spec_list[$i],
+ $grouptype, $grouptypeval, $binspec);
+ # Prepare background spectrum
+ if ($bkg_type[$i] == NO_BKG) {
+ # Dose not use a background
+ $fit_bkg_spec_list[$i] = "none";
+ } elsif (($bkg_type[$i] == BLANKSKY) || ($bkg_type[$i] == STOW_BKG)) {
+ extract_spectrum($bkg_list[$i], $fit_bkg_spec_list[$i], $fit_reg_fn);
+ if ($rescale) {
+ bkg_rescale($fit_spec_list[$i], $fit_bkg_spec_list[$i]);
+ }
+ } elsif ($bkg_type[$i] == LBKG_REG) {
+ extract_spectrum($evt_list[$i], $fit_bkg_spec_list[$i], $bkg_list[$i]);
+ if ($rescale) {
+ bkg_rescale($fit_spec_list[$i], $fit_bkg_spec_list[$i]);
+ }
+ } elsif ($bkg_type[$i] == BKG_SPEC) {
+ # Directly use given background spectrum
+ system("cp -v $bkg_list[$i] $fit_bkg_spec_list[$i]");
+ if ($rescale) {
+ bkg_rescale($fit_spec_list[$i], $fit_bkg_spec_list[$i]);
+ }
+ } else {
+ print "ERROR: unsupported background type!\n";
+ exit 15;
+ }
+ }
+ # prepare spectra }}}
+
+ # Perform the spectral fitting with XSPEC
+ print "Fitting \.\.\.\n";
+ unlink($fit_result_fn);
+ system("xspec - $fit_script_fn >/dev/null") == 0
+ or die "system: xspec failed: $?";
+
+ # Parse fitted results and output
+ open(my $FIT_RESULT, "<", $fit_result_fn)
+ or die "ERROR: Could not open file '$fit_result_fn', $!";
+ my @fitted_results = <$FIT_RESULT>;
+ close($FIT_RESULT);
+ # Array to store parsed results of current fit
+ my @current_results = ($n);
+ foreach my $line (@fitted_results) {
+ chomp($line);
+ push(@current_results, parse_result($line, @para_name_list, %para_num));
+ }
+ print join(" ", @current_results) . "\n";
+ print $OUTFILE join(" ", @current_results) . "\n";
+} # end of fitting loop
+
+close($OUTFILE);
+## spectra fittings }}}
+
+if ($bad_fit_num > 0) {
+ my $bad_fit_percent = $bad_fit_num / $reg_total;
+ print "\n************************************************\n";
+ print "WARNING: Reduced chi-squared of $bad_fit_num ($bad_fit_percent) regions > $rechisq_threshold!!\n";
+}
+
+exit 0;
+
+# vim: set ts=8 sw=4 tw=0 fenc=utf-8 ft=perl: #