diff --git a/input/gray.ini b/input/gray.ini new file mode 100644 index 0000000..1a2dd97 --- /dev/null +++ b/input/gray.ini @@ -0,0 +1,190 @@ +[raytracing] +; Number of rays in the radial/angular direction +nrayr = 1 +nrayth = 16 +; Normalized maximum radius of the beam power +rwmax = 1 + +; Switch between simple raytracing (0) and beamtracing (1) +igrad = 0 + +; Max number of passes inside the plasma [multipass module] +; When positive reflections occur on the plasma limiter provided by the +; G-EQDSK file; when negative on a simple limiter at R=`rwall`, see below. +ipass = 1 + +; Whether to compute the wave polarisation +ipol = 0 +; Step size (cm) for the numerical integration +dst = 0.1 +; Max number of integration steps +nstep = 12000 + +; Choice of the integration variable +; 0: path length (s) +; 1: "time" (actually c⋅t) +; 2: real part of the eikonal (S_r) +idst = 0 + + +[ecrh_cd] +; Choice of the power absorption model +; 0: no absorption at all +; 1: weakly relativistic +; 2: fully relativistic (faster variant) +; 3: fully relativistic (slower variant) +; 4: tenuous plasma (very fast) +; Note: iwarm>0 is required for current driver +iwarm = 2 + +; Order of the electron Larmor radius expansion +; (used by some absorption models) +ilarm = 5 + +; Max number of iterations for the solution of the dispersion relation. +; (used by some absorption models) +; Note: if negative the result of the first iteration will be used in +; case the result doesn't converge within |imx| iterations. +imx = -20 + +; Current drive model +; 0: no current drive at all +; 1: Cohen +; 2: no trapping +; 3: Neoclassical +ieccd = 3 + + +[antenna] +; Wave launch angles (deg) +alpha = 45 ; Poloidal angle (positive → up) +beta = 0 ; Toroidal angle (positive → right) + +; Injected power (MW) +power = 1 + +; Polarisation mode +; 1: ordinary (O) +; 2: extraordinary (X) +iox = 1 +; Alternatively, parameters of the polarisation ellipse +; χ: angle between the principal axes and the (x,y) axes +; ψ: atan(ε), where ε is the ellipticity +chi = 0 +psi = 0 + +; Beam description kind +; 0: simple beam shape +; 1: 1D table +; 2: 2D table +ibeam = 0 + +; Filepath of the beam data (relative to this file) +filenm = "beamdata.txt" + + +[equilibrium] +; MHD equilibrium kind +; 0: vacuum (i.e. no plasma at all) +; 1: analytical +; 2: G-EQDSK format - data valid on the whole domain +; 3: G-EQDSK format - data valid only inside the LCFS +iequil = 3 + +; Filepath of the equilibrium data (relative to this file) +filenm = "magneticdata.eqdsk" +; COCOS index +icocos = 0 + +; Normalisation of the poloidal function +; 0: G-EQDSK, ψ → |ψ - ψ(edge)|/|ψ(axis) - ψ(edge)| +; 1: no normalisation +ipsinorm = 0 + +; G-EQDSK format parameters +; Whether header starts with a description, a.k.a identification string +idesc = 1 +; Whether the records have variable length +; Note: some non-compliant programs output numbers formatted with variable length +; instead of using the single (5e16.9) specifier. +ifreefmt = 0 + +; Position of the X point +; -1: bottom +; 0: no X point +; +1: top +ixp = 0 + +; Tension of splines +; Note: 0 means perfect interpolation +ssplps = 0.005 ; for ψ(R,Z), normalised poloidal flux +ssplf = 0.01 ; for I(ψ)=R⋅B_T, poloidal current function + +; Sign of toroidal field/current (used when COCOS index is 0,10) +; When viewing from above: +1 → counter-clockwise, -1 → clockwise +sgnb = -1 +sgni = +1 +; Rescaling factor for the magnetic field +factb = 1 + + +[profiles] +; (input) plasma profiles parameters + +; Profiles kind +; 0: analytical +; 1: numerical +iprof = 1 + +; Profile radial coordinate +; 0: ρ_t = √Φ (where Φ is the normalised toroidal flux) +; 1: ρ_p = √ψ (where ψ is the normalised poloidal flux) +; 2: normalised poloidal flux ψ +irho = 0 + +; Filepath of the equilibrium (relative to this file) +filenm = "profiles.txt" + +; Value of ψ at the plasma boundary [multipass module] +; Notes: +; 1. boundary means zero density; +; 2. determines when a ray is considered inside the plasma +psnbnd = 1.007 + +; Tension of the density spline +; Note: 0 means perfect interpolation +sspld = 0.1 + +; Rescaling factor for electron temperature/density +factte = 1 +factne = 1 + +; Choice of model for rescaling the temperature/density with the +; magnetic field +; 1: costant Greenwald density (ab=1) +; 2: no rescaling (ab=0) +iscal = 2 + + +[output] +; Output data parameters + +; ECRH&CD profiles grid: +; Radial coordinate +; 1: ρ_p = √ψ (where ψ is the normalised poloidal flux) +; 2: ρ_t = √Φ (where Φ is the normalised toroidal flux) +ipec = 1 +; Number of points +nrho = 501 + +; Subsampling factors: +istpr = 5 ; beam cross section (units 8, 12) +istpl = 5 ; outer rays data (unit 33) + + +[misc] +; Other parameters + +; Radius of the inner wall (m) [multipass module] +; (when ipass<0, used to build a simple limiter for reflections) +rwall = 1.36 diff --git a/src/gray_cli.f90 b/src/gray_cli.f90 index a2ed916..f720bcd 100644 --- a/src/gray_cli.f90 +++ b/src/gray_cli.f90 @@ -15,6 +15,7 @@ module gray_cli ! Files character(len=:), allocatable :: output_dir character(len=:), allocatable :: params_file + character(len=:), allocatable :: config_file character(len=:), allocatable :: sum_filelist ! others integer :: verbose @@ -47,13 +48,15 @@ contains print '(a)', ' -q, --quiet suppress all non-critical messages' print '(a)', ' -o, --output-dir DIR specify where to write the output files' print '(a)', ' (default: current directory)' - print '(a)', ' -p, --params-file FILE set the parameters file' + print '(a)', ' -p, --params-file FILE set the (legacy) parameters file' print '(a)', ' (default: gray_params.data)' + print '(a)', ' -c, --config-file FILE set the (new) GRAY config file' + print '(a)', ' (default: gray.ini)' print '(a)', ' -s, --sum FILE sum the output profiles from a list of files' print '(a)', ' -u, --units ID[,ID...] select which units to output (default: 4, 7);' print '(a)', ' see the manual for all unit IDs.' print '(a)', ' -g, --gray-param ID=VAL set a GRAY parameter, overriding the value' - print '(a)', ' specified via --params-file;' + print '(a)', ' specified via --params-file/--config-file;' print '(a)', ' the ID is GROUP.NAME, ex. antenna.fghz;' print '(a)', ' see the manual for available parameters.' print '(a)', '' @@ -155,6 +158,10 @@ contains call get_command_string(i + 1, opts%params_file) skip_next = .true. + case ('-c', '--config-file') + call get_command_string(i + 1, opts%config_file) + skip_next = .true. + case ('-s', '--sum') call get_command_string(i + 1, opts%sum_filelist) skip_next = .true. @@ -201,6 +208,7 @@ contains ! Reads GRAY parameters from CLI and overrides `params` accordingly use gray_params, only : gray_parameters, update_parameter + use ini_parser, only : ERR_VALUE, ERR_UNKNOWN implicit none @@ -211,7 +219,7 @@ contains character(len=:), allocatable :: argument, temp, id, val logical :: skip_next = .false. integer :: i, nargs - integer :: error, sep + integer :: sep nargs = command_argument_count() do i = 1, nargs @@ -242,12 +250,12 @@ contains ! match the name string to a parameter select case (update_parameter(params, id, val)) - case (1) + case (ERR_VALUE) print '(4a)', 'invalid value for ', id, ': ', val deallocate(temp) call exit(1) - case (2) + case (ERR_UNKNOWN) print '(a,a)', 'unknown GRAY parameter: ', id deallocate(temp) call exit(1) @@ -297,6 +305,7 @@ contains if (allocated(opts%output_dir)) deallocate(opts%output_dir) if (allocated(opts%params_file)) deallocate(opts%params_file) + if (allocated(opts%config_file)) deallocate(opts%config_file) if (allocated(opts%sum_filelist)) deallocate(opts%sum_filelist) if (allocated(opts%units)) deallocate(opts%units) end subroutine deinit_cli_options diff --git a/src/gray_jetto1beam.f90 b/src/gray_jetto1beam.f90 index 4d5475a..999ba6e 100644 --- a/src/gray_jetto1beam.f90 +++ b/src/gray_jetto1beam.f90 @@ -31,8 +31,8 @@ subroutine gray_jetto1beam(ijetto, mr, mz, r, z, psin, psia, rax, zax, & ! Read parameters from external file init_params: block - use gray_params, only : read_parameters, set_globals - call read_parameters('gray.data', params) + use gray_params, only : read_gray_params, set_globals + call read_gray_params('gray.data', params) ! Override some parameters params%misc%rwall = r(1) diff --git a/src/gray_params.f90 b/src/gray_params.f90 index dac47be..99b631f 100644 --- a/src/gray_params.f90 +++ b/src/gray_params.f90 @@ -25,31 +25,46 @@ module gray_params ! MHD equilibrium parameters type equilibrium_parameters - real(wp_) :: ssplps, ssplf, factb - integer :: sgnb, sgni, ixp - integer :: iequil, icocos, ipsinorm, idesc, ifreefmt - character(len=lenfnm) :: filenm + real(wp_) :: ssplps, ssplf ! Spline tensions for ψ(R,Z), I(ψ) + real(wp_) :: factb ! Rescaling factor for B + integer :: sgnb, sgni ! Sign of B, I + integer :: ixp ! Position of X point + integer :: iequil ! Equilibrium kind + integer :: icocos ! COCOS index + integer :: ipsinorm ! Normalisation of ψ + integer :: idesc, ifreefmt ! G-EQDSK quirks + character(len=lenfnm) :: filenm ! Equilibrium data filepath end type ! Kinetic plasma profiles type profiles_parameters - real(wp_) :: psnbnd ! plasma boundary (ψ: ne(ψ)=0) - real(wp_) :: sspld, factne, factte - integer :: iscal, irho - integer :: iprof - character(len=lenfnm) :: filenm + real(wp_) :: psnbnd ! Plasma boundary (ψ: ne(ψ)=0) + real(wp_) :: sspld ! Density spline tension + real(wp_) :: factne, factte ! Rescale factors for n_e, T_e + integer :: iscal ! Rescaling model + integer :: irho ! Radial coordinate + integer :: iprof ! Profile kind + character(len=lenfnm) :: filenm ! Profiles data filepath end type ! Raytracing parameters type raytracing_parameters - real(wp_) :: rwmax, dst - integer :: nrayr, nrayth, nstep - integer :: igrad, idst, ipass, ipol + real(wp_) :: dst ! Integration step size + real(wp_) :: rwmax ! Normalized maximum radius of beam power + integer :: nrayr, nrayth ! Radial/angular number of rays + integer :: igrad ! Complex eikonal switch + integer :: nstep ! Max number of integration steps + integer :: idst ! Choice of the integration variable + integer :: ipass ! Number of plasma passes + integer :: ipol ! Whether to compute wave polarisation end type ! EC resonant heating & Current Drive parameters type ecrh_cd_parameters - integer :: iwarm, ilarm, imx, ieccd + integer :: iwarm ! Choice of power absorption model + integer :: ieccd ! Choice of current drive model + integer :: ilarm ! Larmor-radius expansion order + integer :: imx ! Max iterations for solving the dispersion relation end type ! Output data parameters @@ -231,28 +246,71 @@ contains function update_parameter(params, name, value) result(error) ! Updates the value of a parameter, addressed by a string ! The return error is: - ! 0 on success; - ! 1 on invalid parameter value; - ! 2 on unknown parameter name. + ! ERR_SUCCESS on success; + ! ERR_VALUE on invalid parameter value; + ! ERR_UNKNOWN on unknown parameter name. ! ! Ex. update_parameter(params, 'raytracing.nrayr', '10') + use ini_parser, only : ini_error, ERR_SUCCESS, ERR_VALUE, ERR_UNKNOWN + implicit none ! function arguments - type(gray_parameters), intent(inout) :: params - character(len=:), allocatable, intent(in) :: name, value - integer :: error + type(gray_parameters), intent(inout) :: params + character(*), intent(in) :: name, value + integer(kind(ini_error)) :: error + + ! hope for the best... + error = ERR_SUCCESS select case (name) #include "gray_params.inc" case default - error = 2 + ! unknown parameter + error = ERR_UNKNOWN end select end function update_parameter - subroutine read_parameters(filename, params, unit) + subroutine read_gray_config(filename, params) + ! Reads the GRAY parameters from the gray.ini configuration file + use ini_parser, only : parse_ini, property_handler, ini_error, ERR_SUCCESS + + implicit none + + ! subroutine arguments + character(len=*), intent(in) :: filename + type(gray_parameters), intent(out) :: params + + ! local variables + procedure(property_handler), pointer :: p + integer :: error + + p => ini_handler + call parse_ini(filename, p, error) + if (error /= ERR_SUCCESS) call exit(1) + + contains + + function ini_handler(section, name, value) result(error) + ! This function handles a single INI property and updates + ! the `params` structure + + implicit none + + ! function arguments + character(*), intent(in) :: section, name, value + integer(kind(ini_error)) :: error + + error = update_parameter(params, section // "." // name, value) + end function ini_handler + + end subroutine read_gray_config + + + subroutine read_gray_params(filename, params) + ! Reads the GRAY parameters from the legacy gray_params.data file use utils, only : get_free_unit use logger, only : log_error @@ -261,125 +319,57 @@ contains ! subrouting arguments character(len=*), intent(in) :: filename type(gray_parameters), intent(out) :: params - integer, intent(in), optional :: unit ! local variables integer :: u, err - u = get_free_unit(unit) + u = get_free_unit() open(u, file=filename, status='old', action='read', iostat=err) if (err /= 0) then call log_error('opening gray_params file ('//filename//') failed!', & - mod='gray_params', proc='read_parameters') + mod='gray_params', proc='read_gray_params') call exit(1) end if - ! Raytracing - ! ======================================================================== - ! nrayr :number of rays in radial direction - ! nrayth :number of rays in angular direction - ! rwmax :normalized maximum radius of beam power - ! rwmax=1 -> :last ray at radius = waist - read(u, *) params%raytracing%nrayr, params%raytracing%nrayth, params%raytracing%rwmax - ! igrad=0 :optical ray-tracing, initial conditions as for beam - ! igrad=1 :quasi-optical ray-tracing - ! igrad=-1 :ray-tracing, init. condit. - ! from center of mirror and with angular spread - ! ipass=1/2 :1 or 2 passes into plasma - ! ipol=0 :compute mode polarization at antenna, ipol=1 use polariz angles - read(u, *) params%raytracing%igrad, params%raytracing%ipass, params%raytracing%ipol - ! dst :integration step - ! nstep :maximum number of integration steps - ! idst=0/1/2 :0 integration in s, 1 integr. in ct, 2 integr. in Sr - read(u, *) params%raytracing%dst, params%raytracing%nstep, params%raytracing%idst + read(u, *) params%raytracing%nrayr, params%raytracing%nrayth, & + params%raytracing%rwmax + read(u, *) params%raytracing%igrad, params%raytracing%ipass, & + params%raytracing%ipol + read(u, *) params%raytracing%dst, params%raytracing%nstep, & + params%raytracing%idst - ! Heating & Current drive - ! ======================================================================== - ! iwarm=0 :no absorption and cd - ! iwarm=1 :weakly relativistic absorption - ! iwarm=2 :relativistic absorption, n<1 asymptotic expansion - ! iwarm=3 :relativistic absorption, numerical integration - ! ilarm :order of larmor expansion - ! imx :max n of iterations in dispersion, imx<0 uses 1st - ! iteration in case of failure after |imx| iterations - read(u, *) params%ecrh_cd%iwarm,params%ecrh_cd%ilarm,params%ecrh_cd%imx - ! ieccd 0/1 NO/YES ECCD calculation ieccd>0 different CD models + read(u, *) params%ecrh_cd%iwarm, params%ecrh_cd%ilarm, params%ecrh_cd%imx read(u, *) params%ecrh_cd%ieccd - ! Wave launcher - ! ======================================================================== - ! alpha0, beta0 (cartesian) launching angles read(u, *) params%antenna%alpha, params%antenna%beta - ! p0mw injected power (MW) read(u, *) params%antenna%power - ! abs(iox)=1/2 OM/XM - ! psipol0,chipol0 polarization angles at the antenna (if iox<0) read(u, *) params%antenna%iox, params%antenna%psi, params%antenna%chi read(u, *) params%antenna%ibeam read(u, *) params%antenna%filenm - ! MHD equilibrium - ! ======================================================================== - ! iequil=0 :vacuum (no plasma) - ! iequil=1 :analytical equilibrium - ! iequil=2 :read eqdsk - ! iequil=2 :read eqdsk, data only valid inside last closed flux surface read(u, *) params%equilibrium%iequil read(u, *) params%equilibrium%filenm - ! icocos :index for equilibrium from COCOS - O. Sauter Feb 2012 - ! ipsinorm :0 standard EQDSK format, 1 format Portone summer 2004 - read(u, *) params%equilibrium%icocos, params%equilibrium%ipsinorm, params%equilibrium%idesc, params%equilibrium%ifreefmt - ! ixp=0,-1,+1 : no X point , bottom/up X point - ! ssplps : spline parameter for psi interpolation - read(u, *) params%equilibrium%ixp, params%equilibrium%ssplps !, params%equilibrium%ssplf - params%equilibrium%ssplf=0.01_wp_ - ! signum of toroidal B and I - ! factb factor for magnetic field (only for numerical equil) - ! scaling adopted: beta=const, qpsi=const, nustar=const - read(u, *) params%equilibrium%sgnb, params%equilibrium%sgni, params%equilibrium%factb + read(u, *) params%equilibrium%icocos, params%equilibrium%ipsinorm, & + params%equilibrium%idesc, params%equilibrium%ifreefmt + read(u, *) params%equilibrium%ixp, params%equilibrium%ssplps, & + params%equilibrium%ssplf + read(u, *) params%equilibrium%sgnb, params%equilibrium%sgni, & + params%equilibrium%factb - ! Wall - ! ======================================================================== read(u, *) params%misc%rwall - ! Profiles - ! ======================================================================== - ! iprof=0 :analytical density and temp. profiles - ! iprof>0 :numerical density and temp. profiles - read(u, *) params%profiles%iprof, params%profiles%irho ! irho=0,1,2 -> num profiles vs rhot,rhop,psin + read(u, *) params%profiles%iprof, params%profiles%irho read(u, *) params%profiles%filenm - ! psbnd value of psi ( > 1 ) of density boundary read(u, *) params%profiles%psnbnd, params%profiles%sspld - ! prfparam%sspld=0.001_wp_ - ! iscal :ne Te scaling 0: nustar=const, 1: n_greenw=const; 2 no rescaling - ! factT factn :factor for Te&ne scaling - read(u, *) params%profiles%factte, params%profiles%factne, params%profiles%iscal + read(u, *) params%profiles%factte, params%profiles%factne, & + params%profiles%iscal - ! Output - ! ======================================================================== - ! ipec=0/1 :pec profiles grid in psi/rhop - ! nrho :number of grid steps for pec profiles +1 read(u, *) params%output%ipec, params%output%nrho - ! istpr0 :projection step = dsdt*istprj - ! istpl0 :plot step = dsdt*istpl read(u, *) params%output%istpr, params%output%istpl close(u) - - ! Convert all filenames to absolute paths - block - use utils, only : dirname, isrelative - if (isrelative(params%antenna%filenm)) & - params%antenna%filenm = dirname(filename) // params%antenna%filenm - - if (isrelative(params%equilibrium%filenm)) & - params%equilibrium%filenm = dirname(filename) // params%equilibrium%filenm - - if (isrelative(params%profiles%filenm)) & - params%profiles%filenm = dirname(filename) // params%profiles%filenm - end block - end subroutine read_parameters + end subroutine read_gray_params subroutine set_globals(params) diff --git a/src/gray_params.sh b/src/gray_params.sh index fa0b035..ea0a9e1 100644 --- a/src/gray_params.sh +++ b/src/gray_params.sh @@ -24,7 +24,7 @@ for set in $sets; do cat < 0) error = 1 + if (error > 0) error = ERR_VALUE EOF done done diff --git a/src/ini_parser.f90 b/src/ini_parser.f90 new file mode 100644 index 0000000..e717ee7 --- /dev/null +++ b/src/ini_parser.f90 @@ -0,0 +1,193 @@ +! This module provides a minimal INI parser +! +! The format is: +! +! ; comment +! [section-name] +! property-name = property-value ; comment +! +! The `parse_ini` subroutine takes a file and a handler +! function that is called with the section, name and value +! of each property in the INI file. +! +module ini_parser + + use logger, only : log_error + + ! INI syntax constants + character, parameter :: comment_sign = ';' + character, parameter :: property_sep = '=' + character, parameter :: section_start = '[' + character, parameter :: section_stop = ']' + + ! Errors + enum, bind(C) + enumerator :: ini_error = -1 + enumerator :: ERR_SUCCESS = 0 ! no errors + enumerator :: ERR_SYNTAX = 1 ! syntax error in the INI file + enumerator :: ERR_VALUE = 2 ! invalid value for a property + enumerator :: ERR_UNKNOWN = 3 ! unknown property name + enumerator :: ERR_IO = 4 ! I/O error + end enum + + abstract interface + function property_handler(section, name, value) result(error) + character(*), intent(in) :: section, name, value + integer(kind(ini_error)) :: error + end function + end interface + + private + public parse_ini + public property_handler + public ini_error, ERR_SUCCESS, ERR_SYNTAX, ERR_VALUE, ERR_UNKNOWN, ERR_IO + +contains + + subroutine parse_ini(filepath, handler, error) + ! Parses a INI file + ! filepath: path of the INI file to pase + ! handler: handler function + ! + ! The handler must have the following signature: + ! + ! function handler(section, name, value) result(error) + ! + ! where the error should be: + ! ERR_SUCCESS on success; + ! ERR_VALUE on invalid values for this property; + ! ERR_UNKNOWN on unknown property. + ! + use utils, only : get_free_unit + + implicit none + + ! function argument + character(*), intent(in) :: filepath + procedure(property_handler), pointer, intent(in) :: handler + integer(kind(ini_error)), intent(out) :: error + + ! local variables + integer :: ini, sep, n + character(256) :: msg + character(len=:), allocatable :: line + character(len=:), allocatable :: section, name, value + + ! open the INI file + ini = get_free_unit() + open(unit=ini, file=filepath, iostat=error) + if (error /= 0) then + write (msg, '("failed to open INI file: ", a)') filepath + call log_error(msg, proc='parse_ini', mod='ini_parser') + error = ERR_IO + return + end if + + n = 1 ! line number + do + ! get one line + call getline(ini, line, error) + if (error /= 0) exit + + ! skip empty lines + if (len(line) == 0) cycle + + ! skip comments + if (line(1:1) == comment_sign) cycle + + ! parse section header + if (line(1:1) == section_start) then + ! split at section stop (ex. [section]) + sep = str_index(line, section_stop) + + if (sep == 0) then + write (msg, '("invalid section header at line ",g0,": ",a)') n, line + call log_error(msg, proc='parse_ini', mod='ini_parser') + error = ERR_SYNTAX + exit + end if + + ! update the current section + section = line(2:sep - 1) + cycle + end if + + ! split line at separator (ex. name=value) + sep = str_index(line, property_sep) + if (sep == 0) then + write (msg, '("invalid property definition at line ",g0,": ",a)') n, line + call log_error(msg, proc='parse_ini', mod='ini_parser') + error = ERR_SYNTAX + exit + end if + name = trim(line(1:sep - 1)) + value = trim(line(sep + 1:)) + + ! call the handler + select case (handler(section, name, value)) + case (ERR_SUCCESS) + cycle + + case (ERR_VALUE) + write (msg, '("invalid value for property ",a,": ", a)') name, value + call log_error(msg, proc='parse_ini', mod='ini_parser') + deallocate(line) + error = ERR_VALUE + exit + + case (ERR_UNKNOWN) + write (msg, '("unknown property ",a)') name + call log_error(msg, proc='parse_ini', mod='ini_parser') + error = ERR_UNKNOWN + exit + end select + end do + + ! parsed the whole file + if (error < 0) error = ERR_SUCCESS + close(ini) + end subroutine parse_ini + + + subroutine getline(unit, line, error) + ! Reads a line into a deferred length string + + ! subroutine arguments + integer, intent(in) :: unit + character(len=:), allocatable, intent(out) :: line + integer, intent(out) :: error + + integer, parameter :: bufsize = 512 + character(len=bufsize) :: buffer + integer :: chunk + + allocate(character(len=0) :: line) + do + read(unit, '(a)', advance='no', iostat=error, size=chunk) buffer + if (error > 0) exit + line = line // buffer(:chunk) + if (error < 0) then + if (is_iostat_eor(error)) error = 0 + exit + end if + end do + end subroutine getline + + + pure function str_index(str, char) result(n) + ! Returns the index of the first occurence of `char` within `str` + ! If not `char` is not found returns 0 + + implicit none + + ! function arguments + character(*), intent(in) :: str + character, intent(in) :: char + + ! local variables + integer i, n + + n = findloc([(str(i:i) == char, i = 1, len(str))], .true., 1) + end function str_index + +end module ini_parser diff --git a/src/main.f90 b/src/main.f90 index 65829be..e87c26e 100644 --- a/src/main.f90 +++ b/src/main.f90 @@ -2,11 +2,14 @@ program main use const_and_precisions, only : wp_, one, zero use logger, only : INFO, ERROR, set_log_level, log_message use units, only : set_active_units, close_units + use utils, only : dirname use gray_cli, only : cli_options, parse_cli_options, & deinit_cli_options, parse_param_overrides use gray_core, only : gray_main use gray_params, only : gray_parameters, gray_data, gray_results, & - read_parameters, params_set_globals => set_globals + read_gray_params, read_gray_config, & + params_set_globals => set_globals + implicit none ! CLI options @@ -18,6 +21,10 @@ program main type(gray_results) :: results ! Outputs integer :: err ! Exit code + ! Store the original working directory + character(len=256) :: cwd + call getcwd(cwd) + ! Parse the command-line options call parse_cli_options(opts) @@ -28,10 +35,28 @@ program main ! Activate the given output units call set_active_units(opts%units) - ! Load the parameters from file, apply CLI - ! overrides, and copy them into global - ! variables exported by the gray_params - call read_parameters(opts%params_file, params) + ! Load the parameters from file and move to its directory + ! (all other filepaths are assumed relative to it) + if (allocated(opts%config_file)) then + ! Use the newer gray.ini configuration file + call log_message(level=INFO, mod='main', msg='using GRAY INI config') + call read_gray_config(opts%config_file, params) + err = chdir(dirname(opts%config_file)) + else + ! Use the legacy gray_params.data file + call log_message(level=INFO, mod='main', msg='using GRAY params file') + call read_gray_params(opts%params_file, params) + err = chdir(dirname(opts%params_file)) + end if + + if (err /= 0) then + call log_message(level=ERROR, mod='main', & + msg='chdir to configuration file directory failed!') + call exit(1) + end if + + ! Apply CLI overrides and copy the parameters into + ! global variables exported by the gray_params module call parse_param_overrides(params) call params_set_globals(params) @@ -42,9 +67,9 @@ program main call init_antenna(params%antenna) call init_misc(params, data) - ! Change the current directory to output files here + ! Change the current directory to output files there if (allocated(opts%output_dir)) then - if (chdir(opts%output_dir) /= 0) then + if (chdir(trim(cwd) // '/' // opts%output_dir) /= 0) then call log_message(level=ERROR, mod='main', & msg='chdir to output_dir ('//opts%output_dir//') failed!') call exit(1) @@ -254,9 +279,9 @@ contains ! Convert psrad to ψ select case (params%irho) - case (0) ! psrad is ρ_t + case (0) ! psrad is ρ_t = √Φ (toroidal flux) data%psrad = frhopolv(data%psrad)**2 - case (1) ! psrad is ρ_p + case (1) ! psrad is ρ_p = √ψ (poloidal flux) data%psrad = data%psrad**2 case default ! psrad is already ψ end select