Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

https://doi.org/10.5281/zenodo.8075681
29 October 2025, 17:21:42 UTC
  • Code
  • Branches (0)
  • Releases (4)
  • Visits
    • Branches
    • Releases
      • 4
      • 4
      • 3
      • 2
      • 1
    • ea30f42
    • /
    • danielver02-ALPS-220597d
    • /
    • src
    • /
    • ALPS_analyt.f90
    Raw File Download

    To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
    Select below a type of object currently browsed in order to display its associated SWHID and permalink.

    • content
    • directory
    • snapshot
    • release
    origin badgecontent badge
    swh:1:cnt:36ef2a720739d7ba3426668fa0b6ced3b313096f
    origin badgedirectory badge
    swh:1:dir:713ad21bcde4444c62318606c9e4b8ab59cc8bff
    origin badgesnapshot badge
    swh:1:snp:e9971dcac38583a5eceb223e9c3c25638ea32323
    origin badgerelease badge
    swh:1:rel:38e50bdfa887feee20e30d4b944782bd46bda1ba

    This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
    Select below a type of object currently browsed in order to generate citations for them.

    • content
    • directory
    • snapshot
    • release
    (requires biblatex-software package)
    Generating citation ...
    (requires biblatex-software package)
    Generating citation ...
    (requires biblatex-software package)
    Generating citation ...
    (requires biblatex-software package)
    Generating citation ...
    ALPS_analyt.f90
    !Copyright (c) 2023, Kristopher G. Klein and Daniel Verscharen
    !All rights reserved.
    !
    !This source code is licensed under the BSD-style license found in the
    !LICENSE file in the root directory of this source tree.
    !
    !===============================================================================
    !I                                                                             I
    !I                              A  L  P  S                                     I
    !I                     Arbitrary Linear Plasma Solver                          I
    !I                                                                             I
    !I                              Version 1.0                                    I
    !I                                                                             I
    !I  Kristopher Klein   (kgklein@arizona.edu)                                   I
    !I  Daniel Verscharen  (d.verscharen@ucl.ac.uk)                                I
    !I                                                                             I
    !===============================================================================
    
    module alps_analyt
    	!! This module contains functions and subroutines for the hybrid analytical continuation.
    	implicit none
    
            public  :: eval_fit, determine_param_fit
            private :: set_polynomial_basis
            private :: fit_function, output_fit, determine_JT, LM_nonlinear_fit
            private :: determine_GLLS, factorial
    
    contains
    
    
    
    double complex function eval_fit(is,iperp,ppar_valC)
      !! This function evaluates the fit to f0 at and the complex parallel
      !! momentum ppar_valC. It requires the fit parameters that will be determined
      !! by the subroutine [[determine_param_fit(subroutine)]].
    
            use alps_var, only : fit_type, pp, param_fit, n_fits, gamma_rel,nspec,relativistic
            use alps_var, only : ACmethod, poly_fit_coeffs, poly_order
    	use alps_distribution_analyt, only : distribution_analyt
    	implicit none
    
    
    	integer, intent(in) :: is
    	!! Index of species for which [[eval_fit(function)]] is executed.
    
    	integer, intent(in) :: iperp
    	!! Index of perpendicular momentum at which [[eval_fit(function)]] is executed.
    
    	double complex, intent(in) :: ppar_valC
    	!! Complex parallel momentum at which [[eval_fit(function)]] is executed.
    
    	double precision :: pperp_val
    	!! Perpendicular momentum corrsponding to index iperp.
    
    	integer :: par_ind
    	!! Parameter index for the fit parameters.
    
    	integer :: ifit
    	!! Index for the fits that add up to the total fit for a given species.
    
    	integer :: n_params
    	!! Total number of fit parameters for a given species.
    
    	integer :: is_rel
    	!! Index for relativistic species (if any).
    
    	integer :: sproc_rel
    	!! MPI process number for relativistic species.
    
    	integer :: is_run
    	!! Running variable over species to determine relativistic evaluation.
    
    	double precision,allocatable, dimension(:) :: params
    	!! Array of fit parameters.
    
     select case (ACmethod(is))
     case (0)
        ! Use the pre-coded distribution from distribution/distribution_analyt.f90
        eval_fit=distribution_analyt(is,pp(is,iperp,1,1),ppar_valC)
        return
    
    
     case (1)
        ! Use the 'n_fits' functions described with 'fit_type'
    	n_params=0	! total number of fit_parameters
    	do ifit=1,n_fits(is)
    		if (fit_type(is,ifit).EQ.1) n_params=n_params+3	! Maxwell
    		if (fit_type(is,ifit).EQ.2) n_params=n_params+5	! kappa
    		if (fit_type(is,ifit).EQ.3) n_params=n_params+3	! Juettner with pperp and ppar
    		if (fit_type(is,ifit).EQ.4) n_params=n_params+1	! Juettner with gamma and pparbar, constant in pparbar
    		if (fit_type(is,ifit).EQ.5) n_params=n_params+3	! Juettner with gamma and pparbar with pparbar-dependence
    		if (fit_type(is,ifit).EQ.6) n_params=n_params+4	! Bi-Moyal distribution
    	enddo
    
    	allocate (params(n_params))
    
    
    	par_ind=0
    	do ifit=1,n_fits(is)
    		if (fit_type(is,ifit).EQ.1) then	! Maxwell
    			pperp_val=pp(is,iperp,1,1)
    			params(ifit+par_ind+0)=param_fit(is,iperp,1,ifit)
    			params(ifit+par_ind+1)=param_fit(is,iperp,2,ifit)
    			params(ifit+par_ind+2)=param_fit(is,iperp,3,ifit)
    			par_ind=par_ind+2
    		endif
    
    		if (fit_type(is,ifit).EQ.2) then	! kappa
    			pperp_val=pp(is,iperp,1,1)
    			params(ifit+par_ind+0)=param_fit(is,iperp,1,ifit)
    			params(ifit+par_ind+1)=param_fit(is,iperp,2,ifit)
    			params(ifit+par_ind+2)=param_fit(is,iperp,3,ifit)
    			params(ifit+par_ind+3)=param_fit(is,iperp,4,ifit)
    			params(ifit+par_ind+4)=param_fit(is,iperp,5,ifit)
    			par_ind=par_ind+4
    		endif
    
    		if (fit_type(is,ifit).EQ.3) then	! Juettner in pperp and ppar
    			pperp_val=pp(is,iperp,1,1)
    			params(ifit+par_ind+0)=param_fit(is,iperp,1,ifit)
    			params(ifit+par_ind+1)=param_fit(is,iperp,2,ifit)
    			params(ifit+par_ind+2)=param_fit(is,iperp,3,ifit)
    			par_ind=par_ind+2
    		endif
    
    		if (fit_type(is,ifit).EQ.4) then	! Juettner in gamma and pparbar, constant in pparbar
    			! Determine your sproc_rel:
     			 is_rel=0
    			 do is_run=1,nspec
    			 	if (relativistic(is_run)) then
    		 			is_rel=is_rel+1
    	 				if (is_run.EQ.is) sproc_rel=is_rel
    		 		endif
    			 enddo
    			pperp_val=gamma_rel(sproc_rel,iperp,1)
    			params(ifit+par_ind+0)=param_fit(is,iperp,1,ifit)
    			par_ind=par_ind+0
    		endif
    
    		if (fit_type(is,ifit).EQ.5) then	! Juettner in gamma and pparbar with pparbar-dependence
    			! Determine your sproc_rel:
     			 is_rel=0
    			 do is_run=1,nspec
    			 	if (relativistic(is_run)) then
    		 			is_rel=is_rel+1
    	 				if (is_run.EQ.is) sproc_rel=is_rel
    		 		endif
    			 enddo
    			pperp_val=gamma_rel(sproc_rel,iperp,1)
    			params(ifit+par_ind+0)=param_fit(is,iperp,1,ifit)
    			params(ifit+par_ind+1)=param_fit(is,iperp,2,ifit)
    			params(ifit+par_ind+2)=param_fit(is,iperp,3,ifit)
    			par_ind=par_ind+2
    		endif
    
    		if (fit_type(is,ifit).EQ.6) then	! Bi-Moyal
    			pperp_val=pp(is,iperp,1,1)
    			params(ifit+par_ind+0)=param_fit(is,iperp,1,ifit)
    			params(ifit+par_ind+1)=param_fit(is,iperp,2,ifit)
    			params(ifit+par_ind+2)=param_fit(is,iperp,3,ifit)
    			params(ifit+par_ind+3)=param_fit(is,iperp,4,ifit)
    			par_ind=par_ind+3
    		endif
    	enddo
    
    	eval_fit=fit_function(is,n_params,params,pperp_val,ppar_valC)
    
    	deallocate (params)
    
     return
    
    case (2)
       ! use the orthogonal polynomials of kind described in &poly_is
       
       !write(*,*)iproc,is,iperp,ppar_valC
       eval_fit=fit_function_poly(is,iperp,ppar_valC,poly_order(is),&
            poly_fit_coeffs(is,iperp,0:poly_order(is)))
       !write(*,*)iproc,is,iperp,ppar_valC,eval_fit
       
    end select
    end function eval_fit
    
    
    
    double complex function fit_function(is,n_params,params,pperp_val,ppar_val)
    	!! This function evaluates the fit to f0 at real pperp_val and complex ppar_val,
    	!! provided that the one-dimensional fit-parameter array params is fed into the
    	!! function. This is only used during the fitting. For the evaluation in ALPS,
    	!! use [[eval_fit(function)]].
    	use alps_var, only : fit_type, n_fits, ms, vA, perp_correction
    	implicit none
    
    	integer, intent(in) :: is
    	!! Index of species for which [[eval_fit(function)]] is executed.
    
    	integer, intent(in) :: n_params
    	!! Total number of fit parameters for a given species.
    
    	double precision, intent(in) :: params(n_params)
    	!! Array of fit parameters.
    
    	double precision, intent(in) :: pperp_val
    	!! Perpendicular momentum.
    
    	double complex, intent(in) :: ppar_val
    	!! Complex parallel momentum.
    
    	integer :: ifit
    	!! Index for the fits that add up to the total fit for a given species.
    
    	integer :: par_ind
    	!! Parameter index to loop over fits.
    
    	double complex :: sqrtpart
    	!! Square-root part of Juettner distribution.
    
    	double complex :: kappapart
    	!! Kappa part of the kappa distribution.
    
    
    	fit_function=cmplx(0.d0,0.d0,kind(1.d0))
    
    	par_ind=0
    	do ifit=1,n_fits(is)
    		if (fit_type(is,ifit).EQ.1) then	! Maxwell
    			fit_function=fit_function+params(ifit+par_ind+0)*exp(-perp_correction(is,ifit)*pperp_val**2)&
    				*exp(-params(ifit+par_ind+1)*(ppar_val-params(ifit+par_ind+2))**2)
    			par_ind=par_ind+2
    		endif
    
    		if (fit_type(is,ifit).EQ.2) then	! kappa
    			kappapart=1.d0+params(ifit+par_ind+1)*(ppar_val-params(ifit+par_ind+2))**2&
    				+ perp_correction(is,ifit)*params(ifit+par_ind+4)*pperp_val**2
    			fit_function=fit_function+params(ifit+par_ind+0)*kappapart**params(ifit+par_ind+3)
    			par_ind=par_ind+4
    		endif
    
    		if (fit_type(is,ifit).EQ.3) then	! Juettner in pperp and ppar
    			sqrtpart=sqrt(1.d0+(pperp_val**2+(ppar_val-params(ifit+par_ind+2))**2)*vA*vA/(ms(is)*ms(is)))
    			fit_function=fit_function+params(ifit+par_ind+0)*exp(-params(ifit+par_ind+1)*sqrtpart)
    			par_ind=par_ind+2
    		endif
    
    		if (fit_type(is,ifit).EQ.4) then ! Juettner in gamma only, constant in pparbar
    			fit_function=fit_function+params(ifit+par_ind+0)*exp(-perp_correction(is,ifit)*pperp_val)
    			par_ind=par_ind+0
    		endif
    
    		if (fit_type(is,ifit).EQ.5) then ! Juettner in gamma and pparbar with pparbar-dependence
    			fit_function=fit_function+params(ifit+par_ind+0)*exp(-perp_correction(is,ifit)*pperp_val)* &
    				exp(-params(ifit+par_ind+1)*(ppar_val-params(ifit+par_ind+2))**2)
    			par_ind=par_ind+2
    		endif
    
    		if (fit_type(is,ifit).EQ.6) then	! Bi-Moyal
    			fit_function=fit_function+params(ifit+par_ind+0)*exp(0.5d0*(params(ifit+par_ind+3)* &
    					perp_correction(is,ifit)*pperp_val**2 + params(ifit+par_ind+1)* &
    					(ppar_val-params(ifit+par_ind+2))**2 - &
    					exp(params(ifit+par_ind+3)*perp_correction(is,ifit)*pperp_val**2 + &
    					params(ifit+par_ind+1)*(ppar_val-params(ifit+par_ind+2))**2) ))
    			par_ind=par_ind+3
    		endif
    
    	enddo
    	return
    end function fit_function
    
    double complex function fit_function_poly(is,iperp,ppar_val,n_poly,fit_coeffs)
      !! This function evaluates the orthogonal polynomical fit to f0
      !! at real pperp_val and complex ppar_val,
      !! with the one-dimensional polynomical coefficient array fit_coeffs is fed into the
      !! function.
      !! For the evaluation in ALPS, use [[eval_fit(function)]].
      use alps_var, only : pp, poly_kind, npar, logfit
      use alps_var, only : poly_log_max
     implicit none
    
     integer :: n_poly
     !! Maximum Polynomial Order
     
     integer, intent(in) :: is
     !! Index of species for which [[eval_fit(function)]] is executed.
    
     double precision, intent(in) :: fit_coeffs(0:n_poly)
     !! Array of polynomial coefficients
    
     double complex :: poly_basis(0:n_poly)
     !! Array of polynomial coefficients
    
      integer :: n
     !! Polynomial Order Index
    
     integer, intent(in) :: iperp
     !! Index of perpendicular momentum at which [[fit_function_poly(function)]] is executed.
    
     double complex :: ppar_val
     !! Complex parallel momentum.
    
     double complex :: ppar_val_tmp
     !! Complex parallel momentum.
    
     double precision :: norm_1, norm_2
     !! Range of p_parallel for rescaling polynomials
    
     fit_function_poly=cmplx(0.d0,0.d0,kind(1.d0))
     
     select case (poly_kind(is))
     case (1)
        !Chebyshev polynomials range from [-1,1]
        norm_1=5.d-1*(pp(is,iperp,npar,2)+pp(is,iperp,0,2))
        norm_2=5.d-1*(pp(is,iperp,npar,2)-pp(is,iperp,0,2))
        ppar_val_tmp=(ppar_val-norm_1)/norm_2
    
        if (abs(ppar_val_tmp).gt.1.d0) then !!kgk: should this be .gt. or .ge. ?
           fit_function_poly=0.d0
        else
        n=0
        poly_basis(n)=cmplx(1.d0,0.d0,kind(1.d0))
        fit_function_poly=fit_function_poly+fit_coeffs(n)*poly_basis(n)
        n=1
        poly_basis(n)=ppar_val_tmp
        fit_function_poly=fit_function_poly+fit_coeffs(n)*poly_basis(n)
        do n=2,n_poly
           poly_basis(n) = cmplx(2.d0,0.d0,kind(1.d0)) * ppar_val_tmp * poly_basis(n-1) - poly_basis(n-2)
           fit_function_poly=fit_function_poly+fit_coeffs(n)*poly_basis(n)
        enddo
        if (logfit(is)) then
    
           !if (abs(fit_function_poly).gt.20.) then
           !KGK: how does this impact the 'noise'
           !if ((real(fit_function_poly).gt.1.).or.(aimag(fit_function_poly).gt.1.)&
           !     .or.(real(fit_function_poly).lt.-18.).or.(aimag(fit_function_poly).lt.-18.)) then
    
           !if ((real(fit_function_poly).gt.10.).or.(aimag(fit_function_poly).gt.10.)&
           !     .or.(real(fit_function_poly).lt.-22.).or.(aimag(fit_function_poly).lt.-22.)) then
    
           !KGK: Need to investigate the appropriate threshold here for when this functional
           !representation ceases to work.
           !if ((real(fit_function_poly).gt.17.).or.(aimag(fit_function_poly).gt.17.)&
             !if ((real(fit_function_poly).lt.-poly_log_max(is)).or.& !??
                  !(aimag(fit_function_poly).lt.-poly_log_max(is))) then
    
           if ((real(fit_function_poly).lt.-poly_log_max(is)).or.& !??
                (aimag(fit_function_poly).lt.-poly_log_max(is)).or.&
                (real(fit_function_poly).gt. poly_log_max(is)).or.& !??
                (aimag(fit_function_poly).gt.poly_log_max(is))) then 
    
    
           !if ((abs(fit_function_poly).lt.-100.d0).or.(abs(fit_function_poly).gt.100.d0)) then
           !if ((abs(fit_function_poly).lt.-50.d0).or.(abs(fit_function_poly).gt.50.d0)) then
           !if (abs(fit_function_poly).gt.poly_log_max(is)) then
              fit_function_poly=cmplx(0.d0,0.d0,kind(1.d0))
           else
              !write(*,*)iproc,is,iperp,ppar_val,ppar_val_tmp,fit_function_poly
              fit_function_poly=10.d0**(fit_function_poly)
              !write(*,*)iproc,is,iperp,ppar_val,ppar_val_tmp,fit_function_poly
           endif
        endif
     endif
    end select
     
     return
    end function fit_function_poly
    
    subroutine determine_param_fit
    	!! This is the fitting routine for the hybrid analytic continuation. It determines
    	!! the full field [[alps_var(module):param_fit(variable)]].
    	use alps_var, only : writeOut, fit_type, param_fit, n_fits, nspec, f0, nperp, npar, logfit, runname
            use alps_var, only : relativistic,npparbar,f0_rel,ngamma, perp_correction, gamma_rel, usebM
            use alps_var, only : ACmethod, poly_fit_coeffs 
    	implicit none
    
    	integer :: ifit
    	!! Index for the fits that add up to the total fit for a given species.
    
    	integer :: n_params
    	!! Total number of fit parameters for a given species.
    
    	integer :: par_ind
    	!! Parameter index for the fit parameters.
    
    	integer :: iperp
    	!! Index of perpendicular momentum.
    
    	integer :: is
    	!! Index of species.
    
    	integer :: is_run
    	!! Running variable over species to determine relativistic evaluation.
    
    	integer :: is_rel
    	!! Index for relativistic species (if any).
    
    	integer :: sproc_rel
    	!! Local process index for relativistic calculation.
    
    	integer :: nJT
    	!! First dimension of matrix JT.
    
    	integer :: ipparbar
    	!! Index of parallel momentum (relativistic).
    
    	integer :: ipparbar_lower
    	!! Lower index of parallel momentum (relativistic).
    
    	integer :: ipparbar_upper
    	!! Upper index of parallel momentum (relativistic).
    
    	integer :: upperlimit
    	!! Upper limit of iperp space (relativistic and non-relativistic).
    
    	integer :: unit_spec
    	!! Unit to write fit parameters to file.
    
    	logical :: found_lower
    	!! Check whether lower boundary was found.
    
    	logical :: found_upper
    	!! Check whether upper boundary was found.
    
    	double precision :: quality
    	!! Quality of the individual fit result.
    
    	double precision :: qualitytotal
    	!! Quality of the total fit result.
    
    	logical, allocatable, dimension (:) :: param_mask
    	!! Bit mask for required fit parameters.
    
    	double precision,allocatable,dimension (:) :: g
    	!! Array of function to be fitted.
    
    	double precision,allocatable,dimension(:) :: params
    	!! Array of fit parameters.
    
    	character (10) :: specwrite
    	!! File name to write fit parameters to file.
    
    	if (writeOut) then
    		write (*,'(a)') 'Determine fit parameters for hybrid analytic continuation...'
    	endif
    
    	qualitytotal=0.d0
    
    	do is=1,nspec
    
    		if (usebM(is)) then
         write (*,'(a,i2)') ' Bi-Maxwellian/cold-plasma calculation: no fits necessary for species ',is
         param_fit(is,:,:,:)=0.d0
         fit_type(is,:)=1
         perp_correction(is,:)=1.d0
    
    		 elseif (ACmethod(is).EQ.0) then
    
    			 	write (*,'(a,i2)') ' Using analytical function from distribution/distribution_analyt.f90: no fits necessary for species ',is
    
    				param_fit(is,:,:,:)=0.d0
    				fit_type(is,:)=1
        perp_correction(is,:)=1.d0
    
        		 elseif (ACmethod(is).EQ.2) then
    
    			 	write (*,'(a,i2)') ' Using polynomial representation for species ',is
    
    				param_fit(is,:,:,:)=0.d0
    				fit_type(is,:)=1
                                    perp_correction(is,:)=1.d0
    
                                    call set_polynomial_basis(is)
    
                                    call determine_GLLS(is)
    
                                    unit_spec=2500+is
                                    write(specwrite,'(i0)') is
                                    open(unit = unit_spec,file = 'distribution/'&
                                         //trim(runname)//'.poly_parameters.'//trim(specwrite)&
                                         //'.out', status = 'replace')
                                    do iperp=0,nperp
                                       write(unit_spec,*)&
                                            iperp,poly_fit_coeffs(is,iperp,:)
                                    enddo
    
                                    close(unit_spec)
    
    		 else
    		! For all fit types that include a fit parameter for the perpendicular momentum (kappa and Moyal),
    		! we must not fit this parameter when pperp=0. Otherwise, the LM matrix is singular:
    
    					n_params=0	! total number of fit_parameters
    					do ifit=1,n_fits(is)
    						if (fit_type(is,ifit).EQ.1) n_params=n_params+3	! Maxwell
    						if (fit_type(is,ifit).EQ.2) n_params=n_params+5	! kappa
    						if (fit_type(is,ifit).EQ.3) n_params=n_params+3	! Juettner with pperp and ppar
    						if (fit_type(is,ifit).EQ.4) n_params=n_params+1	! Juettner with gamma and pparbar, constant in pparbar
    						if (fit_type(is,ifit).EQ.5) n_params=n_params+3	! Juettner with gamma and pparbar with pparbar-dependence
    						if (fit_type(is,ifit).EQ.6) n_params=n_params+4	! Bi-Moyal
    					enddo
    
    			allocate (params(n_params))
    			allocate (param_mask(n_params))
    
    
    		if (relativistic(is)) then
    			upperlimit=ngamma
    		else
    			upperlimit=nperp
    		endif
    
    
    	  unit_spec=2500+is
    	  write(specwrite,'(i0)') is
    	  open(unit = unit_spec,file = 'distribution/'//trim(runname)//'.fit_parameters.'//trim(specwrite)//'.out', status = 'replace')
    
    
    		do iperp=0,upperlimit
    
    			! Every step that is not iperp = 0 should use the previous result as a start value:
    			if (iperp.NE.0) param_fit(is,iperp,:,:)=param_fit(is,iperp-1,:,:)
    
    			par_ind=0
    			nJT=0
    			param_mask = .TRUE.
    
    			do ifit=1,n_fits(is)
    				if (fit_type(is,ifit).EQ.1) then	! Maxwell
    					params(ifit+par_ind+0)=param_fit(is,iperp,1,ifit)
    					params(ifit+par_ind+1)=param_fit(is,iperp,2,ifit)
    					params(ifit+par_ind+2)=param_fit(is,iperp,3,ifit)
    					par_ind=par_ind+2
    				endif
    
    				if (fit_type(is,ifit).EQ.2) then	! kappa
    					params(ifit+par_ind+0)=param_fit(is,iperp,1,ifit)
    					params(ifit+par_ind+1)=param_fit(is,iperp,2,ifit)
    					params(ifit+par_ind+2)=param_fit(is,iperp,3,ifit)
    					params(ifit+par_ind+3)=param_fit(is,iperp,4,ifit)
    					params(ifit+par_ind+4)=param_fit(is,iperp,5,ifit)
    					if (iperp.EQ.0) then
    							nJT=nJT-1
    							param_mask(ifit+par_ind+4)=.FALSE.
    					endif
    					par_ind=par_ind+4
    				endif
    
    				if (fit_type(is,ifit).EQ.3) then	! Juettner in pperp and ppar
    					params(ifit+par_ind+0)=param_fit(is,iperp,1,ifit)
    					params(ifit+par_ind+1)=param_fit(is,iperp,2,ifit)
    					params(ifit+par_ind+2)=param_fit(is,iperp,3,ifit)
    					par_ind=par_ind+2
    				endif
    
    				if (fit_type(is,ifit).EQ.4) then	! Juettner in gamma and pparbar, constant in pparbar
    					params(ifit+par_ind+0)=param_fit(is,iperp,1,ifit)
    					par_ind=par_ind+0
    				endif
    
    				if (fit_type(is,ifit).EQ.5) then	! Juettner in gamma and pparbar with pparbar-dependence
    					params(ifit+par_ind+0)=param_fit(is,iperp,1,ifit)
    					params(ifit+par_ind+1)=param_fit(is,iperp,2,ifit)
    					params(ifit+par_ind+2)=param_fit(is,iperp,3,ifit)
    					par_ind=par_ind+2
    				endif
    
    				if (fit_type(is,ifit).EQ.6) then	! Bi-Moyal
    					params(ifit+par_ind+0)=param_fit(is,iperp,1,ifit)
    					params(ifit+par_ind+1)=param_fit(is,iperp,2,ifit)
    					params(ifit+par_ind+2)=param_fit(is,iperp,3,ifit)
    					params(ifit+par_ind+3)=param_fit(is,iperp,4,ifit)
    					if (iperp.EQ.0) then
    							nJT=nJT-1
    							param_mask(ifit+par_ind+3)=.FALSE.
    					endif
    					par_ind=par_ind+3
    				endif
    
    			enddo
    
    
    			nJT=nJT+n_params
    
    
    			! Fit and return everything in one array "params":
    			if (relativistic(is)) then
    				 ! Determine your sproc_rel:
    				 is_rel=0
    				 do is_run=1,nspec
    					if (relativistic(is_run)) then
    						is_rel=is_rel+1
    						if (is_run.EQ.is) sproc_rel=is_rel
    					endif
    				 enddo
    
    				 ! What are the relevant ranges in pparbar:
    				found_lower=.FALSE.
    				found_upper=.FALSE.
    				ipparbar_lower = 1
    				ipparbar_upper = npparbar - 1
    				do ipparbar=1,npparbar-1
    					if((.NOT.found_lower).AND.(f0_rel(sproc_rel,iperp,ipparbar-1).LE.-1.d0).AND.(f0_rel(sproc_rel,iperp,ipparbar).GT.-1.d0)) then
    						ipparbar_lower = ipparbar
    						found_lower=.TRUE.
    					endif
    					if((.NOT.found_upper).AND.(f0_rel(sproc_rel,iperp,ipparbar).GT.-1.d0).AND.(f0_rel(sproc_rel,iperp,ipparbar+1).LE.-1.d0)) then
    						ipparbar_upper = ipparbar
    						found_upper=.TRUE.
    					endif
    				enddo
    
    				if ((ipparbar_upper-ipparbar_lower).GT.2) then
    
    					allocate(g(0:ipparbar_upper-ipparbar_lower))
    
    
    					if (logfit(is)) then
    						g=log(f0_rel(sproc_rel,iperp,ipparbar_lower:ipparbar_upper))
    					else
    						g=f0_rel(sproc_rel,iperp,ipparbar_lower:ipparbar_upper)
    					endif
    
    
    					call LM_nonlinear_fit(is,g,n_params,nJT,params,param_mask,iperp,&
    											(ipparbar_upper-ipparbar_lower),ipparbar_lower,quality)
    					deallocate(g)
    
    				else
    
    
    					par_ind=0
    					do ifit=1,n_fits(is)
    						params(ifit+par_ind+0)=f0_rel(sproc_rel,iperp,(ipparbar_upper+ipparbar_lower)/2) /&
    							exp(-perp_correction(is,ifit)*gamma_rel(sproc_rel,iperp,1))
    						if (fit_type(is,ifit).EQ.5) then
    							params(ifit+par_ind+1)=1.d-12
    							params(ifit+par_ind+2)=0.d0
    							par_ind=par_ind+2
    						endif
    					enddo
    
    				endif
    
    			else	! non-relativistic
    
    
    				allocate(g(0:npar))
    
    				if (logfit(is)) then
    					g=log(f0(is,iperp,:))
    				else
    					g=f0(is,iperp,:)
    				endif
    
    
    				call LM_nonlinear_fit(is,g,n_params,nJT,params,param_mask,iperp,npar,0,quality)
    				deallocate(g)
    
    			endif
    
    			qualitytotal=qualitytotal+quality
    
    			! Write  Fit parameters to output files
    			write (unit_spec,*) iperp,params
    
    
    			! Fill it back into the param_fit field:
    			par_ind=0
    			do ifit=1,n_fits(is)
    				if (fit_type(is,ifit).EQ.1) then	! Maxwell
    					param_fit(is,iperp,1,ifit)=params(ifit+par_ind+0)
    					param_fit(is,iperp,2,ifit)=params(ifit+par_ind+1)
    					param_fit(is,iperp,3,ifit)=params(ifit+par_ind+2)
    					par_ind=par_ind+2
    				endif
    
    				if (fit_type(is,ifit).EQ.2) then	! kappa
    					param_fit(is,iperp,1,ifit)=params(ifit+par_ind+0)
    					param_fit(is,iperp,2,ifit)=params(ifit+par_ind+1)
    					param_fit(is,iperp,3,ifit)=params(ifit+par_ind+2)
    					param_fit(is,iperp,4,ifit)=params(ifit+par_ind+3)
    					param_fit(is,iperp,5,ifit)=params(ifit+par_ind+4)
    					par_ind=par_ind+4
    				endif
    
    				if (fit_type(is,ifit).EQ.3) then	! Juettner in pperp and ppar
    					param_fit(is,iperp,1,ifit)=params(ifit+par_ind+0)
    					param_fit(is,iperp,2,ifit)=params(ifit+par_ind+1)
    					param_fit(is,iperp,3,ifit)=params(ifit+par_ind+2)
    					par_ind=par_ind+2
    				endif
    
    				if (fit_type(is,ifit).EQ.4) then	! Juettner in gamma and pparbar, constant in pparbar
    					param_fit(is,iperp,1,ifit)=params(ifit+par_ind+0)
    					par_ind=par_ind+0
    				endif
    
    				if (fit_type(is,ifit).EQ.5) then	! Juettner in gamma and pparbar with pparbar-dependence
    					param_fit(is,iperp,1,ifit)=params(ifit+par_ind+0)
    					param_fit(is,iperp,2,ifit)=params(ifit+par_ind+1)
    					param_fit(is,iperp,3,ifit)=params(ifit+par_ind+2)
    					par_ind=par_ind+2
    				endif
    
    				if (fit_type(is,ifit).EQ.6) then	! Bi-Moyal
    					param_fit(is,iperp,1,ifit)=params(ifit+par_ind+0)
    					param_fit(is,iperp,2,ifit)=params(ifit+par_ind+1)
    					param_fit(is,iperp,3,ifit)=params(ifit+par_ind+2)
    					param_fit(is,iperp,4,ifit)=params(ifit+par_ind+3)
    					par_ind=par_ind+3
    				endif
    
    			enddo
    		enddo	! End loop over iperp
    
    		close (unit_spec)
    
    		deallocate (params)
    		deallocate (param_mask)
    
    endif !end if (bM or ACmethod) selection 
    enddo	! End loop over is
    
    
    if(writeOut) then
       call output_fit(qualitytotal)
       write(*,'(a)') '-=-=-=-=-=-=-=-=-'
    endif
    
    
    end subroutine determine_param_fit
    
    
    subroutine determine_GLLS(is)
      !! This subroutine evaluates the General Linear Least Squares fit
      !! to the distribution function for component 'is' using the selected
      !! polynomial basis functions.
      use alps_var, only : polynomials, nperp, npar, f0, poly_fit_coeffs, poly_order
      use alps_var, only : logfit
      implicit none
    
      integer :: is
      !! Index of species.
    
      integer :: iperp
      !! Perpendicular index.
    
      integer :: ipar
      !! parallel index
    
      double precision, dimension(:), allocatable :: f0_fit
    
      double precision :: min_val
    
      write(*,'(a,i2)')'Determining GLLS Coefficients for Component: ',is
      
      allocate(f0_fit(0:npar));f0_fit=0.d0
      do iperp=0,nperp
         if (logfit(is)) then
    
            if (minval(f0(is,iperp,:)) .gt. 0.d0) then
               f0_fit(:)=log10(f0(is,iperp,:))
            else
               ! Get min nonzero value from f0(is,iperp,:) before log10 conversion:
               if (any(f0(is,iperp,:) > 0.d0)) then
                  min_val = minval(f0(is,iperp,:), mask=(f0(is,iperp,:) > 0.d0))
               else
                  ! Fallback value if all entries are zero; adjust as needed.
                  min_val = 1.0d-6
               endif
               
               ! Copy the slice and replace zeros with 0.01*min_val:
               f0_fit(:) = f0(is,iperp,:)
               do ipar = 0, npar
                  if (f0_fit(ipar) == 0.d0) then
                     f0_fit(ipar) = 0.01d0 * min_val
                  endif
               enddo
               
               do ipar = 0, npar
                  if (f0_fit(ipar) <= 0.0d0) then
                     print*, 'Non-positive value at index', ipar, f0_fit(ipar)
                  endif
               enddo
               f0_fit(:)=log10(f0_fit(:))
            !else
            !   f0_fit(:)=log10(f0(is,iperp,:))
            endif
         else
            f0_fit(:)=f0(is,iperp,:)
         endif
         call least_squares_fit(polynomials(is,:,:),f0_fit,poly_fit_coeffs(is,iperp,:),poly_order(is))
      enddo
      deallocate(f0_fit)
      
    end subroutine determine_GLLS
    
    subroutine least_squares_fit(AA,BB,coeffs,npoly)
      !!Solves General Linear Least Squares Normal Equation
      use alps_var, only : npar
      implicit none
    
      integer :: npoly
      !! Order for polyhedral representation
      
      double precision, intent(in) :: BB(0:npar)
      !! f0(is,iperp,:)
    
        double precision, intent(out) :: coeffs(0:npoly)
        !! fit coefficients
    
        double precision, intent(in) :: AA(0:npar, 0:npoly)
        !! Polynomial Basis
       
        integer, dimension(0:npoly) :: ipiv
        !!Pivot Indices
    
        integer :: info
        !!LAPACK error diagnostics
    
        double precision :: alpha(0:npoly,0:npoly)
        !! A^T.A component of General Linear Least Square calculation
        !! e.g. Eqn 15.4.7 from Numerical Recipies
        
        double precision :: beta(0:npoly)
        !! A^T.b component of General Linear Least Square calculation
        !! e.g. Eqn 15.4.7 from Numerical Recipies
    
        alpha=0.d0
        beta=0.d0
        
        ! Calculate (A^T * A) for the normal equation solution
        call dgemm('T', 'N', npoly+1, npoly+1, npar+1, 1.0d0, AA(0:npar, 0:npoly), &
             npar+1, AA(0:npar, 0:npoly), npar+1, 0.0d0, alpha(0:npoly, 0:npoly), npoly+1)
    
        ! Calculate (A^T * B) for the normal equation solution
        call dgemv('T', npar+1, npoly+1, 1.0d0, AA(0:npar, 0:npoly), &
             npar+1, BB(0:npar), 1, 0.0d0, beta(0:npoly), 1)
        
        ! Solve for the coefficients using the LAPACK routine
        call dgesv(npoly+1, 1, alpha(0:npoly, 0:npoly), npoly+1, &
             ipiv(0:npoly), beta(0:npoly), npoly+1, info)
    
        coeffs(0:npoly)=beta(0:npoly)
    
        if (info /= 0) then
            print *, "Error in dgesv:", info
            stop
        end if
      
    end subroutine least_squares_fit
    
    subroutine set_polynomial_basis(is)
      !! This subroutine evaluates the General Linear Least Squares fit
      !! to the distribution function for component 'is' using the selected
      !! polynomial basis functions.
      use alps_var, only : polynomials, poly_kind, poly_order
      use alps_var, only : writeOut, npar
      use alps_io, only : alps_error
      implicit none
    
      integer :: is
      !! Index of species.
    
      integer :: ipar
      !! Index of parallel momentum.
    
      integer :: n
      !! Polynomial Order
    
      double precision :: yy
      !! Argument of Polynomial
    
      select case (poly_kind(is))
      case (1) !Chebyshev Polynomial Basis
         if (writeOut) & 
              write(*,'(a,i2)')'Constructing Chebyshev Basis for Component ',is
         
         polynomials(is,:,0) = 1.0
         do ipar = 0, npar
            yy=-1.d0+ipar*(2.d0/npar)
            polynomials(is,ipar,1) = yy
            do n = 2, poly_order(is)
               polynomials(is,ipar,n) = &
                    2.0 * yy * polynomials(is,ipar,n-1) - polynomials(is,ipar,n-2)
            end do
         enddo
      case default
         call alps_error(10)
      end select
      
    end subroutine set_polynomial_basis
    
    subroutine output_fit(qualitytotal)
    	!! This subroutine outputs the fit parameters for iperp=0 to stdout to monitor the fit.
    	use alps_io, only : isnancheck, alps_error
    	use alps_var, only : fit_type, param_fit, n_fits, nspec, nperp, npar, pp, f0, pi, vA, runname
            use alps_var, only : relativistic,gamma_rel,pparbar_rel,ngamma,npparbar,f0_rel, ms, usebM
            use alps_var, only : ACmethod
            use alps_var, only : ns, qs, ms, bMpdrifts
            use alps_var, only : density_int, current_int, poly_order
    	implicit none
    
    	double precision, intent(in) :: qualitytotal
    	!! Quality of the total fit result.
    
    	integer :: is
    	!! Index of species.
    
    	integer :: ifit
    	!! Index for the fits that add up to the total fit for a given species.
    
    	integer :: n_params
    	!! Total number of fit parameters for a given species.
    
    	integer :: iparam
    	!! Variable running over fit parameters.
    
    	integer :: unit_spec
    	!! Unit to write fit results to file.
    
    	integer :: ipar
    	!! Index of parallel momentum.
    
    	integer :: iperp
    	!! Index of perpendicular momentum.
    
    	integer :: is_rel
    	!! Index for relativistic species (if any).
    
    	integer :: sproc_rel
    	!! Local process index for relativistic calculation.
    
    	integer :: igamma
    	!! Index of gamma (relativistic).
    
    	integer :: ipparbar
    	!! Index of pparbar (relativistic).
    
    	integer :: is_run
    	!! Index for relativistic species (if any).
    
    	double precision :: integrate
     !! Integration of fit result.
    
     	double precision, dimension(0:nspec) :: charge
      !! Charge Density of species.
      !! Zeroth index is sum over all species
    
       	double precision, dimension(0:nspec) :: current
      !! Current Density of species.
      !! Zeroth index is sum over all species.
    
    	double precision :: dpperp
    	!! Step size in pperp for integration of fit result.
    
    	double precision :: dppar
    	!! Step size in ppar for integration of fit result.
    
    	double precision :: dgamma_rel
    	!! Step size in gamma for integration of relativistic fit result.
    
    	double precision :: dpparbar
    	!! Step size in pparbar for integration of relativistic fit result.
    
    	double complex :: ppar_comp
    	!! Complex parallel momentum to evaluate fit function at.
    
    	character (10) :: specwrite
    	!! File name to write fit results to file.
    
    	write (*,'(a)') ' Results of the fit for hybrid analytic continuation at iperp = 1:'
    
     do is=1,nspec
        select case (ACmethod(is))
        case (1)
           do ifit=1,n_fits(is)
              if(fit_type(is,ifit).EQ.1) n_params=3
              if(fit_type(is,ifit).EQ.2) n_params=5
              if(fit_type(is,ifit).EQ.3) n_params=3
              if(fit_type(is,ifit).EQ.4) n_params=1
              if(fit_type(is,ifit).EQ.5) n_params=3
              if(fit_type(is,ifit).EQ.6) n_params=4
              
              if (.not.usebM(is)) then
                 do iparam=1,n_params
                    write (*,'(a,i2,a,i2,a,i2,a,2es14.4)') '  param_fit(',is,', 1,',iparam,',',ifit,') = ',param_fit(is,1,iparam,ifit)
                 enddo
                 write (*,'(a)') ' '
              endif
           enddo
        case (2)
           
        end select
     enddo
    
    
    	write (*,'(a,es14.4e3)') ' Sum of all least-squares: ', qualitytotal
    	write (*,'(a,es14.4e3)') ' Standard error of the estimate: ', sqrt(qualitytotal/(1.d0*(nspec*nperp*npar)))
    
    
    	if (isnancheck(qualitytotal)) call alps_error(7)
    
     charge=0.d0
     current=0.d0
    
    	write (*,'(a)') ' Writing fit result to files'
    	do is=1,nspec
    
        if (usebM(is)) then
           charge(is)=ns(is)*qs(is)
           current(is)=ns(is)*qs(is)*&
                bMpdrifts(is)/ms(is)
           write(*,'(a)')&
                '-=-=-=-='
           write(*,'(a,i3,a)')&
                'Bi-Maxwellian/cold-plasma Species ',is,':'
           write(*,'(a, 2es14.4e3)') &
                ' Charge density:           ', charge(is)
           write(*,'(a, 2es14.4e3)') &
                ' Parallel current density: ', current(is)
    
        else
           select case (ACmethod(is))
           case (1)
    	  unit_spec=2000+is
    	  write(specwrite,'(i0)') is
    	  open(unit = unit_spec,file = 'distribution/'//trim(runname)//'.fit_result.'//trim(specwrite)//'.out', status = 'replace')
    
    
    	  integrate = 0.d0
    
    	  if (relativistic(is)) then
    
    		 ! Determine your sproc_rel:
    		 is_rel=0
    		 do is_run=1,nspec
    				if (relativistic(is_run)) then
    					is_rel=is_rel+1
    					if (is_run.EQ.is) sproc_rel=is_rel
    				endif
    		 enddo
    
    		dgamma_rel = gamma_rel(sproc_rel,2,2)-gamma_rel(sproc_rel,1,2)
    		dpparbar = pparbar_rel(sproc_rel,2,2)-pparbar_rel(sproc_rel,2,1)
    
    		do igamma=0,ngamma
    		 do ipparbar=0,npparbar
    			ppar_comp=pparbar_rel(sproc_rel,igamma,ipparbar)
    			if (f0_rel(sproc_rel,igamma,ipparbar).EQ.-1.d0) then
    				write (unit_spec,*) gamma_rel(sproc_rel,igamma,ipparbar),pparbar_rel(sproc_rel,igamma,ipparbar),&
    					"-1.0",	abs(-1.d0-f0_rel(sproc_rel,igamma,ipparbar))
    			else
    
    				write (unit_spec,*) gamma_rel(sproc_rel,igamma,ipparbar),pparbar_rel(sproc_rel,igamma,ipparbar),&
    					real(eval_fit(is,igamma,ppar_comp)),&
    						abs(real(eval_fit(is,igamma,ppar_comp))-f0_rel(sproc_rel,igamma,ipparbar))/f0_rel(sproc_rel,igamma,ipparbar)
    
    						  integrate = integrate + &
                         gamma_rel(is_rel,igamma,ipparbar) * real(eval_fit(is,igamma,ppar_comp)) * &
                         2.d0 * pi * dgamma_rel * dpparbar * (ms(is) / vA)**3
    
            charge(is) = charge(is) + &
                 qs(is)* ns(is)* gamma_rel(is_rel,igamma,ipparbar) * real(eval_fit(is,igamma,ppar_comp)) * &
                 2.d0 * pi * dgamma_rel * dpparbar * (ms(is) / vA)**3
    
            current(is) = current(is) + &
                 (ppar_comp/ms(is))*qs(is)* ns(is)* real(eval_fit(is,igamma,ppar_comp)) * &
                 2.d0 * pi * dgamma_rel * dpparbar * (ms(is) / vA)**3
    
    			endif
    		 enddo
    		enddo
    
    	  else 		! non-relativistic:
    
    	    dpperp = pp(is,2,2,1)-pp(is,1,2,1)
    	    dppar = pp(is,2,2,2)-pp(is,2,1,2)
    
    		do iperp=0,nperp
    			do ipar=0,npar
    				ppar_comp=pp(is,iperp,ipar,2)
    				write (unit_spec,*) pp(is,iperp,ipar,1), pp(is,iperp,ipar,2), real(eval_fit(is,iperp,ppar_comp)), &
             abs(real(eval_fit(is,iperp,ppar_comp))-f0(is,iperp,ipar))/f0(is,iperp,ipar)
    
        integrate = integrate + pp(is,iperp,ipar,1) * real(eval_fit(is,iperp,ppar_comp)) * &
                 2.d0 * pi * dpperp * dppar
        charge(is) = charge(is) + qs(is)* ns(is)* pp(is,iperp,ipar,1) * real(eval_fit(is,iperp,ppar_comp)) * &
             2.d0 * pi * dpperp * dppar
        current(is) = current(is) + (qs(is)* ns(is)/ms(is))*&
             pp(is,iperp,ipar,2) *pp(is,iperp,ipar,1) * real(eval_fit(is,iperp,ppar_comp)) * &
             2.d0 * pi * dpperp * dppar
    
    			enddo
    enddo
    
    	  endif
    	  close(unit_spec)
    
       write(*,'(a)')&
            '-=-=-=-='
       write(*,'(a,i3,a)')&
            'Species ',is,':'
       write(*,'(a, 2es14.4e3)') &
            ' Integration of fit/analytical function:              ', integrate
       write(*,'(a, 2es14.4e3)') &
            ' Charge density of fit/analytical function:           ', charge(is)
       write(*,'(a, 2es14.4e3)') &
            ' Parallel current density of fit/analytical function: ', current(is)
    
    case (2)
    
       integrate = 0.d0
       dpperp = pp(is,2,2,1)-pp(is,1,2,1)
       dppar = pp(is,2,2,2)-pp(is,2,1,2)
    
       
         unit_spec=2000+is
         write(specwrite,'(i0)') is
         open(unit = unit_spec,file = 'distribution/'//trim(runname)//'.poly_fit_result.'//trim(specwrite)//'.out', status = 'replace')
         write (*,'(a,i0)') ' Writing GLLS fit result to files: ispec',is
         do iperp=0,nperp
            do ipar=0,npar
               ppar_comp=pp(is,iperp,ipar,2)
               if (f0(is,iperp,ipar).gt.0.d0) then
                  write (unit_spec,*) pp(is,iperp,ipar,1), pp(is,iperp,ipar,2), &
                       real(eval_fit(is,iperp,ppar_comp)), &
                       abs(real(eval_fit(is,iperp,ppar_comp))-f0(is,iperp,ipar))/f0(is,iperp,ipar)
               else
                  write (unit_spec,*) pp(is,iperp,ipar,1), pp(is,iperp,ipar,2), &
                       real(eval_fit(is,iperp,ppar_comp)), &
                       0.d0
               endif
    
        integrate = integrate + pp(is,iperp,ipar,1) * real(eval_fit(is,iperp,ppar_comp)) * &
                 2.d0 * pi * dpperp * dppar
        charge(is) = charge(is) + qs(is)* ns(is)* pp(is,iperp,ipar,1) * real(eval_fit(is,iperp,ppar_comp)) * &
             2.d0 * pi * dpperp * dppar
        current(is) = current(is) + (qs(is)* ns(is)/ms(is))*&
             pp(is,iperp,ipar,2) *pp(is,iperp,ipar,1) * real(eval_fit(is,iperp,ppar_comp)) * &
             2.d0 * pi * dpperp * dppar
               
            enddo
         enddo
        
         close(unit_spec)
    
            write(*,'(a)')&
            '-=-=-=-='
       write(*,'(a,i3,a)')&
            'Species ',is,':'
       write(*,'(a, 2es14.4)') &
            ' Integration of polynomial representation:              ', integrate
       write(*,'(a, i0, es14.4)') &
            ' Relative Percent Difference in density for Order:', poly_order(is), &
            2.d0*(abs(density_int(is))-abs(integrate))/(abs(density_int(is))+abs(integrate))
       write(*,'(a, 2es14.4)') &
            ' Charge density of polynomial representation:           ', charge(is)
       if (relativistic(is)) then
          write(*,'(a)')'Relativistic parallel current density not yet implemented!'
       else
          write(*,'(a, es14.4)') &
               ' Parallel current density of polynomial representation: ', current(is)
          write(*,'(a, i0, es14.4)') &
               ' Relative Percent Difference in current density for Order:', poly_order(is), &
               2.d0*(abs(current(is))-abs(current_int(is)))/(abs(current_int(is))+abs(current(is)))
       endif
    
    
       
    end select
       
    endif
    enddo
    write(*,'(a)')         '-=-=-=-='
    write(*,'(a, es14.4e3)') ' Total charge density of fit/analytical function:           ', sum(charge(1:nspec))
    write(*,'(a, es14.4e3)') ' Total parallel current density of fit/analytical function: ', sum(current(1:nspec))
    write(*,'(a)')         '-=-=-=-=-=-=-=-='
    
    
    end subroutine output_fit
    
    
    
    
    subroutine determine_JT(is,n_params,nJT,JT,params,iperp,upper_limit,ipparbar_lower)
    	!! This subroutine calculates the transposed Jacobian matrix of the fit function with respect to the
    	!! fit parameter array.
    	use alps_var, only : fit_type, n_fits, pp, ms, vA, perp_correction
    	use alps_var, only : gamma_rel,pparbar_rel,nspec,relativistic
    	implicit none
    
    	integer, intent(in) :: is
    	!! Index of species for which [[determine_JT]] is executed.
    
    	integer, intent(in) :: n_params
    	!! Total number of fit parameters for a given species.
    
    	integer, intent(in) :: nJT
    	!! First dimension of matrix JT.
    
    	integer, intent(in) :: upper_limit
    	!! Upper limit of iperp space (relativistic and non-relativistic).
    
    	double precision, intent(out) :: JT(nJT,0:upper_limit)
    	!! Transposed Jacobian matrix of the fit function.
    
    	double precision, intent(in) :: params(n_params)
    	!! Array of fit parameters.
    
    	integer, intent(in) :: iperp
    	!! Index of perpendicular momentum at which JT is evaluated.
    
    	integer, intent(in) :: ipparbar_lower
    	!! Lower index of parallel momentum (relativistic).
    
    	integer :: ifit
    	!! Index for the fits that add up to the total fit for a given species.
    
    	integer :: par_ind
    	!! Parameter index to loop over fits.
    
    	integer :: ipar
    	!! Index of parallel momentum.
    
    	integer :: is_rel
    	!! Index for relativistic species (if any).
    
    	integer :: sproc_rel
    	!! Local process index for relativistic calculation.
    
    	integer :: is_run
    	!! Index for relativistic species (if any).
    
    	integer :: JT_ind
    	!! Index running over [[JT]]
    
    	double precision :: ppar_val
    	!! Parallel momentum.
    
    	double precision :: pperp_val
    	!! Perpendicular momentum.
    
    	double precision :: sqrtpart
    	!! Square-root part of Juettner distribution.
    
    	double precision :: expterm
    	!! Exponential part of the Maxwellian distribution.
    
    	double precision :: kappapart
    	!! Kappa part of the kappa distribution.
    
    	if (relativistic(is)) then
    		 ! Determine your sproc_rel:
    		 is_rel=0
    		 do is_run=1,nspec
    				if (relativistic(is_run)) then
    					is_rel=is_rel+1
    					if (is_run.EQ.is) sproc_rel=is_rel
    				endif
    		 enddo
    	endif
    
    	do ipar=0,upper_limit
    
    		if (relativistic(is)) then
    			pperp_val=gamma_rel(sproc_rel,iperp,ipar+ipparbar_lower)
    			ppar_val=pparbar_rel(sproc_rel,iperp,ipar+ipparbar_lower)
    		else
    			pperp_val=pp(is,iperp,ipar,1)
    			ppar_val=pp(is,iperp,ipar,2)
    		endif
    
    
    		par_ind=0
    		JT_ind=0
    
    		do ifit=1,n_fits(is)
    
    			if (fit_type(is,ifit).EQ.1) then	! Maxwell
    				expterm=exp(-params(ifit+par_ind+1)*(ppar_val-params(ifit+par_ind+2))**2 &
    							-perp_correction(is,ifit)*pperp_val**2)
    
    				JT(ifit+JT_ind+0,ipar)=expterm
    
    				JT(ifit+JT_ind+1,ipar)=-((ppar_val-params(ifit+par_ind+2))**2)*params(ifit+par_ind+0)*expterm
    
    				JT(ifit+JT_ind+2,ipar)=2.d0*params(ifit+par_ind+1)*(ppar_val-params(ifit+par_ind+2))*&
    							params(ifit+par_ind+0)*expterm
    
    				par_ind=par_ind+2
    				JT_ind=JT_ind+2
    			endif
    
    			if (fit_type(is,ifit).EQ.2) then	! kappa
    				kappapart=1.d0+params(ifit+par_ind+1)*(ppar_val-params(ifit+par_ind+2))**2 &
    						+ perp_correction(is,ifit)*params(ifit+par_ind+4)*pperp_val**2
    
    				JT(ifit+JT_ind+0,ipar)=kappapart**params(ifit+par_ind+3)
    
    				JT(ifit+JT_ind+1,ipar)=params(ifit+par_ind+0)*params(ifit+par_ind+3)*kappapart**(params(ifit+par_ind+3)-1.d0)*&
    							(ppar_val-params(ifit+par_ind+2))**2
    
    				JT(ifit+JT_ind+2,ipar)=params(ifit+par_ind+0)*params(ifit+par_ind+3)*kappapart**(params(ifit+par_ind+3)-1.d0)*&
    							2.d0*params(ifit+par_ind+1)*(params(ifit+par_ind+2)-ppar_val)
    
    				JT(ifit+JT_ind+3,ipar)=log(kappapart)*params(ifit+par_ind+0)*kappapart**params(ifit+par_ind+3)
    
    				if (iperp.EQ.0) then
    					JT_ind=JT_ind+3
    				else
    					JT(ifit+JT_ind+4,ipar)=params(ifit+par_ind+0)*params(ifit+par_ind+3)*&
    									kappapart**(params(ifit+par_ind+3)-1.d0) *perp_correction(is,ifit)*pperp_val**2
    
    					JT_ind=JT_ind+4
    				endif
    
    				par_ind=par_ind+4
    
    			endif
    
    			if (fit_type(is,ifit).EQ.3) then	! Juettner with pperp and ppar
    				sqrtpart=sqrt(1.d0+(pperp_val**2+(ppar_val-params(ifit+par_ind+2))**2)*vA*vA/(ms(is)*ms(is)))
    
    				JT(ifit+JT_ind+0,ipar)=exp(-params(ifit+par_ind+1)*sqrtpart)
    
    				JT(ifit+JT_ind+1,ipar)=-params(ifit+par_ind+0)*exp(-params(ifit+par_ind+1)*sqrtpart)*sqrtpart
    
    				JT(ifit+JT_ind+2,ipar)=params(ifit+par_ind+0)*exp(-params(ifit+par_ind+1)*sqrtpart)*&
    							(params(ifit+par_ind+1)/sqrtpart)*(ppar_val-params(ifit+par_ind+2))*vA*vA/(ms(is)*ms(is))
    
    				par_ind=par_ind+2
    				JT_ind=JT_ind+2
    			endif
    
    			if (fit_type(is,ifit).EQ.4) then ! Juettner with gamma and pparbar, constant in pparbar
    				JT(ifit+JT_ind+0,ipar)=exp(-perp_correction(is,ifit)*pperp_val)
    
    				par_ind=par_ind+0
    				JT_ind=JT_ind+0
    			endif
    
    			if (fit_type(is,ifit).EQ.5) then ! Juettner with gamma and pparbar with pparbar-dependence
    				expterm=exp(-params(ifit+par_ind+1)*(ppar_val-params(ifit+par_ind+2))**2)* &
    				exp(-perp_correction(is,ifit)*pperp_val)
    
    				JT(ifit+JT_ind+0,ipar)=expterm
    
    				JT(ifit+JT_ind+1,ipar)=-params(ifit+par_ind+0)*expterm*(ppar_val-params(ifit+par_ind+2))**2
    
    				JT(ifit+JT_ind+2,ipar)=2.d0*params(ifit+par_ind+0)*(ppar_val-params(ifit+par_ind+2))*&
    								params(ifit+par_ind+1)*expterm
    
    				par_ind=par_ind+2
    				JT_ind=JT_ind+2
    			endif
    
    
    			if (fit_type(is,ifit).EQ.6) then	! bi-Moyal
    			expterm=exp(0.5d0*(params(ifit+par_ind+3)*perp_correction(is,ifit)*pperp_val**2 + &
    					params(ifit+par_ind+1)*(ppar_val-params(ifit+par_ind+2))**2 - &
    					exp(params(ifit+par_ind+3)*perp_correction(is,ifit)*pperp_val**2 + &
    					params(ifit+par_ind+1)*(ppar_val-params(ifit+par_ind+2))**2) ))
    
    				JT(ifit+JT_ind+0,ipar)=expterm
    
    				JT(ifit+JT_ind+1,ipar)=params(ifit+par_ind+0)*expterm*0.5d0*(ppar_val-params(ifit+par_ind+2))**2 * &
    							(1.d0 - exp(params(ifit+par_ind+3)*perp_correction(is,ifit)*pperp_val**2 + &
    							params(ifit+par_ind+1) * (ppar_val-params(ifit+par_ind+2))**2) )
    
    				JT(ifit+JT_ind+2,ipar)=params(ifit+par_ind+0)*expterm*params(ifit+par_ind+1)*&
    							(params(ifit+par_ind+2)-ppar_val)* &
    							(1.d0-exp(params(ifit+par_ind+3)*perp_correction(is,ifit)*pperp_val**2 + &
    				 			params(ifit+par_ind+1) * (ppar_val-params(ifit+par_ind+2))**2) )
    
    			 if (iperp.EQ.0) then
    						JT_ind=JT_ind+2
    					else
    				 		JT(ifit+JT_ind+3,ipar)=params(ifit+par_ind+0)*expterm*0.5d0*perp_correction(is,ifit)*pperp_val**2 * &
     								(1.d0 - exp(params(ifit+par_ind+3)*perp_correction(is,ifit)*pperp_val**2 + &
    									params(ifit+par_ind+1) * (ppar_val-params(ifit+par_ind+2))**2)  )
    
    					JT_ind=JT_ind+3
    				endif
    
    				par_ind=par_ind+3
    			endif
    
    		enddo
    	enddo
    
    end subroutine
    
    
    
    
    
    subroutine LM_nonlinear_fit(is,g,n_params,nJT,params,param_mask,iperp,npar,ipparbar_lower,quality)
    	!! This subroutine processes the nonlinear Levenberg-Marquart algorithm and returns
    	!! the one-dimensional array params at a given iperp.
    	!! The variable quality is the sum of the squares of all residuals.
    	use alps_var, only : lambda_initial_fit, pp, lambdafac_fit, logfit
    	use alps_var, only : epsilon_fit, maxsteps_fit
    	use alps_var, only : gamma_rel,pparbar_rel,relativistic,nspec
    	use alps_io, only : alps_error
    	use mpi
    	implicit none
    
    	integer, intent(in) :: is
    	!! Index of species for which [[LM_nonlinear_fit]] is executed.
    
    	double precision, intent(in) :: g(0:npar)
    	!! Array of function to be fitted.
    
    	integer, intent(in) :: n_params
    	!! Total number of fit parameters for a given species.
    
    	integer, intent(in) :: nJT
    	!! First dimension of matrix JT (see [[determine_JT(subroutine)]]).
    
    	double precision, intent(inout) :: params(n_params)
    	!! Array of fit parameters.
    
    	logical, intent(in) :: param_mask(n_params)
    	!! Bit mask for required fit parameters.
    
    	integer, intent(in) :: iperp
    	!! Index of perpendicular momentum.
    
    	integer, intent(in) :: npar
    	!! Number of steps in parallel momentum
    
    	integer, intent(in) :: ipparbar_lower
    	!! Lower index of parallel momentum (relativistic).
    
    	double precision, intent(out) :: quality
    	!! Quality of the individual fit result.
    
    	logical :: converged
    	!! Check whether fit has converged.
    
    	integer :: ipar
    	!! Index running over parallel momentum.
    
    	integer :: k
    	!! Index running over entries of [[JT]].
    
    	integer :: counter
    	!! Count of fit iterations.
    
    	integer :: is_rel
    	!! Index for relativistic species (if any).
    
    	integer :: is_run
    	!! Index for relativistic species (if any).
    
    	integer :: sproc_rel
    	!! Local process index for relativistic calculation.
    
    	integer :: l
    	!! Index running over the delta's in the L-M fit.
    
    	integer :: ipiv(nJT)
    	!! Integer array required for matrix inversion.
    
    	integer :: info
    	!! Integer required for matrix inversion.
    
    	double precision :: LSQ
    	!! Least squares for L-M fit.
    
    	double precision :: LSQnew
    	!! Next iteration of least squares for L-M fit.
    
    	double precision :: lambda_fit
    	!! Lambda in L-M fit.
    
    	double precision :: pperp_val
    	!! Perpendicular momentum.
    
    	double precision :: ppar_val
    	!! Parallel momentum.
    
    	double precision :: residuals(0:npar)
    	!! Array of residuals.
    
    	double precision :: deltaparam_fit(nJT)
    	!! delta's in the L-M fit.
    
    	double precision :: JTJ(nJT,nJT)
    	!! Matrix product of JT and J.
    
    	double precision :: JT(nJT,0:npar)
    	!! Transposed Jacobian matrix of the fit function.
    
    	double precision :: diagmat(nJT,nJT)
    	!! Diagonal matrix of JT.
    
    	double precision :: Amat(nJT,nJT)
    	!! Matrix to be inverted.
    
    	double precision :: work_array(nJT)
    	!! Work array for matrix inversion.
    
    	converged=.FALSE.
    	counter=0
    	lambda_fit=lambda_initial_fit
    
    	if ((1+npar).LT.n_params) call alps_error(2)
    
    	if (relativistic(is)) then
    		! Determine your sproc_rel:
    		 is_rel=0
    		 do is_run=1,nspec
    				if (relativistic(is_run)) then
    					is_rel=is_rel+1
    					if (is_run.EQ.is) sproc_rel=is_rel
    				endif
    		 enddo
    	endif
    
    	do while(.NOT.converged)
    	counter=counter+1
    
    	LSQ=0.d0
    
    	! Determine the transposed Jacobian and the residuals:
    
    	call determine_JT(is,n_params,nJT,JT,params,iperp,npar,ipparbar_lower)
    
    
    
    	do ipar=0,npar
    
    		if (relativistic(is)) then
    			pperp_val=gamma_rel(sproc_rel,iperp,ipar+ipparbar_lower)
    			ppar_val=pparbar_rel(sproc_rel,iperp,ipar+ipparbar_lower)
    		else
    			pperp_val=pp(is,iperp,ipar,1)
    			ppar_val=pp(is,iperp,ipar,2)
    		endif
    
    
    
    		if (logfit(is)) then
    
    			do k=1,nJT
    					JT(k,ipar)=JT(k,ipar)/fit_function(is,n_params,params,pperp_val,cmplx(ppar_val,0.d0,kind(1.d0)))
    			enddo
    
    			residuals(ipar)=g(ipar)-log(real(fit_function(is,n_params,params,pperp_val,cmplx(ppar_val,0.d0,kind(1.d0)))))
    		else
    			residuals(ipar)=g(ipar)-real(fit_function(is,n_params,params,pperp_val,cmplx(ppar_val,0.d0,kind(1.d0))))
    		endif
    
    		! Least squares:
    		LSQ=LSQ+residuals(ipar)*residuals(ipar)
    	enddo
    
    
    	JTJ=matmul(JT,transpose(JT))
    
    
    	diagmat=0.d0
    	do k=1,nJT
    		diagmat(k,k)=JTJ(k,k)
    	enddo
    
    
    	Amat=JTJ+lambda_fit*diagmat
    
    	! The following routine is from LAPACK to invert the matrix:
    
    	call dgetrf(nJT,nJT,Amat,nJT,ipiv,info)
    	if (info.NE.0) stop "Fit matrix is numerically singular."
    
    	call dgetri(nJT,Amat,nJT,ipiv,work_array,nJT,info)
    	if (info.NE.0) stop "Fit matrix inversion failed."
    
    	! Now Amat is the inverse of JTJ+lambda_fit*diagmat
    
    	deltaparam_fit=matmul(Amat,matmul(JT,residuals))
    
    	l=0
    	do k=1,n_params
    		if (param_mask(k)) then
    			l=l+1
    			params(k)=params(k)+deltaparam_fit(l)
    		endif
    	enddo
    
    	! With the new param_fit, what is the new mean square error:
    	LSQnew=0.d0
    	do ipar=0,npar
    
    		if (relativistic(is)) then
    			pperp_val=gamma_rel(sproc_rel,iperp,ipar+ipparbar_lower)
    			ppar_val=pparbar_rel(sproc_rel,iperp,ipar+ipparbar_lower)
    		else
    			pperp_val=pp(is,iperp,ipar,1)
    			ppar_val=pp(is,iperp,ipar,2)
    		endif
    
    
    		if (logfit(is)) then
    			residuals(ipar)=g(ipar)-log(real(fit_function(is,n_params,params,pperp_val,cmplx(ppar_val,0.d0,kind(1.d0)))))
    		else
    			residuals(ipar)=g(ipar)-real(fit_function(is,n_params,params,pperp_val,cmplx(ppar_val,0.d0,kind(1.d0))))
    		endif
    
    		! Least squares:
    		LSQnew=LSQnew+residuals(ipar)*residuals(ipar)
    	enddo
    
    
    	if (LSQnew.GT.LSQ) then
    
    		l=0
    		do k=1,n_params
    			if (param_mask(k)) then
    				l=l+1
    				params(k)=params(k)-deltaparam_fit(l)
    			endif
    		enddo
    
    		lambda_fit=lambda_fit*lambdafac_fit
    	else
    		lambda_fit=lambda_fit/lambdafac_fit
    	endif
    
    
    	! Check if it converged (we can add further break criteria):
    	if (abs(LSQnew-LSQ).LT.epsilon_fit) converged=.TRUE.
    	if (counter.EQ.maxsteps_fit) converged=.TRUE.
    	enddo
    
    	quality=LSQ
    
    end subroutine LM_nonlinear_fit
    
      integer function factorial(n)
        implicit none
        integer, intent(in) :: n
        integer :: i, Ans
    
        Ans=1
        do i=1,n
           Ans=Ans*i
        enddo
        Factorial=Ans
      end function factorial
    
    end module alps_analyt
    

    back to top

    Software Heritage — Copyright (C) 2015–2026, The Software Heritage developers. License: GNU AGPLv3+.
    The source code of Software Heritage itself is available on our development forge.
    The source code files archived by Software Heritage are available under their own copyright and licenses.
    Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API