! This module contains several routines for different uses. ! See below for a short description of each routine. ! ! It is part of the program SP_Ace, which derives stellar parameters, ! such as gravity, temperature, and element abundances from optical ! stellar spectra, assuming Local Thermodynamic Equilibrium (LTE) ! and 1D stellar atmosphere models. ! ! Copyright (C) 2016 Corrado Boeche ! ! This program 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 . MODULE utils USE num_type USE error USE data_lib USE share, ONLY: ABD_mask,write_ABD_mask IMPLICIT NONE CONTAINS !######################## !this routine set the cosmic_mask SUBROUTINE cosmic_rej USE share, ONLY: f_sp,f_model,weights,cosmic_mask,dimsp USE stats IMPLICIT NONE REAL(DP) :: avg_r,var,sig_r REAL(DP),DIMENSION(dimsp) :: resid LOGICAL,DIMENSION(dimsp) :: mask mask=.FALSE. resid=f_sp-f_model WHERE(weights>1e-3_dp) mask=.TRUE. CALL avg_std_(resid,mask,avg_r,var,sig_r) WHERE(f_sp>(1._dp+4.0_dp*sig_r)) cosmic_mask=.TRUE. WHERE(f_sp<=0.01) cosmic_mask=.TRUE. END SUBROUTINE cosmic_rej !######################## !this routine computes the overall S/N SUBROUTINE new_sn(sn) USE share, ONLY: f_sp_norm, f_model,dimsp,weights USE read_sp_ll, ONLY: add_pix_sp USE stats IMPLICIT NONE REAL(DP), INTENT(INOUT) :: sn REAL(DP), DIMENSION(dimsp) :: resid REAL(DP) :: mean,var,sdev1,sdev2 LOGICAL,DIMENSION(dimsp) :: mask1,mask2,mask3 !initialize mask1=.TRUE. mask2=.FALSE. mask3=.FALSE. resid=f_sp_norm-f_model WHERE(weights<0.001) mask1=.FALSE. !do not consider the extreme of the spectrum where the flux=1. artificially mask1(1:add_pix_sp)=.FALSE. mask1(dimsp-add_pix_sp:dimsp)=.FALSE. CALL avg_std_(resid,mask1,mean,var,sdev1) WHERE(ABS(resid-mean)<(3.*sdev1)) mask2=.TRUE. WHERE(mask1.AND.mask2) mask3=.TRUE. IF(ALL(.NOT.mask3)) THEN sn=-1 ELSE CALL avg_std_(resid,mask3,mean,var,sdev2) !for some bad spectra resid=0.00 for many pixels, then IF(sdev2<1e-4) THEN sdev2=10.0_dp END IF sn=1/sdev2 END IF END SUBROUTINE new_sn !######################## !this routine computes the S/N in the neighbouring of each pixel SUBROUTINE find_sn_var(f_sp_norm,f_model,sn_var) USE stats USE share, ONLY: dimsp,weights IMPLICIT NONE REAL(DP) :: avg_r,var,sig_r REAL(DP),DIMENSION(dimsp), INTENT(IN) :: f_sp_norm,f_model REAL(DP),DIMENSION(dimsp) :: resid REAL(DP),DIMENSION(dimsp), INTENT(OUT) :: sn_var INTEGER(I4B) :: i,iinf,isup,int_pix LOGICAL,DIMENSION(dimsp) :: mask resid=f_sp_norm-f_model int_pix=25_I4B DO i=1,dimsp IF(weights(i)>1e-3) THEN mask=.FALSE. isup=MIN(i+int_pix,dimsp); iinf=MAX(i-int_pix,1_I4B); WHERE(weights(iinf:isup)>1e-3) mask(iinf:isup)=.TRUE. END WHERE CALL avg_std_(resid(iinf:isup),mask(iinf:isup),avg_r,var,sig_r) WHERE(ABS(resid(iinf:isup)-avg_r)>(3*sig_r)) mask(iinf:isup)=.FALSE. IF(COUNT(mask(iinf:isup))>2) THEN CALL avg_std_(resid(iinf:isup),mask(iinf:isup),avg_r,var,sig_r) !for some bad spectra resid=0.0, then IF(sig_r<1e-4) THEN sig_r=10.0_dp END IF sn_var(i)=1.0_dp/sig_r ELSE sn_var(i)=0.1_dp END IF ELSE sn_var(i)=0.1_dp END IF END DO END SUBROUTINE find_sn_var !######################## !### this routine finds the closest point of the grid to the stellar parameters given in TGM_ SUBROUTINE find_prox(TGM_,TGM_prox) IMPLICIT NONE REAL(DP), DIMENSION(:), INTENT(IN) :: TGM_ REAL(DP), DIMENSION(3), INTENT(OUT) :: TGM_prox REAL(DP), DIMENSION(1) :: prox_dummy prox_dummy=temp_grid(MINLOC(ABS(temp_grid-TGM_(1)))) TGM_prox(1)=prox_dummy(1) prox_dummy=logg_grid(MINLOC(ABS(logg_grid-TGM_(2)))) TGM_prox(2)=prox_dummy(1) prox_dummy=met_grid(MINLOC(ABS(met_grid-TGM_(3)))) TGM_prox(3)=prox_dummy(1) IF(TGM_prox(1)>5600.AND.TGM_prox(2)<1.4) THEN IF((TGM_prox(1)-5600)/200.>(1.4-TGM_prox(2))/0.4) THEN TGM_prox(2)=1.4_dp ELSE TGM_prox(1)=5600._dp END IF END IF END SUBROUTINE find_prox !############################## !this routine, given one grid point, creates the small grid "gridS" SUBROUTINE make_gridS(TGM_prox,temp_gridS,logg_gridS,met_gridS) IMPLICIT NONE REAL(DP), DIMENSION(3), INTENT(IN) :: TGM_prox REAL(DP), DIMENSION(5),INTENT(OUT) :: temp_gridS,logg_gridS,met_gridS INTEGER(I1B) :: inf,sup,pos pos=INT(MINLOC(ABS(temp_gridL-TGM_prox(1)),1),I1B) inf=pos-2_I1B sup=pos+2_I1B temp_gridS=temp_gridL(inf:sup) pos=INT(MINLOC(ABS(logg_gridL-TGM_prox(2)),1),I1B) inf=pos-2_I1B sup=pos+2_I1B logg_gridS=logg_gridL(inf:sup) pos=INT(MINLOC(ABS(met_gridL-TGM_prox(3)),1),I1B) inf=pos-2_I1B sup=pos+2_I1B met_gridS=met_gridL(inf:sup) END SUBROUTINE make_gridS !######################## !this routine finds the closest grid point to the present !TGM estimation and set the three flags flag_lim,flag_limS, !and flag_move. The first and the second ones report when the TGM estimation is beyond !the border of the large grid and the small grid, respectively. !The third one indicate if the estimation is beyond 1.5 steps from the central point. SUBROUTINE find_proxS(TGM_,temp_gridS,logg_gridS,met_gridS,& TGM_prox,flag_lim,flag_limS,flag_move) IMPLICIT NONE REAL(DP), DIMENSION(:), INTENT(IN) :: TGM_ REAL(DP), DIMENSION(5),INTENT(IN) :: temp_gridS,logg_gridS,met_gridS REAL(DP), DIMENSION(3),INTENT(INOUT) :: TGM_prox LOGICAL,INTENT(OUT) :: flag_lim,flag_limS,flag_move REAL(DP), DIMENSION(3) :: TGM_dummy !initialize flag_lim=.FALSE. flag_limS=.FALSE. flag_move=.FALSE. !remember that: gridL means Large grid ! grid means the intermediate grid ! gridS means Small grid !see in data_lab their values TGM_dummy(1)=temp_gridS(MINLOC(ABS(temp_gridS-TGM_(1)),1)) TGM_dummy(2)=logg_gridS(MINLOC(ABS(logg_gridS-TGM_(2)),1)) TGM_dummy(3)=met_gridS(MINLOC(ABS(met_gridS-TGM_(3)),1)) CALL find_prox(TGM_dummy,TGM_prox) !if TGM_dummy is on the border of the gridL, then exit the program IF((TGM_dummy(1)==temp_gridL(1).AND.TGM_(1)<=temp_gridL(1))& .OR.(TGM_dummy(1)==temp_gridL(SIZE(temp_gridL)).AND.TGM_(1)>=temp_gridL(SIZE(temp_gridL)))& .OR.(TGM_dummy(2)==logg_gridL(1).AND.TGM_(2)<=logg_gridL(1))& .OR.(TGM_dummy(2)==logg_gridL(SIZE(logg_gridL)).AND.TGM_(2)>=logg_gridL(SIZE(logg_gridL)))& .OR.(TGM_dummy(3)==met_gridL(1).AND.TGM_(3)<=met_gridL(1))& .OR.(TGM_dummy(3)==met_gridL(SIZE(met_gridL)).AND.TGM_(3)>=met_gridL(SIZE(met_gridL)))) THEN flag_lim=.TRUE. RETURN END IF !if the TGM point is out of the gridS then flag_limS=.TRUE. !(which exit the inner loop in the main program) IF(TGM_(1)<=temp_gridS(1).OR.TGM_(1)>=temp_gridS(5)& .OR.TGM_(2)<=logg_gridS(1).OR.TGM_(2)>=logg_gridS(5)& .OR.TGM_(3)==met_gridS(1).OR.TGM_(3)>=met_gridS(5)) THEN flag_limS=.TRUE. !TGM_dummy can be out of the grid (because gridS can be as well) !then take the closest point of grid (we don't want TGM_prox to be out ! of the grid) RETURN END IF !if the TGM point moved more than 1 point of the gridS, then flag_move=.TRUE. !(which call the routine update_gridS_ll in the main program) IF(TGM_(1)<=temp_gridS(2).OR.TGM_(1)>=temp_gridS(4)& &.OR.TGM_(2)<=logg_gridS(2).OR.TGM_(2)>=logg_gridS(4)& .OR.TGM_(3)<=met_gridS(2).OR.TGM_(3)>=met_gridS(4)) THEN flag_move=.TRUE. ELSE flag_move=.FALSE. END IF !if TGM_dummy is outside of the grid, it cannot move forward, then IF(TGM_dummy(1)/=TGM_prox(1).OR.TGM_dummy(2)/=TGM_prox(2)& &.OR.TGM_dummy(3)/=TGM_prox(3)) THEN flag_move=.FALSE. END IF END SUBROUTINE find_proxS !######################## !this routine allocates the array ABD and other arrays !related to it. SUBROUTINE alloc_ABD(ele_in,dim_in,dim_ele,n_ele_symb) USE error USE share, ONLY: ele2meas,ABD,ele2write,up_ABD,lo_ABD,& & ABD_old,residABD,X_abd,alpha_mask IMPLICIT NONE INTEGER(I2B), INTENT(IN) :: dim_in,n_ele_symb INTEGER(I2B), INTENT(OUT) :: dim_ele INTEGER(I2B) :: i, j, AllocateStatus REAL(DP), DIMENSION(dim_in), INTENT(IN) :: ele_in LOGICAL, DIMENSION(dim_in) :: mask LOGICAL, DIMENSION(n_ele_symb) :: mask_ele !initialize variables mask_ele=.FALSE. mask=.FALSE. DO i=2,n_ele_symb WHERE(ABS(ele_in-i)<0.3) mask=.TRUE. IF(ANY(mask)) mask_ele(i)=.TRUE. mask=.FALSE. END DO !if there is no Fe lines the program stops IF(mask_ele(26).NEQV..TRUE.) CALL stop_msg('no iron lines!') dim_ele=INT(COUNT(mask_ele),I1B) ALLOCATE(ABD(dim_ele), STAT = AllocateStatus) IF (AllocateStatus/=0) CALL stop_msg('Not enough memory to allocate ABD') ALLOCATE(lo_ABD(dim_ele), STAT = AllocateStatus) IF (AllocateStatus/=0) CALL stop_msg('Not enough memory to allocate lo_ABD') ALLOCATE(up_ABD(dim_ele), STAT = AllocateStatus) IF (AllocateStatus/=0) CALL stop_msg('Not enough memory to allocate up_ABD') ALLOCATE(ABD_old(dim_ele), STAT = AllocateStatus) IF (AllocateStatus/=0) CALL stop_msg('Not enough memory to allocate ABD_old') ALLOCATE(residABD(dim_ele), STAT = AllocateStatus) IF (AllocateStatus/=0) CALL stop_msg('Not enough memory to allocate residABD') ALLOCATE(ABD_mask(dim_ele), STAT = AllocateStatus) IF (AllocateStatus/=0) CALL stop_msg('Not enough memory to allocate ABD_mask') ALLOCATE(alpha_mask(dim_ele), STAT = AllocateStatus) IF (AllocateStatus/=0) CALL stop_msg('Not enough memory to allocate alpha_mask') ALLOCATE(write_ABD_mask(dim_ele), STAT = AllocateStatus) IF (AllocateStatus/=0) CALL stop_msg('Not enough memory to allocate write_ABD_mask') ALLOCATE(X_abd(70,dim_ele), STAT = AllocateStatus) IF (AllocateStatus/=0) CALL stop_msg('Not enough memory to allocate X_abd') !initialize ABD_mask ABD_mask=.TRUE. alpha_mask=.FALSE. !initialize ABD (it must come before quick estimation) ABD=0.0 !initializing at -0.1 instead of 0.0 allows 2 abundances loops instead of one for !those spectra with met=0.0 ABD_old=-0.1 ALLOCATE(ele2meas(dim_ele), STAT = AllocateStatus) IF (AllocateStatus /= 0) CALL stop_msg('Not enough memory to allocate ele2meas') ele2meas=0 !allocate Fe in the first position of ele2meas ele2meas(1)=26 !now allocate the other elements j=2 DO i=2,n_ele_symb IF(mask_ele(i).AND.i/=26) THEN ele2meas(j)=i j=j+1_I1B END IF END DO !check if ele2write has been allocate. if not, allocate it !it has to be equal to ele2meas (this happen only when the user !did not put the keywork 'ELE2WRITE' in the parameter file IF(.NOT.ALLOCATED(ele2write)) THEN ALLOCATE(ele2write(dim_ele), STAT = AllocateStatus) IF (AllocateStatus/=0) CALL stop_msg('Not enough memory to allocate ele2write') ele2write=ele2meas END IF END SUBROUTINE alloc_ABD !############################## REAL(DP) FUNCTION compute_aFe(ABD, ele2meas, ABD_mask) USE stats, ONLY: avg_std_mask USE share, ONLY: alpha_mask IMPLICIT NONE REAL(DP), DIMENSION(:), INTENT(IN) :: ABD INTEGER(I2B), DIMENSION(:), INTENT(IN) :: ele2meas LOGICAL, DIMENSION(:), INTENT(IN) :: ABD_mask REAL(DP) :: alpha_mean,var,sdev alpha_mask=.FALSE. WHERE((ele2meas==6.OR.ele2meas==8.OR.ele2meas==12.OR.ele2meas==14.OR.ele2meas==20.OR.ele2meas==22)& &.AND.ABD_mask) alpha_mask=.TRUE. IF (COUNT(alpha_mask)>0) THEN CALL avg_std_mask(ABD,alpha_mask,alpha_mean,var,sdev) ELSE alpha_mean = 0. END IF compute_aFe = alpha_mean END FUNCTION compute_aFe !############################## !this subroutine creates the logical arrays select_ll_mask !with dimension dim_ll to select only the lines !that will be measured. Besides, it gives weights=1e-6 to the !rejected pixels, included those pixels that are recognised as !cosmic rays by the cosmic_rej subroutine above SUBROUTINE select_lines(fun,coeff,pars,flag) USE share, ONLY: wave_ll,ele_ll,dim_ll,dim_rej, & & wave_rej,rad_rej,select_ll_mask,w_sp,weights,& &ew,TGM,ABD,ele2meas,ele2write,cosmic_mask,dimsp,& &sn_var!,Ex_inf USE space_pars, ONLY: N_w_int,w_inf,w_sup USE read_sp_ll, ONLY: add_pix_sp USE func_poly, ONLY: poly4_transform IMPLICIT NONE REAL(DP), EXTERNAL :: fun INTEGER(I2B) :: i INTEGER(I4B) :: line_pos REAL(DP), DIMENSION(4), INTENT(IN) :: pars REAL(DP), DIMENSION(:,:), INTENT(IN) :: coeff REAL(DP) :: ew_neigh,rad,c,sigma REAL(DP), DIMENSION(70) :: X LOGICAL, INTENT(IN) :: flag LOGICAL, DIMENSION(dim_ll) :: mask_ew,select_ll_mask2 !initialize variables !this mask is used to select all the lines used plus the unused inside the rejected intervals !the latter will have very low weights select_ll_mask=.FALSE. !this mask select the line used for measurements only. It will not be used for plots !but only to counts which elements are involved in the measurements select_ll_mask2=.FALSE. ew_neigh=0. weights=1.0_dp mask_ew=.FALSE. c=1._dp+TGM(5)/299792._dp sigma=TGM(4) !compute the EW of the lines from the polynomial GCOG CALL poly4_transform(X,ABD,pars) DO i=1,dim_ll ew(i)=fun(X,ele_ll(i),coeff(:,i)) END DO !create the ew_mask to sum up the EW of the lines on a radius=sigma from the i-th line DO i=1,dim_ll mask_ew=.FALSE. WHERE(wave_ll>(wave_ll(i)-sigma).AND.wave_ll<(wave_ll(i)+sigma)) mask_ew=.TRUE. ew_neigh=SUM(ew,mask_ew) !find the pixel at the center of the line line_pos=INT(MINLOC(ABS(wave_ll(i)-w_sp),1),I4B) !now create the mask to select only lines for which the sum of the EW of the neighborhood ! is greater than a given value IF ((ew_neigh/2.5/sigma)>(1./sn_var(line_pos))) THEN WHERE((ABS(wave_ll(i)-wave_ll)<3*sigma)& &.AND.(ew>0.1/sn_var(line_pos))) select_ll_mask=.TRUE. select_ll_mask2=.TRUE. END WHERE END IF END DO !set low weights around the rejected lines DO i=1,dim_rej !determine the radius over which the lines must be rejected rad=max(3*sigma,rad_rej(i)) WHERE(wave_ll>=(wave_rej(i)-rad_rej(i)).AND.wave_ll<=(wave_rej(i)+rad_rej(i))) select_ll_mask=.TRUE. !set the weights WHERE(ABS(w_sp-wave_rej(i)*c)<=rad) weights=1e-6_dp END DO !set low weights on the 4 Angstroms tails added to the !blue and red extremes of the spectrum WHERE(w_spw_sp(dimsp-add_pix_sp)) weights=1e-6 !set low weights on the rejected intervals, if any IF(N_w_int>1) THEN DO i=1,INT(N_w_int,I2B)-1_I2B WHERE(w_sp>w_sup(i).AND.w_sp(wave_ll(i)-sigma).AND.w_sp<(wave_ll(i)+sigma)) weights=5_dp ! END IF ! END DO IF(flag) THEN ! set the ABD_mask ABD_mask=.FALSE. !now give the right mask to the other elements DO i=1,INT(SIZE(ele2meas,1),I2B) mask_ew=.FALSE. !now I use mask_ew as dummy mask !I used select_ll_mask2 because it select only the lines employed for measurements WHERE(ABS(ele_ll-ele2meas(i))<0.3.AND.select_ll_mask2) mask_ew=.TRUE. IF((COUNT(mask_ew)>0).AND.ABD(i)<0.7.AND.ABD(i)>-0.5) THEN ABD_mask(i)=.TRUE. ELSE ABD_mask(i)=.FALSE. ABD(i)=0. END IF END DO write_ABD_mask=.FALSE. DO i=1,INT(SIZE(ele2write,1),I2B) WHERE (ele2meas==ele2write(i)) write_ABD_mask=.TRUE. END DO END IF END SUBROUTINE select_lines !######################### SUBROUTINE normalize_pars(tgm, tgmx, tgm_mask) IMPLICIT NONE REAL(DP), DIMENSION(:),INTENT(IN) :: tgm REAL(DP), DIMENSION(:),INTENT(INOUT) :: tgmx LOGICAL, DIMENSION(:),INTENT(IN) :: tgm_mask REAL(DP), DIMENSION(6) :: tgm_local tgm_local=tgm !normalize Teff and log g tgm_local(1)=tgm_local(1)/5000. tgm_local(2)=tgm_local(2)/3. !pack it again for the minimization routine tgmx=PACK(tgm_local,tgm_mask) END SUBROUTINE normalize_pars !######################### SUBROUTINE denormalize_pars(tgm_local, tgmx, tgm_mask) USE share, ONLY: TGM IMPLICIT NONE REAL(DP), DIMENSION(:),INTENT(INOUT) :: tgm_local REAL(DP), DIMENSION(:),INTENT(IN) :: tgmx LOGICAL, DIMENSION(:),INTENT(IN) :: tgm_mask !unpack tgm_local=UNPACK(tgmx,tgm_mask,TGM) IF (tgm_mask(1)) THEN tgm_local(1)=tgm_local(1)*5000. END IF IF (tgm_mask(2)) THEN tgm_local(2)=tgm_local(2)*3. END IF END SUBROUTINE denormalize_pars !##################################### END MODULE utils