From 33846514eb92a021e4a6cf19f86c358dc3b5506b Mon Sep 17 00:00:00 2001 From: Christophe Pinte Date: Thu, 10 Nov 2022 14:58:40 +1100 Subject: [PATCH 1/4] Adding utilities to reconstruct moments --- src/fsolve.f90 | 1767 ++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.f90 | 70 +- 2 files changed, 1831 insertions(+), 6 deletions(-) create mode 100644 src/fsolve.f90 diff --git a/src/fsolve.f90 b/src/fsolve.f90 new file mode 100644 index 000000000..77b1d79a3 --- /dev/null +++ b/src/fsolve.f90 @@ -0,0 +1,1767 @@ +subroutine dogleg ( n, r, lr, diag, qtb, delta, x ) + +!*****************************************************************************80 +! +!! dogleg() finds the minimizing combination of Gauss-Newton and gradient steps. +! +! Discussion: +! +! Given an M by N matrix A, an N by N nonsingular diagonal +! matrix D, an M-vector B, and a positive number DELTA, the +! problem is to determine the convex combination X of the +! Gauss-Newton and scaled gradient directions that minimizes +! (A*X - B) in the least squares sense, subject to the +! restriction that the euclidean norm of D*X be at most DELTA. +! +! This function completes the solution of the problem +! if it is provided with the necessary information from the +! QR factorization of A. That is, if A = Q*R, where Q has +! orthogonal columns and R is an upper triangular matrix, +! then DOGLEG expects the full upper triangle of R and +! the first N components of Q'*B. +! +! Licensing: +! +! This code is distributed under the GNU LGPL license. +! +! Modified: +! +! 06 April 2010 +! +! Author: +! +! Original FORTRAN77 version by Jorge More, Burton Garbow, Kenneth Hillstrom. +! FORTRAN90 version by John Burkardt. +! +! Reference: +! +! Jorge More, Burton Garbow, Kenneth Hillstrom, +! User Guide for MINPACK-1, +! Technical Report ANL-80-74, +! Argonne National Laboratory, 1980. +! +! Parameters: +! +! Input, integer N, the order of the matrix R. +! +! Input, real ( kind = rk ) R(LR), the upper triangular matrix R stored +! by rows. +! +! Input, integer LR, the size of the R array, which must be +! no less than (N*(N+1))/2. +! +! Input, real ( kind = rk ) DIAG(N), the diagonal elements of the matrix D. +! +! Input, real ( kind = rk ) QTB(N), the first N elements of the vector Q'* B. +! +! Input, real ( kind = rk ) DELTA, is a positive upper bound on the +! euclidean norm of D*X(1:N). +! +! Output, real ( kind = rk ) X(N), the desired convex combination of the +! Gauss-Newton direction and the scaled gradient direction. +! + implicit none + + integer, parameter :: rk = kind ( 1.0D+00 ) + + integer lr + integer n + + real ( kind = rk ) alpha + real ( kind = rk ) bnorm + real ( kind = rk ) delta + real ( kind = rk ) diag(n) + real ( kind = rk ) enorm + real ( kind = rk ) epsmch + real ( kind = rk ) gnorm + integer i + integer j + integer jj + integer k + integer l + real ( kind = rk ) qnorm + real ( kind = rk ) qtb(n) + real ( kind = rk ) r(lr) + real ( kind = rk ) sgnorm + real ( kind = rk ) sum2 + real ( kind = rk ) temp + real ( kind = rk ) wa1(n) + real ( kind = rk ) wa2(n) + real ( kind = rk ) x(n) + + epsmch = epsilon ( epsmch ) +! +! Calculate the Gauss-Newton direction. +! + jj = ( n * ( n + 1 ) ) / 2 + 1 + + do k = 1, n + + j = n - k + 1 + jj = jj - k + l = jj + 1 + sum2 = 0.0D+00 + + do i = j + 1, n + sum2 = sum2 + r(l) * x(i) + l = l + 1 + end do + + temp = r(jj) + + if ( temp == 0.0D+00 ) then + + l = j + do i = 1, j + temp = max ( temp, abs ( r(l)) ) + l = l + n - i + end do + + if ( temp == 0.0D+00 ) then + temp = epsmch + else + temp = epsmch * temp + end if + + end if + + x(j) = ( qtb(j) - sum2 ) / temp + + end do +! +! Test whether the Gauss-Newton direction is acceptable. +! + wa1(1:n) = 0.0D+00 + wa2(1:n) = diag(1:n) * x(1:n) + qnorm = enorm ( n, wa2 ) + + if ( qnorm <= delta ) then + return + end if +! +! The Gauss-Newton direction is not acceptable. +! Calculate the scaled gradient direction. +! + l = 1 + do j = 1, n + temp = qtb(j) + do i = j, n + wa1(i) = wa1(i) + r(l) * temp + l = l + 1 + end do + wa1(j) = wa1(j) / diag(j) + end do +! +! Calculate the norm of the scaled gradient. +! Test for the special case in which the scaled gradient is zero. +! + gnorm = enorm ( n, wa1 ) + sgnorm = 0.0D+00 + alpha = delta / qnorm + + if ( gnorm /= 0.0D+00 ) then +! +! Calculate the point along the scaled gradient which minimizes the quadratic. +! + wa1(1:n) = ( wa1(1:n) / gnorm ) / diag(1:n) + + l = 1 + do j = 1, n + sum2 = 0.0D+00 + do i = j, n + sum2 = sum2 + r(l) * wa1(i) + l = l + 1 + end do + wa2(j) = sum2 + end do + + temp = enorm ( n, wa2 ) + sgnorm = ( gnorm / temp ) / temp +! +! Test whether the scaled gradient direction is acceptable. +! + alpha = 0.0D+00 +! +! The scaled gradient direction is not acceptable. +! Calculate the point along the dogleg at which the quadratic is minimized. +! + if ( sgnorm < delta ) then + + bnorm = enorm ( n, qtb ) + temp = ( bnorm / gnorm ) * ( bnorm / qnorm ) * ( sgnorm / delta ) + temp = temp - ( delta / qnorm ) * ( sgnorm / delta) ** 2 & + + sqrt ( ( temp - ( delta / qnorm ) ) ** 2 & + + ( 1.0D+00 - ( delta / qnorm ) ** 2 ) & + * ( 1.0D+00 - ( sgnorm / delta ) ** 2 ) ) + + alpha = ( ( delta / qnorm ) * ( 1.0D+00 - ( sgnorm / delta ) ** 2 ) ) & + / temp + + end if + + end if +! +! Form appropriate convex combination of the Gauss-Newton +! direction and the scaled gradient direction. +! + temp = ( 1.0D+00 - alpha ) * min ( sgnorm, delta ) + + x(1:n) = temp * wa1(1:n) + alpha * x(1:n) + + return +end +function enorm ( n, x ) + +!*****************************************************************************80 +! +!! enorm() computes the Euclidean norm of a vector. +! +! Discussion: +! +! The Euclidean norm is computed by accumulating the sum of +! squares in three different sums. The sums of squares for the +! small and large components are scaled so that no overflows +! occur. Non-destructive underflows are permitted. Underflows +! and overflows do not occur in the computation of the unscaled +! sum of squares for the intermediate components. +! +! The definitions of small, intermediate and large components +! depend on two constants, RDWARF and RGIANT. The main +! restrictions on these constants are that RDWARF^2 not +! underflow and RGIANT^2 not overflow. +! +! Licensing: +! +! This code is distributed under the GNU LGPL license. +! +! Modified: +! +! 06 April 2010 +! +! Author: +! +! Original FORTRAN77 version by Jorge More, Burton Garbow, Kenneth Hillstrom. +! FORTRAN90 version by John Burkardt. +! +! Reference: +! +! Jorge More, Burton Garbow, Kenneth Hillstrom, +! User Guide for MINPACK-1 +! Argonne National Laboratory, +! Argonne, Illinois. +! +! Input: +! +! integer N, is the length of the vector. +! +! real ( kind = rk ) X(N), the vector whose norm is desired. +! +! Output: +! +! real ( kind = rk ) ENORM, the Euclidean norm of the vector. +! + implicit none + + integer, parameter :: rk = kind ( 1.0D+00 ) + + integer n + + real ( kind = rk ) agiant + real ( kind = rk ) enorm + integer i + real ( kind = rk ) rdwarf + real ( kind = rk ) rgiant + real ( kind = rk ) s1 + real ( kind = rk ) s2 + real ( kind = rk ) s3 + real ( kind = rk ) x(n) + real ( kind = rk ) xabs + real ( kind = rk ) x1max + real ( kind = rk ) x3max + + rdwarf = sqrt ( tiny ( rdwarf ) ) + rgiant = sqrt ( huge ( rgiant ) ) + + s1 = 0.0D+00 + s2 = 0.0D+00 + s3 = 0.0D+00 + x1max = 0.0D+00 + x3max = 0.0D+00 + agiant = rgiant / real ( n, kind = rk ) + + do i = 1, n + + xabs = abs ( x(i) ) + + if ( xabs <= rdwarf ) then + + if ( x3max < xabs ) then + s3 = 1.0D+00 + s3 * ( x3max / xabs ) ** 2 + x3max = xabs + else if ( xabs /= 0.0D+00 ) then + s3 = s3 + ( xabs / x3max ) ** 2 + end if + + else if ( agiant <= xabs ) then + + if ( x1max < xabs ) then + s1 = 1.0D+00 + s1 * ( x1max / xabs ) ** 2 + x1max = xabs + else + s1 = s1 + ( xabs / x1max ) ** 2 + end if + + else + + s2 = s2 + xabs ** 2 + + end if + + end do +! +! Calculation of norm. +! + if ( s1 /= 0.0D+00 ) then + + enorm = x1max * sqrt ( s1 + ( s2 / x1max ) / x1max ) + + else if ( s2 /= 0.0D+00 ) then + + if ( x3max <= s2 ) then + enorm = sqrt ( s2 * ( 1.0D+00 + ( x3max / s2 ) * ( x3max * s3 ) ) ) + else + enorm = sqrt ( x3max * ( ( s2 / x3max ) + ( x3max * s3 ) ) ) + end if + + else + + enorm = x3max * sqrt ( s3 ) + + end if + + return +end +subroutine fdjac1 ( fcn, n, x, fvec, fjac, ldfjac, ml, mu, epsfcn ) + +!*****************************************************************************80 +! +!! fdjac1() estimates a jacobian matrix using forward differences. +! +! Discussion: +! +! This function computes a forward-difference approximation +! to the N by N jacobian matrix associated with a specified +! problem of N functions in N variables. If the jacobian has +! a banded form, then function evaluations are saved by only +! approximating the nonzero terms. +! +! Licensing: +! +! This code is distributed under the GNU LGPL license. +! +! Modified: +! +! 06 April 2010 +! +! Author: +! +! Original FORTRAN77 version by Jorge More, Burton Garbow, Kenneth Hillstrom. +! FORTRAN90 version by John Burkardt. +! +! Reference: +! +! Jorge More, Burton Garbow, Kenneth Hillstrom, +! User Guide for MINPACK-1, +! Technical Report ANL-80-74, +! Argonne National Laboratory, 1980. +! +! Parameters: +! +! Input, external FCN, the name of the user-supplied subroutine which +! calculates the functions. The routine should have the form: +! subroutine fcn ( n, x, fvec ) +! integer n +! real ( kind = rk ) fvec(n) +! real ( kind = rk ) x(n) +! +! Input, integer N, the number of functions and variables. +! +! Input, real ( kind = rk ) X(N), the point where the jacobian is evaluated. +! +! Input, real ( kind = rk ) FVEC(N), the functions evaluated at X. +! +! Output, real ( kind = rk ) FJAC(LDFJAC,N), the N by N approximate +! jacobian matrix. +! +! Input, integer LDFJAC, the leading dimension of FJAC, which +! must not be less than N. +! +! Input, integer ML, MU, specify the number of subdiagonals and +! superdiagonals within the band of the jacobian matrix. If the +! jacobian is not banded, set ML and MU to N-1. +! +! Input, real ( kind = rk ) EPSFCN, is used in determining a suitable step +! length for the forward-difference approximation. This approximation +! assumes that the relative errors in the functions are of the order of +! EPSFCN. If EPSFCN is less than the machine precision, it is assumed that +! the relative errors in the functions are of the order of the machine +! precision. +! + implicit none + + integer, parameter :: rk = kind ( 1.0D+00 ) + + integer ldfjac + integer n + + real ( kind = rk ) eps + real ( kind = rk ) epsfcn + real ( kind = rk ) epsmch + external fcn + real ( kind = rk ) fjac(ldfjac,n) + real ( kind = rk ) fvec(n) + real ( kind = rk ) h + integer i + integer j + integer k + integer ml + integer msum + integer mu + real ( kind = rk ) temp + real ( kind = rk ) wa1(n) + real ( kind = rk ) wa2(n) + real ( kind = rk ) x(n) + + epsmch = epsilon ( epsmch ) + + eps = sqrt ( max ( epsfcn, epsmch ) ) + msum = ml + mu + 1 +! +! Computation of dense approximate jacobian. +! + if ( n <= msum ) then + + do j = 1, n + + temp = x(j) + h = eps * abs ( temp ) + if ( h == 0.0D+00 ) then + h = eps + end if + + x(j) = temp + h + call fcn ( n, x, wa1 ) + + x(j) = temp + fjac(1:n,j) = ( wa1(1:n) - fvec(1:n) ) / h + + end do + + else +! +! Computation of banded approximate jacobian. +! + do k = 1, msum + + do j = k, n, msum + wa2(j) = x(j) + h = eps * abs ( wa2(j) ) + if ( h == 0.0D+00 ) then + h = eps + end if + x(j) = wa2(j) + h + end do + + call fcn ( n, x, wa1 ) + + do j = k, n, msum + + x(j) = wa2(j) + + h = eps * abs ( wa2(j) ) + if ( h == 0.0D+00 ) then + h = eps + end if + + fjac(1:n,j) = 0.0D+00 + + do i = 1, n + if ( j - mu <= i .and. i <= j + ml ) then + fjac(i,j) = ( wa1(i) - fvec(i) ) / h + end if + end do + + end do + + end do + + end if + + return +end +subroutine fsolve ( fcn, n, x, fvec, tol, info ) + +!*****************************************************************************80 +! +!! fsolve() seeks a zero of N nonlinear equations in N variables. +! +! Discussion: +! +! fsolve() finds a zero of a system of N nonlinear functions in N variables +! by a modification of the Powell hybrid method. This is done by using the +! more general nonlinear equation solver HYBRD. The user provides a +! subroutine which calculates the functions. +! +! The jacobian is calculated by a forward-difference approximation. +! +! Licensing: +! +! This code is distributed under the GNU LGPL license. +! +! Modified: +! +! 07 April 2021 +! +! Author: +! +! Original FORTRAN77 version by Jorge More, Burton Garbow, Kenneth Hillstrom. +! FORTRAN90 version by John Burkardt. +! +! Reference: +! +! Jorge More, Burton Garbow, Kenneth Hillstrom, +! User Guide for MINPACK-1, +! Technical Report ANL-80-74, +! Argonne National Laboratory, 1980. +! +! Input: +! +! external FCN, the user subroutine which calculates the functions. +! The routine should have the form: +! subroutine fcn ( n, x, fvec ) +! integer n +! real ( kind = rk ) fvec(n) +! real ( kind = rk ) x(n) +! +! integer N, the number of functions and variables. +! +! real ( kind = rk ) X(N), an initial estimate of the solution vector. +! +! real ( kind = rk ) TOL. Satisfactory termination occurs when the algorithm +! estimates that the relative error between X and the solution is at +! most TOL. TOL should be nonnegative. +! +! Output: +! +! real ( kind = rk ) X(N), the estimate of the solution vector. +! +! real ( kind = rk ) FVEC(N), the functions evaluated at the output X. +! +! integer INFO, error flag. +! 0, improper input parameters. +! 1, algorithm estimates that the relative error between X and the +! solution is at most TOL. +! 2, number of calls to FCN has reached or exceeded 200*(N+1). +! 3, TOL is too small. No further improvement in the approximate +! solution X is possible. +! 4, the iteration is not making good progress. +! + implicit none + + integer, parameter :: rk = kind ( 1.0D+00 ) + + integer n + + real ( kind = rk ) diag(n) + real ( kind = rk ) epsfcn + real ( kind = rk ) factor + external fcn + real ( kind = rk ) fjac(n,n) + real ( kind = rk ) fvec(n) + integer info + integer ldfjac + integer lr + integer maxfev + integer ml + integer mode + integer mu + integer nfev + real ( kind = rk ) qtf(n) + real ( kind = rk ) r((n*(n+1))/2) + real ( kind = rk ) tol + real ( kind = rk ) x(n) + real ( kind = rk ) xtol + + if ( n <= 0 ) then + info = 0 + return + end if + + if ( tol < 0.0D+00 ) then + info = 0 + return + end if + + xtol = tol + maxfev = 200 * ( n + 1 ) + ml = n - 1 + mu = n - 1 + epsfcn = 0.0D+00 + diag(1:n) = 1.0D+00 + mode = 2 + factor = 100.0D+00 + info = 0 + nfev = 0 + fjac(1:n,1:n) = 0.0D+00 + ldfjac = n + r(1:(n*(n+1))/2) = 0.0D+00 + lr = ( n * ( n + 1 ) ) / 2 + qtf(1:n) = 0.0D+00 + + call hybrd ( fcn, n, x, fvec, xtol, maxfev, ml, mu, epsfcn, diag, mode, & + factor, info, nfev, fjac, ldfjac, r, lr, qtf ) + + if ( info == 5 ) then + info = 4 + end if + + return +end +subroutine hybrd ( fcn, n, x, fvec, xtol, maxfev, ml, mu, epsfcn, diag, mode, & + factor, info, nfev, fjac, ldfjac, r, lr, qtf ) + +!*****************************************************************************80 +! +!! hybrd() seeks a zero of N nonlinear equations in N variables. +! +! Discussion: +! +! HYBRD finds a zero of a system of N nonlinear functions in N variables +! by a modification of the Powell hybrid method. The user must provide a +! subroutine which calculates the functions. +! +! The jacobian is then calculated by a forward-difference approximation. +! +! Licensing: +! +! This code is distributed under the GNU LGPL license. +! +! Modified: +! +! 06 April 2010 +! +! Author: +! +! Original FORTRAN77 version by Jorge More, Burton Garbow, Kenneth Hillstrom. +! FORTRAN90 version by John Burkardt. +! +! Reference: +! +! Jorge More, Burton Garbow, Kenneth Hillstrom, +! User Guide for MINPACK-1, +! Technical Report ANL-80-74, +! Argonne National Laboratory, 1980. +! +! Parameters: +! +! Input, external FCN, the name of the user-supplied subroutine which +! calculates the functions. The routine should have the form: +! subroutine fcn ( n, x, fvec ) +! integer n +! real ( kind = rk ) fvec(n) +! real ( kind = rk ) x(n) +! +! Input, integer N, the number of functions and variables. +! +! Input/output, real ( kind = rk ) X(N). On input, X must contain an initial +! estimate of the solution vector. On output X contains the final +! estimate of the solution vector. +! +! Output, real ( kind = rk ) FVEC(N), the functions evaluated at the output X. +! +! Input, real ( kind = rk ) XTOL. Termination occurs when the relative error +! between two consecutive iterates is at most XTOL. XTOL should be +! nonnegative. +! +! Input, integer MAXFEV. Termination occurs when the number of +! calls to FCN is at least MAXFEV by the end of an iteration. +! +! Input, integer ML, MU, specify the number of subdiagonals and +! superdiagonals within the band of the jacobian matrix. If the jacobian +! is not banded, set ML and MU to at least n - 1. +! +! Input, real ( kind = rk ) EPSFCN, is used in determining a suitable step +! length for the forward-difference approximation. This approximation +! assumes that the relative errors in the functions are of the order of +! EPSFCN. If EPSFCN is less than the machine precision, it is assumed that +! the relative errors in the functions are of the order of the machine +! precision. +! +! Input/output, real ( kind = rk ) DIAG(N). If MODE = 1, then DIAG is set +! internally. If MODE = 2, then DIAG must contain positive entries that +! serve as multiplicative scale factors for the variables. +! +! Input, integer MODE, scaling option. +! 1, variables will be scaled internally. +! 2, scaling is specified by the input DIAG vector. +! +! Input, real ( kind = rk ) FACTOR, determines the initial step bound. This +! bound is set to the product of FACTOR and the euclidean norm of DIAG*X if +! nonzero, or else to FACTOR itself. In most cases, FACTOR should lie +! in the interval (0.1, 100) with 100 the recommended value. +! +! Output, integer INFO, error flag. +! 0, improper input parameters. +! 1, relative error between two consecutive iterates is at most XTOL. +! 2, number of calls to FCN has reached or exceeded MAXFEV. +! 3, XTOL is too small. No further improvement in the approximate +! solution X is possible. +! 4, iteration is not making good progress, as measured by the improvement +! from the last five jacobian evaluations. +! 5, iteration is not making good progress, as measured by the improvement +! from the last ten iterations. +! +! Output, integer NFEV, the number of calls to FCN. +! +! Output, real ( kind = rk ) FJAC(LDFJAC,N), an N by N array which contains +! the orthogonal matrix Q produced by the QR factorization of the final +! approximate jacobian. +! +! Input, integer LDFJAC, the leading dimension of FJAC. +! LDFJAC must be at least N. +! +! Output, real ( kind = rk ) R(LR), the upper triangular matrix produced by +! the QR factorization of the final approximate jacobian, stored rowwise. +! +! Input, integer LR, the size of the R array, which must be no +! less than (N*(N+1))/2. +! +! Output, real ( kind = rk ) QTF(N), contains the vector Q'*FVEC. +! + implicit none + + integer, parameter :: rk = kind ( 1.0D+00 ) + + integer ldfjac + integer lr + integer n + + real ( kind = rk ) actred + real ( kind = rk ) delta + real ( kind = rk ) diag(n) + real ( kind = rk ) enorm + real ( kind = rk ) epsfcn + real ( kind = rk ) epsmch + real ( kind = rk ) factor + external fcn + real ( kind = rk ) fjac(ldfjac,n) + real ( kind = rk ) fnorm + real ( kind = rk ) fnorm1 + real ( kind = rk ) fvec(n) + integer i + integer info + integer iter + integer iwa(1) + integer j + logical jeval + integer l + integer maxfev + integer ml + integer mode + integer msum + integer mu + integer ncfail + integer nslow1 + integer nslow2 + integer ncsuc + integer nfev + logical pivot + real ( kind = rk ) pnorm + real ( kind = rk ) prered + real ( kind = rk ) qtf(n) + real ( kind = rk ) r(lr) + real ( kind = rk ) ratio + logical sing + real ( kind = rk ) sum2 + real ( kind = rk ) temp + real ( kind = rk ) wa1(n) + real ( kind = rk ) wa2(n) + real ( kind = rk ) wa3(n) + real ( kind = rk ) wa4(n) + real ( kind = rk ) x(n) + real ( kind = rk ) xnorm + real ( kind = rk ) xtol + + epsmch = epsilon ( epsmch ) + + info = 0 + nfev = 0 +! +! Check the input parameters for errors. +! + if ( n <= 0 ) then + return + else if ( xtol < 0.0D+00 ) then + return + else if ( maxfev <= 0 ) then + return + else if ( ml < 0 ) then + return + else if ( mu < 0 ) then + return + else if ( factor <= 0.0D+00 ) then + return + else if ( ldfjac < n ) then + return + else if ( lr < ( n * ( n + 1 ) ) / 2 ) then + return + end if + + if ( mode == 2 ) then + + do j = 1, n + if ( diag(j) <= 0.0D+00 ) then + return + end if + end do + + end if +! +! Evaluate the function at the starting point +! and calculate its norm. +! + call fcn ( n, x, fvec ) + nfev = 1 + + fnorm = enorm ( n, fvec ) +! +! Determine the number of calls to FCN needed to compute the jacobian matrix. +! + msum = min ( ml + mu + 1, n ) +! +! Initialize iteration counter and monitors. +! + iter = 1 + ncsuc = 0 + ncfail = 0 + nslow1 = 0 + nslow2 = 0 +! +! Beginning of the outer loop. +! +30 continue + + jeval = .true. +! +! Calculate the jacobian matrix. +! + call fdjac1 ( fcn, n, x, fvec, fjac, ldfjac, ml, mu, epsfcn ) + + nfev = nfev + msum +! +! Compute the QR factorization of the jacobian. +! + pivot = .false. + call qrfac ( n, n, fjac, ldfjac, pivot, iwa, 1, wa1, wa2 ) +! +! On the first iteration, if MODE is 1, scale according +! to the norms of the columns of the initial jacobian. +! + if ( iter == 1 ) then + + if ( mode /= 2 ) then + + diag(1:n) = wa2(1:n) + do j = 1, n + if ( wa2(j) == 0.0D+00 ) then + diag(j) = 1.0D+00 + end if + end do + + end if +! +! On the first iteration, calculate the norm of the scaled X +! and initialize the step bound DELTA. +! + wa3(1:n) = diag(1:n) * x(1:n) + xnorm = enorm ( n, wa3 ) + delta = factor * xnorm + if ( delta == 0.0D+00 ) then + delta = factor + end if + + end if +! +! Form Q' * FVEC and store in QTF. +! + qtf(1:n) = fvec(1:n) + + do j = 1, n + + if ( fjac(j,j) /= 0.0D+00 ) then + temp = - dot_product ( qtf(j:n), fjac(j:n,j) ) / fjac(j,j) + qtf(j:n) = qtf(j:n) + fjac(j:n,j) * temp + end if + + end do +! +! Copy the triangular factor of the QR factorization into R. +! + sing = .false. + + do j = 1, n + l = j + do i = 1, j - 1 + r(l) = fjac(i,j) + l = l + n - i + end do + r(l) = wa1(j) + if ( wa1(j) == 0.0D+00 ) then + sing = .true. + end if + end do +! +! Accumulate the orthogonal factor in FJAC. +! + call qform ( n, n, fjac, ldfjac ) +! +! Rescale if necessary. +! + if ( mode /= 2 ) then + do j = 1, n + diag(j) = max ( diag(j), wa2(j) ) + end do + end if +! +! Beginning of the inner loop. +! +180 continue +! +! Determine the direction P. +! + call dogleg ( n, r, lr, diag, qtf, delta, wa1 ) +! +! Store the direction P and X + P. +! Calculate the norm of P. +! + wa1(1:n) = - wa1(1:n) + wa2(1:n) = x(1:n) + wa1(1:n) + wa3(1:n) = diag(1:n) * wa1(1:n) + + pnorm = enorm ( n, wa3 ) +! +! On the first iteration, adjust the initial step bound. +! + if ( iter == 1 ) then + delta = min ( delta, pnorm ) + end if +! +! Evaluate the function at X + P and calculate its norm. +! + call fcn ( n, wa2, wa4 ) + nfev = nfev + 1 + fnorm1 = enorm ( n, wa4 ) +! +! Compute the scaled actual reduction. +! + actred = -1.0D+00 + if ( fnorm1 < fnorm ) then + actred = 1.0D+00 - ( fnorm1 / fnorm ) ** 2 + endif +! +! Compute the scaled predicted reduction. +! + l = 1 + do i = 1, n + sum2 = 0.0D+00 + do j = i, n + sum2 = sum2 + r(l) * wa1(j) + l = l + 1 + end do + wa3(i) = qtf(i) + sum2 + end do + + temp = enorm ( n, wa3 ) + prered = 0.0D+00 + if ( temp < fnorm ) then + prered = 1.0D+00 - ( temp / fnorm ) ** 2 + end if +! +! Compute the ratio of the actual to the predicted reduction. +! + ratio = 0.0D+00 + if ( 0.0D+00 < prered ) then + ratio = actred / prered + end if +! +! Update the step bound. +! + if ( ratio < 0.1D+00 ) then + + ncsuc = 0 + ncfail = ncfail + 1 + delta = 0.5D+00 * delta + + else + + ncfail = 0 + ncsuc = ncsuc + 1 + + if ( 0.5D+00 <= ratio .or. 1 < ncsuc ) then + delta = max ( delta, pnorm / 0.5D+00 ) + end if + + if ( abs ( ratio - 1.0D+00 ) <= 0.1D+00 ) then + delta = pnorm / 0.5D+00 + end if + + end if +! +! Test for successful iteration. +! +! Successful iteration. +! Update X, FVEC, and their norms. +! + if ( 0.0001D+00 <= ratio ) then + x(1:n) = wa2(1:n) + wa2(1:n) = diag(1:n) * x(1:n) + fvec(1:n) = wa4(1:n) + xnorm = enorm ( n, wa2 ) + fnorm = fnorm1 + iter = iter + 1 + end if +! +! Determine the progress of the iteration. +! + nslow1 = nslow1 + 1 + if ( 0.001D+00 <= actred ) then + nslow1 = 0 + end if + + if ( jeval ) then + nslow2 = nslow2 + 1 + end if + + if ( 0.1D+00 <= actred ) then + nslow2 = 0 + end if +! +! Test for convergence. +! + if ( delta <= xtol * xnorm .or. fnorm == 0.0D+00 ) then + info = 1 + end if + + if ( info /= 0 ) then + return + end if +! +! Tests for termination and stringent tolerances. +! + if ( maxfev <= nfev ) then + info = 2 + end if + + if ( 0.1D+00 * max ( 0.1D+00 * delta, pnorm ) <= epsmch * xnorm ) then + info = 3 + end if + + if ( nslow2 == 5 ) then + info = 4 + end if + + if ( nslow1 == 10 ) then + info = 5 + end if + + if ( info /= 0 ) then + return + end if +! +! Criterion for recalculating jacobian approximation +! by forward differences. +! + if ( ncfail == 2 ) then + go to 290 + end if +! +! Calculate the rank one modification to the jacobian +! and update QTF if necessary. +! + do j = 1, n + sum2 = dot_product ( wa4(1:n), fjac(1:n,j) ) + wa2(j) = ( sum2 - wa3(j) ) / pnorm + wa1(j) = diag(j) * ( ( diag(j) * wa1(j) ) / pnorm ) + if ( 0.0001D+00 <= ratio ) then + qtf(j) = sum2 + end if + end do +! +! Compute the QR factorization of the updated jacobian. +! + call r1updt ( n, n, r, lr, wa1, wa2, wa3, sing ) + call r1mpyq ( n, n, fjac, ldfjac, wa2, wa3 ) + call r1mpyq ( 1, n, qtf, 1, wa2, wa3 ) +! +! End of the inner loop. +! + jeval = .false. + go to 180 + + 290 continue +! +! End of the outer loop. +! + go to 30 + + return +end +subroutine qform ( m, n, q, ldq ) + +!*****************************************************************************80 +! +!! qform() produces the explicit QR factorization of a matrix. +! +! Discussion: +! +! The QR factorization of a matrix is usually accumulated in implicit +! form, that is, as a series of orthogonal transformations of the +! original matrix. This routine carries out those transformations, +! to explicitly exhibit the factorization constructed by QRFAC. +! +! Licensing: +! +! This code is distributed under the GNU LGPL license. +! +! Modified: +! +! 06 April 2010 +! +! Author: +! +! Original FORTRAN77 version by Jorge More, Burton Garbow, Kenneth Hillstrom. +! FORTRAN90 version by John Burkardt. +! +! Reference: +! +! Jorge More, Burton Garbow, Kenneth Hillstrom, +! User Guide for MINPACK-1, +! Technical Report ANL-80-74, +! Argonne National Laboratory, 1980. +! +! Parameters: +! +! Input, integer M, is a positive integer input variable set +! to the number of rows of A and the order of Q. +! +! Input, integer N, is a positive integer input variable set +! to the number of columns of A. +! +! Input/output, real ( kind = rk ) Q(LDQ,M). Q is an M by M array. +! On input the full lower trapezoid in the first min(M,N) columns of Q +! contains the factored form. +! On output, Q has been accumulated into a square matrix. +! +! Input, integer LDQ, is a positive integer input variable +! not less than M which specifies the leading dimension of the array Q. +! + implicit none + + integer, parameter :: rk = kind ( 1.0D+00 ) + + integer ldq + integer m + integer n + + integer j + integer k + integer l + integer minmn + real ( kind = rk ) q(ldq,m) + real ( kind = rk ) temp + real ( kind = rk ) wa(m) + + minmn = min ( m, n ) + + do j = 2, minmn + q(1:j-1,j) = 0.0D+00 + end do +! +! Initialize remaining columns to those of the identity matrix. +! + q(1:m,n+1:m) = 0.0D+00 + + do j = n + 1, m + q(j,j) = 1.0D+00 + end do +! +! Accumulate Q from its factored form. +! + do l = 1, minmn + + k = minmn - l + 1 + + wa(k:m) = q(k:m,k) + + q(k:m,k) = 0.0D+00 + q(k,k) = 1.0D+00 + + if ( wa(k) /= 0.0D+00 ) then + + do j = k, m + temp = dot_product ( wa(k:m), q(k:m,j) ) / wa(k) + q(k:m,j) = q(k:m,j) - temp * wa(k:m) + end do + + end if + + end do + + return +end +subroutine qrfac ( m, n, a, lda, pivot, ipvt, lipvt, rdiag, acnorm ) + +!*****************************************************************************80 +! +!! qrfac() computes a QR factorization using Householder transformations. +! +! Discussion: +! +! This function uses Householder transformations with optional column +! pivoting to compute a QR factorization of the +! M by N matrix A. That is, QRFAC determines an orthogonal +! matrix Q, a permutation matrix P, and an upper trapezoidal +! matrix R with diagonal elements of nonincreasing magnitude, +! such that A*P = Q*R. +! +! The Householder transformation for column K, K = 1,2,...,min(M,N), +! is of the form +! +! I - ( 1 / U(K) ) * U * U' +! +! where U has zeros in the first K-1 positions. +! +! The form of this transformation and the method of pivoting first +! appeared in the corresponding LINPACK routine. +! +! Licensing: +! +! This code is distributed under the GNU LGPL license. +! +! Modified: +! +! 06 April 2010 +! +! Author: +! +! Original FORTRAN77 version by Jorge More, Burton Garbow, Kenneth Hillstrom. +! FORTRAN90 version by John Burkardt. +! +! Reference: +! +! Jorge More, Burton Garbow, Kenneth Hillstrom, +! User Guide for MINPACK-1, +! Technical Report ANL-80-74, +! Argonne National Laboratory, 1980. +! +! Parameters: +! +! Input, integer M, the number of rows of A. +! +! Input, integer N, the number of columns of A. +! +! Input/output, real ( kind = rk ) A(LDA,N), the M by N array. +! On input, A contains the matrix for which the QR factorization is to +! be computed. On output, the strict upper trapezoidal part of A contains +! the strict upper trapezoidal part of R, and the lower trapezoidal +! part of A contains a factored form of Q, the non-trivial elements of +! the U vectors described above. +! +! Input, integer LDA, the leading dimension of A, which must +! be no less than M. +! +! Input, logical PIVOT, is TRUE if column pivoting is to be carried out. +! +! Output, integer IPVT(LIPVT), defines the permutation matrix P +! such that A*P = Q*R. Column J of P is column IPVT(J) of the identity +! matrix. If PIVOT is false, IPVT is not referenced. +! +! Input, integer LIPVT, the dimension of IPVT, which should +! be N if pivoting is used. +! +! Output, real ( kind = rk ) RDIAG(N), contains the diagonal elements of R. +! +! Output, real ( kind = rk ) ACNORM(N), the norms of the corresponding +! columns of the input matrix A. If this information is not needed, +! then ACNORM can coincide with RDIAG. +! + implicit none + + integer, parameter :: rk = kind ( 1.0D+00 ) + + integer lda + integer lipvt + integer m + integer n + + real ( kind = rk ) a(lda,n) + real ( kind = rk ) acnorm(n) + real ( kind = rk ) ajnorm + real ( kind = rk ) enorm + real ( kind = rk ) epsmch + integer i4_temp + integer ipvt(lipvt) + integer j + integer k + integer kmax + integer minmn + logical pivot + real ( kind = rk ) r8_temp(m) + real ( kind = rk ) rdiag(n) + real ( kind = rk ) temp + real ( kind = rk ) wa(n) + + epsmch = epsilon ( epsmch ) +! +! Compute the initial column norms and initialize several arrays. +! + do j = 1, n + acnorm(j) = enorm ( m, a(1:m,j) ) + end do + + rdiag(1:n) = acnorm(1:n) + wa(1:n) = acnorm(1:n) + + if ( pivot ) then + do j = 1, n + ipvt(j) = j + end do + end if +! +! Reduce A to R with Householder transformations. +! + minmn = min ( m, n ) + + do j = 1, minmn +! +! Bring the column of largest norm into the pivot position. +! + if ( pivot ) then + + kmax = j + + do k = j, n + if ( rdiag(kmax) < rdiag(k) ) then + kmax = k + end if + end do + + if ( kmax /= j ) then + + r8_temp(1:m) = a(1:m,j) + a(1:m,j) = a(1:m,kmax) + a(1:m,kmax) = r8_temp(1:m) + + rdiag(kmax) = rdiag(j) + wa(kmax) = wa(j) + + i4_temp = ipvt(j) + ipvt(j) = ipvt(kmax) + ipvt(kmax) = i4_temp + + end if + + end if +! +! Compute the Householder transformation to reduce the +! J-th column of A to a multiple of the J-th unit vector. +! + ajnorm = enorm ( m-j+1, a(j,j) ) + + if ( ajnorm /= 0.0D+00 ) then + + if ( a(j,j) < 0.0D+00 ) then + ajnorm = -ajnorm + end if + + a(j:m,j) = a(j:m,j) / ajnorm + a(j,j) = a(j,j) + 1.0D+00 +! +! Apply the transformation to the remaining columns and update the norms. +! + do k = j + 1, n + + temp = dot_product ( a(j:m,j), a(j:m,k) ) / a(j,j) + + a(j:m,k) = a(j:m,k) - temp * a(j:m,j) + + if ( pivot .and. rdiag(k) /= 0.0D+00 ) then + + temp = a(j,k) / rdiag(k) + rdiag(k) = rdiag(k) * sqrt ( max ( 0.0D+00, 1.0D+00 - temp ** 2 ) ) + + if ( 0.05D+00 * ( rdiag(k) / wa(k) ) ** 2 <= epsmch ) then + rdiag(k) = enorm ( m-j, a(j+1,k) ) + wa(k) = rdiag(k) + end if + + end if + + end do + + end if + + rdiag(j) = - ajnorm + + end do + + return +end +subroutine r1mpyq ( m, n, a, lda, v, w ) + +!*****************************************************************************80 +! +!! r1mpyq() computes A*Q, where Q is the product of Householder transformations. +! +! Discussion: +! +! Given an M by N matrix A, this function computes A*Q where +! Q is the product of 2*(N - 1) transformations +! +! GV(N-1)*...*GV(1)*GW(1)*...*GW(N-1) +! +! and GV(I), GW(I) are Givens rotations in the (I,N) plane which +! eliminate elements in the I-th and N-th planes, respectively. +! Q itself is not given, rather the information to recover the +! GV, GW rotations is supplied. +! +! Licensing: +! +! This code is distributed under the GNU LGPL license. +! +! Modified: +! +! 06 April 2010 +! +! Author: +! +! Original FORTRAN77 version by Jorge More, Burton Garbow, Kenneth Hillstrom. +! FORTRAN90 version by John Burkardt. +! +! Reference: +! +! Jorge More, Burton Garbow, Kenneth Hillstrom, +! User Guide for MINPACK-1, +! Technical Report ANL-80-74, +! Argonne National Laboratory, 1980. +! +! Parameters: +! +! Input, integer M, the number of rows of A. +! +! Input, integer N, the number of columns of A. +! +! Input/output, real ( kind = rk ) A(LDA,N), the M by N array. +! On input, the matrix A to be postmultiplied by the orthogonal matrix Q. +! On output, the value of A*Q. +! +! Input, integer LDA, the leading dimension of A, which must not +! be less than M. +! +! Input, real ( kind = rk ) V(N), W(N), contain the information necessary +! to recover the Givens rotations GV and GW. +! + implicit none + + integer, parameter :: rk = kind ( 1.0D+00 ) + + integer lda + integer m + integer n + + real ( kind = rk ) a(lda,n) + real ( kind = rk ) c + integer i + integer j + real ( kind = rk ) s + real ( kind = rk ) temp + real ( kind = rk ) v(n) + real ( kind = rk ) w(n) +! +! Apply the first set of Givens rotations to A. +! + do j = n - 1, 1, -1 + + if ( 1.0D+00 < abs ( v(j) ) ) then + c = 1.0D+00 / v(j) + s = sqrt ( 1.0D+00 - c ** 2 ) + else + s = v(j) + c = sqrt ( 1.0D+00 - s ** 2 ) + end if + + do i = 1, m + temp = c * a(i,j) - s * a(i,n) + a(i,n) = s * a(i,j) + c * a(i,n) + a(i,j) = temp + end do + + end do +! +! Apply the second set of Givens rotations to A. +! + do j = 1, n - 1 + + if ( 1.0D+00 < abs ( w(j) ) ) then + c = 1.0D+00 / w(j) + s = sqrt ( 1.0D+00 - c ** 2 ) + else + s = w(j) + c = sqrt ( 1.0D+00 - s ** 2 ) + end if + + do i = 1, m + temp = c * a(i,j) + s * a(i,n) + a(i,n) = - s * a(i,j) + c * a(i,n) + a(i,j) = temp + end do + + end do + + return +end +subroutine r1updt ( m, n, s, ls, u, v, w, sing ) + +!*****************************************************************************80 +! +!! r1updt() re-triangularizes a matrix after a rank one update. +! +! Discussion: +! +! Given an M by N lower trapezoidal matrix S, an M-vector U, and an +! N-vector V, the problem is to determine an orthogonal matrix Q such that +! +! (S + U * V' ) * Q +! +! is again lower trapezoidal. +! +! This function determines Q as the product of 2 * (N - 1) +! transformations +! +! GV(N-1)*...*GV(1)*GW(1)*...*GW(N-1) +! +! where GV(I), GW(I) are Givens rotations in the (I,N) plane +! which eliminate elements in the I-th and N-th planes, +! respectively. Q itself is not accumulated, rather the +! information to recover the GV and GW rotations is returned. +! +! Licensing: +! +! This code is distributed under the GNU LGPL license. +! +! Modified: +! +! 06 April 2010 +! +! Author: +! +! Original FORTRAN77 version by Jorge More, Burton Garbow, Kenneth Hillstrom. +! FORTRAN90 version by John Burkardt. +! +! Reference: +! +! Jorge More, Burton Garbow, Kenneth Hillstrom, +! User Guide for MINPACK-1, +! Technical Report ANL-80-74, +! Argonne National Laboratory, 1980. +! +! Parameters: +! +! Input, integer M, the number of rows of S. +! +! Input, integer N, the number of columns of S. +! N must not exceed M. +! +! Input/output, real ( kind = rk ) S(LS). On input, the lower trapezoidal +! matrix S stored by columns. On output S contains the lower trapezoidal +! matrix produced as described above. +! +! Input, integer LS, the length of the S array. LS must be at +! least (N*(2*M-N+1))/2. +! +! Input, real ( kind = rk ) U(M), the U vector. +! +! Input/output, real ( kind = rk ) V(N). On input, V must contain the +! vector V. On output V contains the information necessary to recover the +! Givens rotations GV described above. +! +! Output, real ( kind = rk ) W(M), contains information necessary to +! recover the Givens rotations GW described above. +! +! Output, logical SING, is set to TRUE if any of the diagonal elements +! of the output S are zero. Otherwise SING is set FALSE. +! + implicit none + + integer, parameter :: rk = kind ( 1.0D+00 ) + + integer ls + integer m + integer n + + real ( kind = rk ) cos + real ( kind = rk ) cotan + real ( kind = rk ) giant + integer i + integer j + integer jj + integer l + real ( kind = rk ) s(ls) + real ( kind = rk ) sin + logical sing + real ( kind = rk ) tan + real ( kind = rk ) tau + real ( kind = rk ) temp + real ( kind = rk ) u(m) + real ( kind = rk ) v(n) + real ( kind = rk ) w(m) +! +! GIANT is the largest magnitude. +! + giant = huge ( giant ) +! +! Initialize the diagonal element pointer. +! + jj = ( n * ( 2 * m - n + 1 ) ) / 2 - ( m - n ) +! +! Move the nontrivial part of the last column of S into W. +! + l = jj + do i = n, m + w(i) = s(l) + l = l + 1 + end do +! +! Rotate the vector V into a multiple of the N-th unit vector +! in such a way that a spike is introduced into W. +! + do j = n - 1, 1, -1 + + jj = jj - ( m - j + 1 ) + w(j) = 0.0D+00 + + if ( v(j) /= 0.0D+00 ) then +! +! Determine a Givens rotation which eliminates the J-th element of V. +! + if ( abs ( v(n) ) < abs ( v(j) ) ) then + cotan = v(n) / v(j) + sin = 0.5D+00 / sqrt ( 0.25D+00 + 0.25D+00 * cotan ** 2 ) + cos = sin * cotan + tau = 1.0D+00 + if ( abs ( cos ) * giant > 1.0D+00 ) then + tau = 1.0D+00 / cos + end if + else + tan = v(j) / v(n) + cos = 0.5D+00 / sqrt ( 0.25D+00 + 0.25D+00 * tan ** 2 ) + sin = cos * tan + tau = sin + end if +! +! Apply the transformation to V and store the information +! necessary to recover the Givens rotation. +! + v(n) = sin * v(j) + cos * v(n) + v(j) = tau +! +! Apply the transformation to S and extend the spike in W. +! + l = jj + do i = j, m + temp = cos * s(l) - sin * w(i) + w(i) = sin * s(l) + cos * w(i) + s(l) = temp + l = l + 1 + end do + + end if + + end do +! +! Add the spike from the rank 1 update to W. +! + w(1:m) = w(1:m) + v(n) * u(1:m) +! +! Eliminate the spike. +! + sing = .false. + + do j = 1, n-1 + + if ( w(j) /= 0.0D+00 ) then +! +! Determine a Givens rotation which eliminates the +! J-th element of the spike. +! + if ( abs ( s(jj) ) < abs ( w(j) ) ) then + + cotan = s(jj) / w(j) + sin = 0.5D+00 / sqrt ( 0.25D+00 + 0.25D+00 * cotan ** 2 ) + cos = sin * cotan + + if ( 1.0D+00 < abs ( cos ) * giant ) then + tau = 1.0D+00 / cos + else + tau = 1.0D+00 + end if + + else + + tan = w(j) / s(jj) + cos = 0.5D+00 / sqrt ( 0.25D+00 + 0.25D+00 * tan ** 2 ) + sin = cos * tan + tau = sin + + end if +! +! Apply the transformation to S and reduce the spike in W. +! + l = jj + do i = j, m + temp = cos * s(l) + sin * w(i) + w(i) = - sin * s(l) + cos * w(i) + s(l) = temp + l = l + 1 + end do +! +! Store the information necessary to recover the Givens rotation. +! + w(j) = tau + + end if +! +! Test for zero diagonal elements in the output S. +! + if ( s(jj) == 0.0D+00 ) then + sing = .true. + end if + + jj = jj + ( m - j + 1 ) + + end do +! +! Move W back into the last column of the output S. +! + l = jj + do i = n, m + s(l) = w(i) + l = l + 1 + end do + + if ( s(jj) == 0.0D+00 ) then + sing = .true. + end if + + return +end diff --git a/src/utils.f90 b/src/utils.f90 index d6b327294..e880dad2f 100644 --- a/src/utils.f90 +++ b/src/utils.f90 @@ -11,12 +11,32 @@ module utils real, parameter :: VACUUM_TO_AIR_LIMIT=200.0000 real, parameter :: AIR_TO_VACUUM_LIMIT=199.9352 - public :: interp + public :: interp, integrate_trap, span, spanl, in_dir, Blambda, Bpnu, & + Bnu, appel_syst, get_NH, linear_1D_sorted, vacuum2air, is_file, is_dir, & + mcfost_update, get_mcfost_utils_version, is_nan_infinity_vector, & + is_nan_infinity_matrix, locate, gaussslv, cross_product, & + gauss_legendre_quadrature, progress_bar, rotation_3d, cdapres, & + mcfost_get_ref_para, mcfost_get_yorick, mcfost_history, mcfost_v, & + update_utils, indgen + + private + interface interp module procedure interp_sp module procedure interp_dp end interface + abstract interface + pure real function func(x) + real, intent(in) :: x + end function func + end interface + + interface integrate_trap + module procedure integrate_trap_func + module procedure integrate_trap_array + end interface integrate_trap + contains function span(xmin,xmax,n) @@ -54,7 +74,7 @@ function span_dp(xmin,xmax,n, dk) real(kind=dp) :: delta_x delta_x = (xmax-xmin)/real(n-1,kind=dp) - + if (dk < 0) then x1 = xmax x2 = xmin @@ -68,7 +88,7 @@ function span_dp(xmin,xmax,n, dk) iend = n i0 = 1 endif - + span_dp(i0) = x1 do i=istart,iend,dk span_dp(i) = span_dp(i-dk) + dk * delta_x @@ -428,7 +448,7 @@ Function Bpnu (N,lambda,T) Bpnu(la) = 0.0 else Bpnu(la) = twohnu3_c2 / (exp(hnu_kT)-1.0) - end if + end if enddo return @@ -1451,7 +1471,7 @@ subroutine read_line(unit,FMT,line,Nread,commentchar) read(unit, FMT, IOSTAT=EOF) line !'(512A)' Nread = len(trim(line)) !comment or empty ? -> go to next line - if ((line(1:1).eq.com).or.(Nread==0)) cycle + if ((line(1:1).eq.com).or.(Nread==0)) cycle ! line read exit ! to process it exit enddo ! if EOF > 0 reaches end of file, leaving @@ -1583,7 +1603,7 @@ function bilinear(N,xi,i0,M,yi,j0,f,xo,yo) real(kind=dp) :: bilinear integer, intent(in) :: N, M real(kind=dp), intent(in) :: xi(N),yi(M),f(N,M) - real(kind=dp), intent(in) :: xo,yo + real(kind=dp), intent(in) :: xo,yo integer, intent(in) :: i0, j0 integer :: i, j real(kind=dp) :: norm, f11, f21, f12, f22 @@ -1701,4 +1721,42 @@ function E2(x) return end function E2 + + pure real function integrate_trap_func(f,xmin,xmax) result(g) + !------------------------------------------------------------------- + ! helper routine to integrate a function using the trapezoidal rule + !------------------------------------------------------------------- + real, intent(in) :: xmin,xmax + procedure(func) :: f + real :: fx,fprev,dx,x + integer, parameter :: npts = 128 + integer :: i + + g = 0. + dx = (xmax-xmin)/(npts) + fprev = f(xmin) + do i=2,npts + x = xmin + i*dx + fx = f(x) + g = g + 0.5*dx*(fx + fprev) + fprev = fx + enddo + + end function integrate_trap_func + + pure real function integrate_trap_array(n,x,f) result(g) + !------------------------------------------------------------------- + ! helper routine to integrate a function using the trapezoidal rule + !------------------------------------------------------------------- + integer, intent(in) :: n + real, intent(in) :: x(n),f(n) + integer :: i + + g = 0. + do i=2,n + g = g + 0.5*(x(i)-x(i-1))*(f(i) + f(i-1)) + enddo + +end function integrate_trap_array + end module utils From 11afe8bb830ded34eaffa7af9aa5fcb2629f1a5c Mon Sep 17 00:00:00 2001 From: Christophe Pinte Date: Thu, 10 Nov 2022 18:10:10 +1100 Subject: [PATCH 2/4] Implementing grain size reconstruction from moments --- src/Makefile | 5 +- src/SPH2mcfost.f90 | 48 ++++++++-- src/gas/elements_type.f90 | 78 +++++++-------- src/mhd2mcfost.f90 | 7 +- src/read_phantom.f90 | 68 ++++++++----- src/reconstruct_from_moments.f90 | 159 +++++++++++++++++++++++++++++++ src/utils.f90 | 35 +++---- 7 files changed, 305 insertions(+), 95 deletions(-) create mode 100644 src/reconstruct_from_moments.f90 diff --git a/src/Makefile b/src/Makefile index 7ef59bb38..7ed50588f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -80,7 +80,7 @@ endif ifeq ($(SYSTEM), ifort) ifeq ($(shell uname | tr '[a-z]' '[A-Z]' 2>&1 | grep -c DARWIN),1) - ARCH= -axSSSE3,SSE4.1,SSE4.2,AVX,CORE-AVX2 -mmacosx-version-min=10.12 -mdynamic-no-pic + ARCH= -axSSSE3,SSE4.1,SSE4.2,AVX,CORE-AVX2 -mmacosx-version-min=10.13.6 -mdynamic-no-pic STATIC_FLAGS= -static-intel -qopenmp-link static else ARCH= -axSSE2,SSSE3,SSE4.1,SSE4.2,AVX,CORE-AVX2,CORE-AVX512 @@ -206,7 +206,8 @@ SOURCES = mcfost_env.f90 parameters.f90 constants.f90 healpix_mod.f90 sha.f90 me optical_depth.f90 ML_prodimo.f90 \ output.f90 mem.f90 init_mcfost.f90 io_phantom_infiles.f90 \ io_phantom_utils.f90 mess_up_SPH.f90 read_phantom.f90 \ - read_gadget2.f90 SPH2mcfost.f90 mhd2mcfost.f90 dust_transfer.f90 \ + read_gadget2.f90 fsolve.f90 reconstruct_from_moments.f90 \ + SPH2mcfost.f90 mhd2mcfost.f90 dust_transfer.f90 \ mol_transfer.f90 $(gdir)/collision_atom.f90 $(gdir)/io_atom.f90 \ $(gdir)/electron_density.f90 $(gdir)/see.f90 \ $(gdir)/atom_transfer.f90 diff --git a/src/SPH2mcfost.f90 b/src/SPH2mcfost.f90 index 270184657..162374efe 100644 --- a/src/SPH2mcfost.f90 +++ b/src/SPH2mcfost.f90 @@ -29,7 +29,7 @@ subroutine setup_SPH2mcfost(SPH_file,SPH_limits_file, n_SPH, extra_heating) real(dp), allocatable, dimension(:) :: x,y,z,h,vx,vy,vz,rho,massgas,SPH_grainsizes,T_gas real(dp), allocatable, dimension(:) :: vturb,mass_ne_on_massgas,atomic_mask integer, allocatable, dimension(:) :: particle_id - real(dp), allocatable, dimension(:,:) :: rhodust, massdust + real(dp), allocatable, dimension(:,:) :: rhodust, massdust, dust_moments real, allocatable, dimension(:) :: extra_heating logical, allocatable, dimension(:) :: mask ! size == np, not n_SPH, index is original SPH id @@ -37,9 +37,9 @@ subroutine setup_SPH2mcfost(SPH_file,SPH_limits_file, n_SPH, extra_heating) real(dp), dimension(6) :: SPH_limits real :: factor integer :: ndusttypes, ierr, i, ilen - logical :: check_previous_tesselation - + logical :: check_previous_tesselation, ldust_moments + ldust_moments = .false. if (lphantom_file) then write(*,*) "Performing phantom2mcfost setup" if (n_phantom_files==1) then @@ -65,7 +65,7 @@ subroutine setup_SPH2mcfost(SPH_file,SPH_limits_file, n_SPH, extra_heating) call read_phantom_files(iunit,n_phantom_files,density_files, x,y,z,h,vx,vy,vz,T_gas, & particle_id, massgas,massdust,rho,rhodust,extra_heating,ndusttypes, & - SPH_grainsizes,mask,n_SPH,ierr) + SPH_grainsizes,mask,n_SPH,ldust_moments,dust_moments,ierr) if (lphantom_avg) then ! We are averaging the dump factor = 1.0/n_phantom_files @@ -106,7 +106,8 @@ subroutine setup_SPH2mcfost(SPH_file,SPH_limits_file, n_SPH, extra_heating) ! Voronoi tesselation check_previous_tesselation = (.not. lrandomize_Voronoi) call SPH_to_Voronoi(n_SPH, ndusttypes, particle_id, x,y,z,h, vx,vy,vz, & - T_gas, massgas,massdust,rho,rhodust,SPH_grainsizes, SPH_limits, check_previous_tesselation, is_ghost, mask=mask) + T_gas, massgas,massdust,rho,rhodust,SPH_grainsizes, SPH_limits, check_previous_tesselation, is_ghost, & + ldust_moments, dust_moments, mask=mask) deallocate(x,y,z,h) if (allocated(vx)) deallocate(vx,vy,vz) @@ -157,7 +158,7 @@ end subroutine read_SPH_limits_file !********************************************************* subroutine SPH_to_Voronoi(n_SPH, ndusttypes, particle_id, x,y,z,h, vx,vy,vz, T_gas, massgas,massdust,rho,rhodust,& - SPH_grainsizes, SPH_limits, check_previous_tesselation, is_ghost, mask) + SPH_grainsizes, SPH_limits, check_previous_tesselation, is_ghost, ldust_moments, dust_moments, mask) ! ************************************************************************************ ! ! n_sph : number of points in the input model @@ -180,6 +181,7 @@ subroutine SPH_to_Voronoi(n_SPH, ndusttypes, particle_id, x,y,z,h, vx,vy,vz, T_g use grains, only : n_grains_tot, M_grain use disk_physics, only : compute_othin_sublimation_radius use mem + use reconstruct_from_moments integer, intent(in) :: n_SPH, ndusttypes real(dp), dimension(n_SPH), intent(inout) :: x,y,z,h,massgas!,rho, !move rho to allocatable, assuming not always allocated @@ -187,11 +189,12 @@ subroutine SPH_to_Voronoi(n_SPH, ndusttypes, particle_id, x,y,z,h, vx,vy,vz, T_g real(dp), dimension(:), allocatable, intent(inout) :: vx,vy,vz ! dimension n_SPH or 0 real(dp), dimension(:), allocatable, intent(in) :: T_gas integer, dimension(n_SPH), intent(in) :: particle_id - real(dp), dimension(:,:), allocatable, intent(inout) :: rhodust, massdust ! ndusttypes,n_SPH + real(dp), dimension(:,:), allocatable, intent(inout) :: rhodust, massdust, dust_moments ! ndusttypes,n_SPH real(dp), dimension(:), allocatable, intent(in) :: SPH_grainsizes ! ndusttypes real(dp), dimension(6), intent(in) :: SPH_limits logical, intent(in) :: check_previous_tesselation logical, dimension(:), allocatable, intent(in), optional :: mask + logical, intent(in) :: ldust_moments integer, dimension(:), allocatable, intent(out) :: is_ghost @@ -199,13 +202,17 @@ subroutine SPH_to_Voronoi(n_SPH, ndusttypes, particle_id, x,y,z,h, vx,vy,vz, T_g logical :: lwrite_ASCII = .false. ! produce an ASCII file for yorick real, allocatable, dimension(:) :: a_SPH, log_a_SPH, rho_dust + real(dp), allocatable, dimension(:) :: gsize, grainsize_f + real(dp), dimension(4) :: lambsol, lambguess real(dp) :: mass, somme, Mtot, Mtot_dust, facteur real :: f, limit_threshold, density_factor - integer :: icell, l, k, iSPH, n_force_empty, i, id_n + integer :: icell, l, k, iSPH, n_force_empty, i, id_n, ierr real(dp), dimension(6) :: limits + real(dp), parameter :: a0 = 1.28e-4 ! microns. Siess et al 2022 + if (lcorrect_density_elongated_cells) then density_factor = correct_density_factor_elongated_cells else @@ -355,7 +362,28 @@ subroutine SPH_to_Voronoi(n_SPH, ndusttypes, particle_id, x,y,z,h, vx,vy,vz, T_g ! Tableau de densite et masse de poussiere ! interpolation en taille - if (ndusttypes >= 1) then + if (ldust_moments) then + lvariable_dust = .true. + allocate(grainsize_f(n_grains_tot)) + gsize(:) = r_grain(:)/a0 ! grain sizes, units should be ok + + do icell=1,n_cells + iSPH = Voronoi(icell)%id + if (iSPH > 0) then + + ! mass & density indices are shifted by 1 + lambguess = [-log(sqrt(2*pi)),0._dp,0._dp,0._dp] + + call reconstruct_maxent(dust_moments(:,iSPH),gsize,grainsize_f,lambsol,ierr,lambguess=lambguess) + if (ierr > 0) call error("reconstruct_maxent: "//fsolve_error(ierr)) + + densite_pouss(:,icell) = grainsize_f(:) + else ! iSPH == 0, star + densite_pouss(:,icell) = 0. + endif + enddo ! icell + + elseif (ndusttypes >= 1) then lvariable_dust = .true. ! mcfost adds an extra grain size follwing the gas @@ -931,7 +959,7 @@ subroutine test_voro_star(x,y,z,h,vx,vy,vz,T_gas,massgas,rhogas,rhodust,particle stream(i) = init_sprng(gtype, i-1,nb_proc,seed,SPRNG_DEFAULT) enddo - lignore_dust = .true. + lignore_dust = .true. ndusttypes = 0 llimits_file = .false. limits_file = "" diff --git a/src/gas/elements_type.f90 b/src/gas/elements_type.f90 index cb53dcaff..1fc2cce8a 100644 --- a/src/gas/elements_type.f90 +++ b/src/gas/elements_type.f90 @@ -1,13 +1,13 @@ module elements_type !Small module to keep track of data of elements either bound to molecules or in atoms - !sets the abundance of the model, used for atom rt and chemical equilibrium if + !sets the abundance of the model, used for atom rt and chemical equilibrium if !molecules and atoms overlap. use mcfost_env, only : dp, mcfost_utils - use constantes, only : hp, c_light, cm_to_m + use constantes use utils !do not forget to add the math module un int - implicit none + implicit none type Element @@ -24,7 +24,7 @@ module elements_type type elemPointerArray type(Element), pointer :: p => NULL() - end type elemPointerArray + end type elemPointerArray ! type (elemPointerArray), dimension(:), allocatable :: Elements @@ -60,9 +60,9 @@ module elements_type 190.2,192.2,195.1,197.0,200.6,204.4,207.2,209.0, & 210.0,211.0,222.0,223.0,226.1,227.1,232.0,231.0, & 238.0,237.0,244.0,243.0,247.0,247.0,251.0, 254.0/ - + character(len=2), dimension(NELEM_WEIGHTS) :: elemental_ID - + DATA elemental_ID /'H ','He','Li','Be','B ','C ','N ','O ', & 'F ','Ne','Na','Mg','Al','Si','P ','S ','Cl', & 'Ar','K ','Ca','Sc','Ti','V','Cr','Mn','Fe', & @@ -74,7 +74,7 @@ module elements_type 'Hf','Ta','W ','Re','Os','Ir','Pt','Au','Hg', & 'Tl','Pb','Bi','Po','At','Rn','Fr','Ra','Ac', & 'Th','Pa','U ','Np','Pu','Am','Cm','Bk','Cf','Es'/ - + contains @@ -111,7 +111,7 @@ subroutine read_abundance() real(kind=dp), allocatable, dimension(:,:) :: data_krz integer, parameter :: Nstage_max = 100 integer :: NAXISPF(2) - + eof = 0 !Start reading partition function by first reading the grid !get unique unit number @@ -129,7 +129,7 @@ subroutine read_abundance() write(*,*) "exiting... !" stop end if - + allocate(Tpf(Npf)) !now read temperature grid call ftgpvd(unit,1,1,Npf,-999,Tpf,anynull,EOF) !d because double !! @@ -140,11 +140,11 @@ subroutine read_abundance() !do not close yet !will be closed after all data are read! - - + + !read abundances write(FormatLine,'("(1"A,I3")")') "A", 10 - + open(unit=1, file=TRIM(mcfost_utils)//TRIM(ABUNDANCE_FILE),status="old") call read_line(1, FormatLine, inputline, Nread) read(inputline,*) Nelem @@ -157,22 +157,22 @@ subroutine read_abundance() totalAbund = 0.0 avgWeight = 0.0 do n=1, Nelem !H is first, then He ect - + call read_line(1, FormatLine, inputline, Nread) read(inputline,*) charID, A - + elems(n)%weight=atomic_weights(n) elems(n)%ID = charID(1:2) !supposed to be lowercases!!!!! elems(n)%abund = 10.**(A-12.0) - + if (A <= -99) elems(n)%abund = 0.0 - + elems(n)%abundance_set = .true. totalAbund = totalAbund+elems(n)%abund avgWeight = avgWeight+elems(n)%abund*elems(n)%weight - - !read pf data, the fits is not closed yet. + + !read pf data, the fits is not closed yet. call FTMAHD(unit,n+1,hdutype,EOF) if (EOF.ne.0) then write(*,*) "error reading partition function" @@ -185,7 +185,7 @@ subroutine read_abundance() !j'arrive pas a lire ce string, but it is not necessary !but should asks christophe to know how to do !call ftgkyj(unit, "ELEM", AtomID, commentfits, EOF) - + !write(*,*) "Nstage", Nstage, "Z=", Z, "for elem:", elemental_ID(code) !write(*,*) "Npf points = ", Npf !now read partition function @@ -232,21 +232,21 @@ subroutine read_abundance() ! free the unit number call ftfiou(unit, EOF) deallocate(data_krz) - + wght_per_H = avgWeight avgWeight = avgWeight/totalAbund write(*,*) "Total Abundance in the atmosphere = ", totalAbund write(*,*) "Total average weight = ", avgWeight write(*,*) "Weight per Hydrogen = ", wght_per_H !i.e., per AMU = total mass write(*,*) "" - + !store also the mass fraction of each element do n=1, Nelem elems(n)%massf = elems(n)%weight*elems(n)%Abund/wght_per_H enddo Max_ionisation_stage = maxval(elems(:)%Nstage) - + return end subroutine read_abundance @@ -260,17 +260,17 @@ subroutine atom_pos(Z, row, col) ! beware He, is on the 18 column, because they are 16 ! empty columns between H and He ! --------------------------------------------------- - + integer i integer, intent(in) :: Z integer, intent(out) :: row, col integer :: istart(7) - + row = 0 col = 0 - + istart = (/1, 3, 11, 19, 37, 55, 87/) !first Z of each rows - + ! -- find row do i=1,6 if ((Z .ge. istart(i)) .and. (Z .lt. istart(i + 1))) then @@ -281,10 +281,10 @@ subroutine atom_pos(Z, row, col) end do ! find column on the row col = Z - istart(i) + 1 - + ! for relative position just comment out the following lines ! or take col=col-10 (or-18 for He) for these cases. - + ! special case of Z=2 for Helium because there are 16 ! empty columns between Hydrogen and Helium if (Z.eq.2) then @@ -304,7 +304,7 @@ function get_pf(elem, j, temp) result (Uk) ! Interpolate the partition function of Element elem in ionisation stage ! j at temperature temp ! ----------------------------------------------------------------------! - + type(Element), intent(in) :: elem integer, intent(in) :: j real(kind=dp), intent(in) :: temp @@ -313,7 +313,7 @@ function get_pf(elem, j, temp) result (Uk) ! Uk = exp( interp(elem%pf(:,j),Tpf,temp) ) ! elem%pf(:,j) is ln(U) - + !->faster !out of bound the function return 0 not the inner (outer) bound. tp(1) = temp @@ -321,7 +321,7 @@ function get_pf(elem, j, temp) result (Uk) Uk = exp(Uka(1)) if( temp < Tpf(1) ) Uk = exp(elem%pf(1,j)) if (temp > Tpf(Npf)) Uk = exp(elem%pf(Npf,j)) - + return end function get_pf @@ -347,23 +347,23 @@ function phi_jl(temp, Ujl, Uj1l, ionpot) real(kind=dp), intent(in) :: Ujl, Uj1l, ionpot C1 = 0.5*(HP**2 / (2.*PI*mel*kb))**1.5 !C1 = (HPLANCK**2 / (2.*PI*M_ELECTRON*KBOLTZMANN))**1.5 - + !!ionpot = ionpot * 100.*HPLANCK*CLIGHT !cm1->J !! -> avoiding dividing by big numbers causing overflow. !!maximum argument is 600, exp(600) = 3.77e260 expo = exp(min(600d0,ionpot/(kb*temp))) if (ionpot/(kb*temp) >= 600d0) expo = huge_dp - + !if exp(300) it means phi is "infinity", exp(300) == 2e130 so that's enough phi_jl = C1 * (Ujl / Uj1l) * expo / (temp**1.5 + tiny_dp) if (phi_jl < phi_min_limit) phi_jl = 0d0 !tiny_dp ! or phi = 0d0 should be more correct ? ! but test to not divide by 0 - + if (is_nan_infinity(phi_jl)>0) then write(*,*) "error, phi_jl", phi_jl, temp stop endif - + return end function phi_jl @@ -384,11 +384,11 @@ function SahaEq(temp, NI, UI1, UI, chi, ne) result(NI1) real(kind=dp), intent(in) :: NI, UI1, UI, chi, ne real(kind=dp) :: phi, NI1 phi = phi_jl(temp, UI, UI1, chi) ! chi in J - + !phi should be between phi_min_limit and exp(600) !and ne between ne_limit and nemax, so never nan nor infinity !further in General, a larg phi implies a ne close to 0, so the product remains small - + if (ne > 0.0) then NI1 = NI/(phi*ne) else !all in ground state, phi->infinity if T->0 and phi->0 if T->infinty @@ -396,7 +396,7 @@ function SahaEq(temp, NI, UI1, UI, chi, ne) result(NI1) !meaning if ne->0 phi goes to infinity NI1 = 0.0 endif - + RETURN end function SahaEq @@ -408,4 +408,4 @@ end function SahaEq !subroutine read_pf(type="....") !end subroutine -end module \ No newline at end of file +end module diff --git a/src/mhd2mcfost.f90 b/src/mhd2mcfost.f90 index 420bb2407..664d00b64 100644 --- a/src/mhd2mcfost.f90 +++ b/src/mhd2mcfost.f90 @@ -45,6 +45,10 @@ subroutine setup_mhd_to_mcfost() real(kind=dp), dimension(6) :: hydro_limits integer :: ndusttypes !, voroindex, N_fixed_ne = 0 real, parameter :: limit_factor = 1.005!, Lextent = 1.01 + logical :: ldust_moments + real(dp), dimension(:,:), allocatable :: dust_moments + + ldust_moments = .false. write(FormatLine,'("(1"A,I3")")') "A", 512 @@ -167,7 +171,8 @@ subroutine setup_mhd_to_mcfost() !also work with grid-based code !massdust, rhodust, hydro_grainsizes not allocated if ndusttypes = 0 ! call sph_to_voronoi(n_points-n_etoiles, ndusttypes, particle_id, x, y, z, h, vx, vy, vz, & - T_tmp, mass_gas, massdust, rho, rhodust, hydro_grainsizes, hydro_limits, check_previous_tesselation, is_ghost) + T_tmp, mass_gas, massdust, rho, rhodust, hydro_grainsizes, hydro_limits, check_previous_tesselation, is_ghost, & + ldust_moments, dust_moments) ! -> correction for small density applied on mass_gas directly inside call hydro_to_Voronoi_atomic(n_points,T_tmp,vt_tmp,mass_gas,mass_ne_on_massgas,dz) diff --git a/src/read_phantom.f90 b/src/read_phantom.f90 index 37737dec2..9ba4de2fb 100644 --- a/src/read_phantom.f90 +++ b/src/read_phantom.f90 @@ -11,16 +11,17 @@ module read_phantom contains subroutine read_phantom_bin_files(iunit,n_files, filenames, x,y,z,h,vx,vy,vz,T_gas,particle_id,massgas,massdust,& - rhogas,rhodust,extra_heating,ndusttypes,SPH_grainsizes,mask,n_SPH,ierr) + rhogas,rhodust,extra_heating,ndusttypes,SPH_grainsizes,mask,n_SPH,ldust_moments,dust_moments,ierr) integer, intent(in) :: iunit, n_files character(len=*),dimension(n_files), intent(in) :: filenames real(dp), intent(out), dimension(:), allocatable :: x,y,z,h, vx,vy,vz, rhogas,massgas,SPH_grainsizes,T_gas integer, intent(out), dimension(:), allocatable :: particle_id - real(dp), intent(out), dimension(:,:), allocatable :: rhodust,massdust + real(dp), intent(out), dimension(:,:), allocatable :: rhodust,massdust, dust_moments logical, dimension(:), allocatable, intent(out) :: mask real, intent(out), dimension(:), allocatable :: extra_heating integer, intent(out) :: ndusttypes,n_SPH,ierr + logical, intent(out) :: ldust_moments integer, parameter :: maxarraylengths = 12 integer, parameter :: nsinkproperties = 17 @@ -33,7 +34,7 @@ subroutine read_phantom_bin_files(iunit,n_files, filenames, x,y,z,h,vx,vy,vz,T_g integer, parameter :: maxtypes = 6 integer, parameter :: maxfiles = 3 integer, parameter :: maxinblock = 128 - integer, parameter :: n_nucleation = 10 + integer, parameter :: n_nucleation = 6 integer, allocatable, dimension(:) :: npartoftype real(dp), allocatable, dimension(:,:) :: massoftype !(maxfiles,maxtypes) real(dp) :: hfact,umass,utime,ulength,gmw,x2,mass_per_H @@ -43,7 +44,7 @@ subroutine read_phantom_bin_files(iunit,n_files, filenames, x,y,z,h,vx,vy,vz,T_g real(dp), allocatable, dimension(:) :: dudt, tmp_dp,gastemperature real(dp), allocatable, dimension(:,:) :: xyzh,xyzmh_ptmass,vxyz_ptmass,dustfrac,vxyzu,nucleation type(dump_h) :: hdr - logical :: got_h,got_dustfrac,got_itype,tagged,matched,got_temperature,got_u,lpotential,do_nucleation + logical :: got_h,got_dustfrac,got_itype,tagged,matched,got_temperature,got_u,lpotential integer :: ifile, np0, ntypes0, np_tot, ntypes_tot, ntypes_max, ndustsmall, ndustlarge ! We first read the number of particules in each phantom file @@ -96,9 +97,9 @@ subroutine read_phantom_bin_files(iunit,n_files, filenames, x,y,z,h,vx,vy,vz,T_g call extract('mass_per_H',mass_per_h,hdr,ierr) if (ierr /= 0) then ! No dust nucleation - do_nucleation = .false. + ldust_moments = .false. else - do_nucleation = .true. + ldust_moments = .true. endif call free_header(hdr, ierr) @@ -110,7 +111,7 @@ subroutine read_phantom_bin_files(iunit,n_files, filenames, x,y,z,h,vx,vy,vz,T_g allocate(xyzh(4,np_tot),itype(np_tot),vxyzu(4,np_tot),gastemperature(np_tot)) allocate(dustfrac(ndusttypes,np_tot),grainsize(ndusttypes),graindens(ndusttypes)) allocate(dudt(np_tot),ifiles(np_tot),massoftype(n_files,ntypes_max),npartoftype(ntypes_tot)) - if (do_nucleation) allocate(nucleation(n_nucleation,np_tot)) + if (ldust_moments) allocate(nucleation(n_nucleation,np_tot)) np0 = 0 ntypes0 = 0 @@ -269,17 +270,17 @@ subroutine read_phantom_bin_files(iunit,n_files, filenames, x,y,z,h,vx,vy,vz,T_g got_dustfrac = .true. ! Nucleation case('K0') - read(iunit,iostat=ierr) tmp_dp ; nucleation(2,np0+1:np0+np) = tmp_dp + read(iunit,iostat=ierr) tmp_dp ; nucleation(1,np0+1:np0+np) = tmp_dp case('K1') - read(iunit,iostat=ierr) tmp_dp ; nucleation(3,np0+1:np0+np) = tmp_dp + read(iunit,iostat=ierr) tmp_dp ; nucleation(2,np0+1:np0+np) = tmp_dp case('K2') - read(iunit,iostat=ierr) tmp_dp ; nucleation(4,np0+1:np0+np) = tmp_dp + read(iunit,iostat=ierr) tmp_dp ; nucleation(3,np0+1:np0+np) = tmp_dp case('K3') - read(iunit,iostat=ierr) tmp_dp ; nucleation(5,np0+1:np0+np) = tmp_dp + read(iunit,iostat=ierr) tmp_dp ; nucleation(4,np0+1:np0+np) = tmp_dp case('mu') - read(iunit,iostat=ierr) tmp_dp ; nucleation(6,np0+1:np0+np) = tmp_dp + read(iunit,iostat=ierr) tmp_dp ; nucleation(5,np0+1:np0+np) = tmp_dp case('kappa') - read(iunit,iostat=ierr) tmp_dp ; nucleation(9,np0+1:np0+np) = tmp_dp + read(iunit,iostat=ierr) tmp_dp ; nucleation(6,np0+1:np0+np) = tmp_dp case default matched = .false. read(iunit,iostat=ierr) @@ -422,11 +423,11 @@ subroutine read_phantom_bin_files(iunit,n_files, filenames, x,y,z,h,vx,vy,vz,T_g if (got_h) then call modify_dump(np, nptmass, xyzh, vxyzu, xyzmh_ptmass, ulength, mask) - call phantom_2_mcfost(np_tot,nptmass,ntypes_max,ndusttypes,do_nucleation,n_files,dustfluidtype,xyzh,& + call phantom_2_mcfost(np_tot,nptmass,ntypes_max,ndusttypes,ldust_moments,n_files,dustfluidtype,xyzh,& vxyzu,gastemperature,itype,grainsize,dustfrac,nucleation,massoftype,xyzmh_ptmass,vxyz_ptmass,& hfact,umass,utime,ulength,graindens,ndudt,dudt,ifiles, & n_SPH,x,y,z,h,vx,vy,vz,T_gas,particle_id, & - SPH_grainsizes,massgas,massdust,rhogas,rhodust,extra_heating,ieos) + SPH_grainsizes,massgas,massdust,rhogas,rhodust,dust_moments,extra_heating,ieos) write(*,"(a,i8,a)") ' Using ',n_SPH,' particles from Phantom file' else n_SPH = 0 @@ -444,7 +445,7 @@ end subroutine read_phantom_bin_files subroutine read_phantom_hdf_files(iunit,n_files, filenames, x,y,z,h,vx,vy,vz,T_gas,& particle_id,massgas,massdust,rhogas,rhodust, & extra_heating,ndusttypes,SPH_grainsizes, & - mask,n_SPH,ierr) + mask,n_SPH,ldust_moments,dust_moments,ierr) use utils_hdf5, only:open_hdf5file, & close_hdf5file, & @@ -460,10 +461,11 @@ subroutine read_phantom_hdf_files(iunit,n_files, filenames, x,y,z,h,vx,vy,vz,T_g rhogas,massgas, & SPH_grainsizes integer, intent(out), dimension(:), allocatable :: particle_id - real(dp), intent(out), dimension(:,:), allocatable :: rhodust,massdust + real(dp), intent(out), dimension(:,:), allocatable :: rhodust,massdust,dust_moments logical, dimension(:), allocatable, intent(out) :: mask real, intent(out), dimension(:), allocatable :: extra_heating integer, intent(out) :: ndusttypes,n_SPH,ierr + logical, intent(out) :: ldust_moments character(len=200) :: filename @@ -488,7 +490,9 @@ subroutine read_phantom_hdf_files(iunit,n_files, filenames, x,y,z,h,vx,vy,vz,T_g integer(HID_T) :: hdf5_file_id integer(HID_T) :: hdf5_group_id - logical :: got, do_nucleation=.false. + logical :: got + + ldust_moments=.false. ierr = 0 np_tot = 0 @@ -685,12 +689,12 @@ subroutine read_phantom_hdf_files(iunit,n_files, filenames, x,y,z,h,vx,vy,vz,T_g call modify_dump(np, nptmass, xyzh, vxyzu, xyzmh_ptmass, ulength, mask) - call phantom_2_mcfost(np_tot,nptmass,ntypes_max,ndusttypes,do_nucleation,n_files, & + call phantom_2_mcfost(np_tot,nptmass,ntypes_max,ndusttypes,ldust_moments,n_files, & dustfluidtype,xyzh,vxyzu,gastemperature,itype,grainsize,dustfrac,nucleation, & massoftype,xyzmh_ptmass,vxyz_ptmass,hfact,umass, & utime,ulength,graindens,ndudt,dudt,ifiles, & n_SPH,x,y,z,h,vx,vy,vz,T_gas,particle_id,SPH_grainsizes, & - massgas,massdust,rhogas,rhodust,extra_heating,ieos) + massgas,massdust,rhogas,rhodust,dust_moments,extra_heating,ieos) write(*,"(a,i8,a)") ' Using ',n_SPH,' particles from Phantom file' @@ -747,11 +751,11 @@ end subroutine modify_dump !************************************************************************* - subroutine phantom_2_mcfost(np,nptmass,ntypes,ndusttypes,do_nucleation,n_files,dustfluidtype,xyzh, & + subroutine phantom_2_mcfost(np,nptmass,ntypes,ndusttypes,ldust_moments,n_files,dustfluidtype,xyzh, & vxyzu,gastemperature,iphase,grainsize,dustfrac,nucleation,massoftype,xyzmh_ptmass,vxyz_ptmass,hfact,umass, & utime, ulength,graindens,ndudt,dudt,ifiles, & n_SPH,x,y,z,h,vx,vy,vz,T_gas,particle_id, & - SPH_grainsizes, massgas,massdust, rhogas,rhodust,extra_heating,ieos) + SPH_grainsizes, massgas,massdust, rhogas,rhodust,dust_moments,extra_heating,ieos) ! Convert phantom quantities & units to mcfost quantities & units ! @@ -766,7 +770,7 @@ subroutine phantom_2_mcfost(np,nptmass,ntypes,ndusttypes,do_nucleation,n_files,d ! Input arguments integer, intent(in) :: np,nptmass,ntypes,ndusttypes, n_files,dustfluidtype - logical, intent(in) :: do_nucleation + logical, intent(in) :: ldust_moments real(dp), dimension(4,np), intent(in) :: xyzh,vxyzu integer(kind=1), dimension(np), intent(in) :: iphase, ifiles real(dp), dimension(ndusttypes,np), intent(in) :: dustfrac @@ -783,10 +787,11 @@ subroutine phantom_2_mcfost(np,nptmass,ntypes,ndusttypes,do_nucleation,n_files,d integer, intent(out) :: n_SPH, ieos real(dp), dimension(:), allocatable, intent(out) :: x,y,z,h,vx,vy,vz,T_gas,rhogas,massgas ! massgas [Msun] integer, dimension(:), allocatable, intent(out) :: particle_id - real(dp), dimension(:,:), allocatable, intent(out) :: rhodust,massdust + real(dp), dimension(:,:), allocatable, intent(out) :: rhodust,massdust,dust_moments real(dp), dimension(:), allocatable, intent(out) :: SPH_grainsizes ! mum real, dimension(:), allocatable, intent(out) :: extra_heating + type(star_type), dimension(:), allocatable :: etoile_old integer :: i,j,k,itypei,alloc_status,i_etoile, n_etoiles_old, ifile real(dp) :: xi,yi,zi,hi,vxi,vyi,vzi,T_gasi,rhogasi,rhodusti,gasfraci,dustfraci,totlum,qtermi @@ -869,6 +874,14 @@ subroutine phantom_2_mcfost(np,nptmass,ntypes,ndusttypes,do_nucleation,n_files,d endif endif + if (ldust_moments) then + allocate(dust_moments(4,n_SPH), stat=alloc_status) + if (alloc_status /=0) then + write(*,*) "Allocation error dust_moments in phanton_2_mcfost" + write(*,*) "Exiting" + endif + endif + j = 0 do i=1,np ifile = ifiles(i) @@ -916,6 +929,9 @@ subroutine phantom_2_mcfost(np,nptmass,ntypes,ndusttypes,do_nucleation,n_files,d vy(j) = vyi * uvelocity vz(j) = vzi * uvelocity endif + + if (ldust_moments) dust_moments(:,j) = nucleation(1:4,i) + T_gas(j) = T_gasi rhogasi = massoftype(ifile,itypei) *(hfact/hi)**3 * udens ! g/cm**3 dustfraci = sum(dustfrac(:,i)) @@ -1108,12 +1124,12 @@ subroutine phantom_2_mcfost(np,nptmass,ntypes,ndusttypes,do_nucleation,n_files,d enddo etoile(:)%find_spectrum = .true. - if (do_nucleation) then + if (ldust_moments) then etoile(:)%T = xyzmh_ptmass(13,:) ! deriving radius from luminosity (Reff is not correct in dump) etoile(:)%r = sqrt(xyzmh_ptmass(12,:) * uWatt / Lsun) * (Tsun/etoile(:)%T)**2 / 2.5 - write(*,*) "TMP : correcting radius" + write(*,*) "****************** TMP : correcting stellar radius" lfix_star = .true. diff --git a/src/reconstruct_from_moments.f90 b/src/reconstruct_from_moments.f90 new file mode 100644 index 000000000..dd69d3b62 --- /dev/null +++ b/src/reconstruct_from_moments.f90 @@ -0,0 +1,159 @@ +module reconstruct_from_moments + + use mcfost_env, only : dp + + implicit none + real, parameter :: pi = 4.*atan(1.) + + public :: reconstruct,reconstruct_maxent,fsolve_error,integrand + + private + +contains +! +! reconstruct a function from a limited number +! of specified moments mu_n, corresponding to +! +! mu_n = \int f(x) x^n dx +! +! Arguments: +! IN: +! mu(:) : array of moments of arbitrary length +! x(:) : grid of positions on which to evaluate function +! OUT: +! f(size(x)) : reconstructed function evaluated +! on grid positions +! lambsol(size(mu)) : parameters used in max entropy reconstruction +! ierr : error code (>0 if something went wrong) +! +subroutine reconstruct(mu,x,f,lambsol,ierr,lambguess) + real(dp), intent(in) :: mu(:) + real(dp), intent(in) :: x(:) + real(dp), intent(out) :: f(size(x)) + real(dp), intent(out) :: lambsol(size(mu)) + integer, intent(out) :: ierr + real(dp), intent(in), optional :: lambguess(size(mu)) + + ! in principle can choose method here + if (present(lambguess)) then + call reconstruct_maxent(mu,x,f,lambsol,ierr,lambguess) + else + call reconstruct_maxent(mu,x,f,lambsol,ierr,lambguess) + endif + +end subroutine reconstruct + +! +! maximum entropy reconstruction of function given moments +! +subroutine reconstruct_maxent(mu,x,f,lambsol,ierr,lambguess) + real(dp), intent(in) :: mu(:) + real(dp), intent(in) :: x(:) + real(dp), intent(out) :: f(size(x)) + real(dp), intent(out) :: lambsol(size(mu)) + integer, intent(out) :: ierr + real(dp), intent(in), optional :: lambguess(size(mu)) + integer :: n_moments + real(dp), parameter :: tol = 1.e-6 + real(dp) :: lsum(size(mu)) + + lambsol = 0. + ierr = 0 + f = 0. + n_moments = size(mu) + + ! initial guesses for Lagrange multipliers + if (present(lambguess)) then + ! use guesses passed as arguments (e.g. from previous attempt) + lambsol = lambguess + else + ! use predefined guess + lambsol(1) = -log(sqrt(2.*pi)) + endif + + call fsolve(residual,n_moments,lambsol,lsum,tol,ierr) + f = integrand(x,lambsol,n_moments) + +contains +! +! residual of the moment approximation function +! +! Calculates the residual of the moment approximation function. +! +! Parameters: +! lamb (array): an array of Lagrange constants used to approximate the distribution +! x (array): +! k (integer): order of the moment +! mu (array): an array of the known moments needed to approximate the distribution function +! +! Returns: +! rhs: the integrated right hand side of the moment approximation function +! +subroutine residual(n,lamb,l_sum) + use utils, only:integrate_trap + integer, intent(in) :: n + real(dp), intent(in) :: lamb(n) ! guess for solution + real(dp), intent(out) :: l_sum(n) ! function evaluated for given lamb + + integer :: k + + do k=1,n + !l_sum(k) = sum(integrand(x,lamb,k-1)) - mu(k) + l_sum(k) = integrate_trap(size(x),x,integrand(x,lamb,k-1)) - mu(k) + enddo + !print*,' residuals are: ',l_sum(:) + +end subroutine residual + +end subroutine reconstruct_maxent + +! +! compute the integrand (bit inside the integral) of the kth moment +! +! IN: +! x : grid on which to evaluate the integrand +! lamb: array of Lagrange multipliers +! k : order of moment to calculate (e.g. 0, 1, 2...) +! +function integrand(x,lamb,k) result(y) + real(dp), intent(in) :: x(:) + real(dp), intent(in) :: lamb(:) + real(dp) :: y(size(x)) ! result + integer, intent(in) :: k + real(dp) :: xi(size(lamb)) + integer :: i,j + + do i=1,size(x) + do j=1,size(lamb) + xi(j) = x(i)**(j-1) + enddo + y(i) = x(i)**k * exp(dot_product(lamb,xi)) + enddo + +end function integrand + +! +! print the error message corresponding to the error code +! +function fsolve_error(ierr) result(string) + integer, intent(in) :: ierr + character(len=62) :: string + + select case(ierr) + case(0) + string = 'improper input parameters' + case(1) + string = 'relative error between X and solution is at most tol' + case(2) + string = 'number of calls to residual function exceeded 200*(N+1)' + case(3) + string = 'TOL is too small. No further improvement in solution possible' + case(4) + string = 'the iteration is not making good progress' + case default + string = '' + end select + +end function fsolve_error + +end module reconstruct_from_moments diff --git a/src/utils.f90 b/src/utils.f90 index e880dad2f..77ef4b8a7 100644 --- a/src/utils.f90 +++ b/src/utils.f90 @@ -11,24 +11,25 @@ module utils real, parameter :: VACUUM_TO_AIR_LIMIT=200.0000 real, parameter :: AIR_TO_VACUUM_LIMIT=199.9352 - public :: interp, integrate_trap, span, spanl, in_dir, Blambda, Bpnu, & - Bnu, appel_syst, get_NH, linear_1D_sorted, vacuum2air, is_file, is_dir, & - mcfost_update, get_mcfost_utils_version, is_nan_infinity_vector, & - is_nan_infinity_matrix, locate, gaussslv, cross_product, & - gauss_legendre_quadrature, progress_bar, rotation_3d, cdapres, & - mcfost_get_ref_para, mcfost_get_yorick, mcfost_history, mcfost_v, & - update_utils, indgen - - private - +! public :: interp, integrate_trap, span, spanl, in_dir, Blambda, Bpnu, & +! Bnu, appel_syst, get_NH, linear_1D_sorted, vacuum2air, is_file, is_dir, & +! mcfost_update, get_mcfost_utils_version, is_nan_infinity, is_nan_infinity_vector, & +! is_nan_infinity_matrix, locate, gaussslv, cross_product, & +! gauss_legendre_quadrature, progress_bar, rotation_3d, cdapres, & +! mcfost_get_ref_para, mcfost_get_yorick, mcfost_history, mcfost_v, & +! update_utils, indgen, is_diff, real_equality, span_dp, spanl_dp, bilinear, & +! read_comments, read_line, Blambda_dp +! +! private +! interface interp module procedure interp_sp module procedure interp_dp end interface abstract interface - pure real function func(x) - real, intent(in) :: x + pure real(8) function func(x) + real(8), intent(in) :: x end function func end interface @@ -1579,8 +1580,8 @@ end function air2vacuum function locate(xx,x,mask) - !wrapper function to locate the position of x in array xx. - !the closest position is returned. + ! wrapper function to locate the position of x in array xx. + ! the closest position is returned. real(kind=dp), dimension(:), intent(in) :: xx real(kind=dp), intent(in) :: x logical, intent(in), dimension(:), optional :: mask @@ -1726,9 +1727,9 @@ pure real function integrate_trap_func(f,xmin,xmax) result(g) !------------------------------------------------------------------- ! helper routine to integrate a function using the trapezoidal rule !------------------------------------------------------------------- - real, intent(in) :: xmin,xmax + real(dp), intent(in) :: xmin,xmax procedure(func) :: f - real :: fx,fprev,dx,x + real(dp) :: fx,fprev,dx,x integer, parameter :: npts = 128 integer :: i @@ -1749,7 +1750,7 @@ pure real function integrate_trap_array(n,x,f) result(g) ! helper routine to integrate a function using the trapezoidal rule !------------------------------------------------------------------- integer, intent(in) :: n - real, intent(in) :: x(n),f(n) + real(dp), intent(in) :: x(n),f(n) integer :: i g = 0. From c96fa4901f6a7089dfbb84e446474a614ac0bdf3 Mon Sep 17 00:00:00 2001 From: Christophe Pinte Date: Wed, 16 Nov 2022 12:46:47 +1100 Subject: [PATCH 3/4] Adding error message in moment reconstuction --- src/SPH2mcfost.f90 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/SPH2mcfost.f90 b/src/SPH2mcfost.f90 index 162374efe..a1442d83a 100644 --- a/src/SPH2mcfost.f90 +++ b/src/SPH2mcfost.f90 @@ -371,11 +371,15 @@ subroutine SPH_to_Voronoi(n_SPH, ndusttypes, particle_id, x,y,z,h, vx,vy,vz, T_g iSPH = Voronoi(icell)%id if (iSPH > 0) then + write(*,*) i, iSPH ! mass & density indices are shifted by 1 - lambguess = [-log(sqrt(2*pi)),0._dp,0._dp,0._dp] + lambguess = [-log(sqrt(2*pi)) * dust_moments(1,iSPH) ,0._dp,0._dp,0._dp] call reconstruct_maxent(dust_moments(:,iSPH),gsize,grainsize_f,lambsol,ierr,lambguess=lambguess) - if (ierr > 0) call error("reconstruct_maxent: "//fsolve_error(ierr)) + if (ierr > 0) then + write(*,*) iSPH, dust_moments(:,iSPH) + call error("reconstruct_maxent: "//fsolve_error(ierr)) + endif densite_pouss(:,icell) = grainsize_f(:) else ! iSPH == 0, star From 2108c55e7dc9bbbb3f04e682ba9dcfd63782adc0 Mon Sep 17 00:00:00 2001 From: Christophe Pinte Date: Thu, 7 Dec 2023 12:57:47 +1100 Subject: [PATCH 4/4] Fixing mcfost2phantom interface --- src/mcfost2phantom.f90 | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/mcfost2phantom.f90 b/src/mcfost2phantom.f90 index ed88c44cf..8e45ef406 100644 --- a/src/mcfost2phantom.f90 +++ b/src/mcfost2phantom.f90 @@ -199,9 +199,7 @@ subroutine run_mcfost_phantom(np,nptmass,ntypes,ndusttypes,dustfluidtype, & integer, parameter :: n_files = 1 ! the library only works on 1 set of phantom particles integer(kind=1), dimension(np) :: ifiles - ! Tmp : do_nucleation and nucleation will need to be intent(in) - logical, parameter :: do_nucleation = .false. - real(kind=dp), dimension(:,:), allocatable :: nucleation + real(kind=dp), dimension(:,:), allocatable :: nucleation, dust_moments logical, intent(in), optional :: write_T_files @@ -233,12 +231,13 @@ subroutine run_mcfost_phantom(np,nptmass,ntypes,ndusttypes,dustfluidtype, & real :: rand, time, cpu_time_begin, cpu_time_end integer :: n_SPH, icell, nbre_phot2, ibar, id, nnfot1_cumul, i_SPH, i, lambda_seuil integer :: itime, ieos, alloc_status - logical :: lpacket_alive, lintersect, laffichage, flag_star, flag_scatt, flag_ISM + logical :: lpacket_alive, lintersect, laffichage, flag_star, flag_scatt, flag_ISM, ldust_moments integer, target :: lambda, lambda0 integer, pointer, save :: p_lambda logical, save :: lfirst_time = .true. + ldust_moments = .false. ! for now ! We use the phantom_2_mcfost interface with 1 file ifiles(:) = 1 ; massoftype2(1,:) = massoftype(:) @@ -256,17 +255,17 @@ subroutine run_mcfost_phantom(np,nptmass,ntypes,ndusttypes,dustfluidtype, & ierr = 0 mu_gas = mu ! Molecular weight - call phantom_2_mcfost(np,nptmass,ntypes,ndusttypes,do_nucleation,n_files,dustfluidtype,xyzh,& + call phantom_2_mcfost(np,nptmass,ntypes,ndusttypes,ldust_moments,n_files,dustfluidtype,xyzh,& vxyzu,T_gas,iphase,grainsize,dustfrac(1:ndusttypes,np),nucleation,massoftype2(1,1:ntypes),& xyzmh_ptmass,vxyz_ptmass,hfact,umass,utime,udist,graindens,ndudt,dudt,ifiles,& n_SPH,x_SPH,y_SPH,z_SPH,h_SPH,vx_SPH,vy_SPH,vz_SPH,Tgas_SPH,particle_id,& - SPH_grainsizes,massgas,massdust,rhogas,rhodust,extra_heating,ieos) + SPH_grainsizes,massgas,massdust,rhogas,rhodust,dust_moments,extra_heating,ieos) if (.not.lfix_star) call compute_stellar_parameters() ! Performing the Voronoi tesselation & defining density arrays call SPH_to_Voronoi(n_SPH, ndusttypes, particle_id, x_SPH,y_SPH,z_SPH,h_SPH,vx_SPH,vy_SPH,vz_SPH,Tgas_SPH, & - massgas,massdust,rhogas,rhodust,SPH_grainsizes, SPH_limits, .false., is_ghost) + massgas,massdust,rhogas,rhodust,SPH_grainsizes, SPH_limits, .false., is_ghost, ldust_moments, dust_moments) call setup_grid() call setup_scattering()