class CodeRunner class Gs2 MAX_NAME_SIZE = 310 def warning(message) eputs "Warning: " + message; sleep 0.3 end class InputFileError < StandardError end def error(message) raise InputFileError.new("Error: " + message) end def test_failed(namelist, var, gs2_var, tst) return <<EOF --------------------------- Test Failed --------------------------- Namelist: #{namelist} Variable: #{var} GS2 Name: #{gs2_var} Value: #{send(var)} Test: #{tst[:test]} Explanation: #{tst[:explanation]} --------------------------- EOF end def namelist_test_failed(namelist, tst) return <<EOF --------------------------- Test Failed --------------------------- Namelist: #{namelist} Test: #{tst[:test]} Explanation: #{tst[:explanation]} --------------------------- EOF end # Checks input parameters for inconsistencies and prints a report. def run_namelist_tests(namelist, hash, enum = nil) ext = enum ? "_#{enum}" : "" hash[:must_pass].each do |tst| error(namelist_test_failed(namelist, tst)) unless instance_eval(tst[:test]) end if hash[:must_pass] hash[:should_pass].each do |tst| warning(namelist_test_failed(namelist, tst)) unless instance_eval(tst[:test]) end if hash[:should_pass] hash[:variables].each do |var, var_hash| #gs2_var = (var_hash[:gs2_name] or var) cr_var = var+ext.to_sym value = send(cr_var) if value.kind_of? Array value.each{|v| test_variable(namelist, var, var_hash, ext, v)} else test_variable(namelist, var, var_hash, ext, value) end end end def test_variable(namelist, var, var_hash, ext, value) gs2_var = (var_hash[:gs2_name] or var) cr_var = var+ext.to_sym if value and (not var_hash[:should_include] or eval(var_hash[:should_include])) var_hash[:must_pass].each do |tst| error(test_failed(namelist, cr_var, gs2_var, tst)) unless value.instance_eval(tst[:test]) end if var_hash[:must_pass] var_hash[:should_pass].each do |tst| warning(test_failed(namelist, cr_var, gs2_var, tst)) unless value.instance_eval(tst[:test]) end if var_hash[:should_pass] if (var_hash[:allowed_values] or var_hash[:text_options]) tst = {test: "#{(var_hash[:allowed_values] or var_hash[:text_options]).inspect}.include? self", explanation: "The variable must have one of these values"} error(test_failed(namelist, cr_var, gs2_var, tst)) unless value.instance_eval(tst[:test]) end end end # Eventually, this will be a full port of the ingen tool in the GS2 folder. At the moment it runs a limited set of tests for common errors in the input parameters (including type checking). def check_parameters # Sections # Namelist Tests # Grids # Parallelisation # Initialisation # Diagnostics # Misc ################## # Namelist Tests # ################## rcp.namelists.each do |namelist, hash| next if hash[:should_include].kind_of? String and not eval(hash[:should_include]) if en = hash[:enumerator] #ep 'en', en, namelist next unless send(en[:name]) send(en[:name]).times do |i| run_namelist_tests(namelist, hash, i+1) end else run_namelist_tests(namelist, hash) end end ############### # Grid Errors # ############### # naky warning("Setting naky when non-linear mode is on is not recommended.") if @naky and @nonlinear_mode == "on" warning("You have set both ny and naky; naky will override ny.") if @ny and @naky error("abs(shat) should not be less that 1.0e-6") if @shat and @shat.abs < 1.0e-6 and not agk? error("abs(s_hat_input) should not be less that 1.0e-6") if @s_hat_input and @s_hat_input.abs < 1.0e-6 and not agk? # delt error("Please specify delt") unless @delt error("delt <= 0") if @delt <= 0.0 warning("Nonlinear run with delt_minimum unspecified.") if @nonlinear_mode=="on" and not @delt_minimum error("delt (#@delt) < delt_minimum") if @delt and @delt_minimum and @delt < @delt_minimum # negrid warning('negrid < 8 is not a good idea!') if @negrid and @negrid < 8 # nakx warning("You have set both nx and ntheta0; ntheta0 will override nx.") if @nx and @ntheta0 warning("Do you have a reason for setting equal_arc = true (default)? If not set false.") if @equilibrium_option=="eik" and (!@equal_arc or @equal_arc.fortran_true?) warning("Recommend nperiod > 1 for linear runs.") if @nonlinear_mode == "off" and (!@nperiod or @nperiod == 1) warning("Recommend nperiod = 1 for nonlinear runs.") if @nonlinear_mode == "on" and (@nperiod > 1) warning("Consider using field_option = local and associated optimizations.") if @field_option and @field_option == "implicit" ################################# # Parallelisation/Layout Errors # ################################# # Best linear run layout is lexys warning("The best layout for linear runs is usually lexys.") if @nonlinear_mode=="off" and not @layout=="lexys" # Best nonlinear run layout is xyles warning("The best layout for nonlinear runs is usually xyles.") if @nonlinear_mode=="on" and not @layout=="xyles" # Check whether we are parallelising over x warning("Parallelising over x: suggest total number of processors should be: #{max_nprocs_no_x}") if actual_number_of_processors > max_nprocs_no_x and not @grid_option == "single" ######################### # Initialisation Errors # ######################### # Check if restart folder exists if @restart_file and @restart_file =~ /^(?<folder>[^\/]+)\// folder = $~[:folder] warning("Folder #{folder}, specified in restart_file, not present. NetCDF save may fail") unless FileTest.exist?(folder) end error("Setting @restart_file as an empty string will result in hidden restart files.") if @restart_file == "" error("ginit_option is 'many' but is_a_restart is false") if @ginit_option == "many" and not @is_a_restart error("read_response is 'true' but run is not a restart. Make sure the "\ "@response_id is set to a run with response files.") if @read_response and @read_response.fortran_true? and not @is_a_restart and not @response_id error("chop_side should not be used (remove test if default changes from T to F)") if !@chop_side or @chop_side.fortran_true? ##################### # Diagnostic errors # ##################### #Check whether useful diagnostics have been omitted. not_set = [:write_verr, :save_for_restart, :write_nl_flux, :write_final_fields, :write_final_moments].find_all do |diagnostic| not (send(diagnostic) and send(diagnostic).fortran_true?) end if not_set.size > 0 str = not_set.inject("") do |s, diagnostic| s + "\n\t#{diagnostic} --- " + rcp.namelists[diagnostics_namelist][:variables][diagnostic][:description] rescue s end warning("The following useful diagnostics were not set:" + str) if str.length > 0 end warning("You are running in nonlinear mode but have not switched the nonlinear flux diagnostic.") if not (@write_nl_flux and @write_nl_flux.fortran_true?) and @nonlinear_mode == "on" #{ #write_verr: "Velocity space diagnostics will not be output for this run" #}.each do |var, warn| #warning(v"#{var} not set or .false. --- " + warn) unless send(var) and send(var).fortran_true? #end error("Please specify nwrite") unless @nwrite error("Please specify nstep") unless @nstep warning("You will write out diagnostics less than 50 times") if @nstep/@nwrite < 50 ######################## # Miscellaneous errors # ######################## error("The run name for this run is too long. Please move some of the variable settings to the local defaults file.") if @relative_directory.size + @run_name.size > MAX_NAME_SIZE warning("You are submitting a nonlinear run with no dissipation.") if @nonlinear_mode == "on" and @hyper_option=="none" and @collision_model=="none" warning("You have no spatial implicitness: (bakdif) for one of your species. Be prepared for numerical instabilities!") if (1..@nspec).to_a.find{|i| bd = send("bakdif_#{i}") and bd == 0} warning("The system will abort with rapid timestep changes...") if !@abort_rapid_time_step_change or @abort_rapid_time_step_change.fortran_true? warning("local_field_solve is an old variable that should not really be used.") if @local_field_solve and @local_field_solve.fortran_true? ############################# # Boundary Condition Errors # ############################# warning("Boundary option should be periodic for shat = 1e-6.") if (!@boundary_option or @boundary_option != "periodic") and ((@s_hat_input and @s_hat_input.abs == 1.0e-6) or (@shat and @shat.abs == 1.0e-6)) warning("Boundary option should be default (unconnected) for single and range mode with shat > 0.") if (@boundary_option != "default") and ((@s_hat_input and @s_hat_input.abs > 1.0e-6) or (@shat and @shat.abs > 1.0e-6)) and (@grid_option == "single" or @grid_option == "range") warning("Boundary option should be linked for box mode with shat > 0.") if (!@boundary_option or @boundary_option != "linked") and ((@s_hat_input and @s_hat_input.abs > 1.0e-6) or (@shat and @shat.abs > 1.0e-6)) and @grid_option == "box" error("Set nonad_zero = true.") if @nonad_zero and not @nonad_zero.fortran_true? ################### # Spectrogk tests # ################### # if spectrogk? if @force_5d and @force_5d.fortran_true? warning("Must specify interpolation method with phi_method.") if not (@phi_method) end end ################ # Damping Rate # ################ error("Linear runs with hyperviscosity are NOT recommended!") if @nonlinear_mode=="off" and (@hyper_option and @hyper_option=="visc_only") and (@d_hypervisc and @d_hypervisc!=0) warning("Amplitude dependent part of hyperviscosity being ignored since const_amp = true") if (@hyper_option and @hyper_option=="visc_only") and (@const_amp and @const_amp.fortran_true?) ################### # Geometry Errors # ################### error("You must set bishop = 4 for Miller(local) geometry. Remember also that s_hat_input will override shat") if (@bishop!=4 and (@local_eq and @local_eq.fortran_true?)) error("Shift should be > 0 for s-alpha equilibrium.") if @equilibrium_option=="s-alpha" and (@shift and @shift < 0) error("Shift should be < 0 for Miller equilibrium.") if @equilibrium_option=="eik" and @local_eq.fortran_true? and (@shift and @shift > 0) error("irho must be 2 for Miller equilibrium.") if @equilibrium_option=="eik" and @local_eq.fortran_true? and (@irho and @irho!=2) warning("Note that shat != s_hat_input") if @shat and @s_hat_input and @shat!=@s_hat_input ################## # Species Errors # ################## error("Must set z = -1 for electron species.") if (@type_2 and @z_2 and @type_2=='electron' and @z_2 != -1) ################# # Optimisations # ################# if CODE_OPTIONS[:gs2] and CODE_OPTIONS[:gs2][:show_opt] eputs("Optimisation Summary:") optimisation_flags.each do |flag| eputs("------------------------- #{flag}: #{send(flag)}\n* #{rcp.variables_with_help[flag].gsub(/\n/, "\n\t").sub(/\A([^.]*.).*\Z/m, '\1')}") end #not_set = [:operator, :save_for_restart, :write_nl_flux, :write_final_fields, :write_final_moments].find_all do |diagnostic| #not (send(diagnostic) and send(diagnostic).fortran_true?) #end #if not_set.size > 0 #str = not_set.inject("") do |s, diagnostic| #s + "\n\t#{diagnostic} --- " + rcp.namelists[diagnostics_namelist][:variables][diagnostic][:description] rescue s #end #warning("The following useful diagnostics were not set:" + str) if str.length > 0 #end end end def optimisation_flags [ :opt_redist_persist, :opt_redist_persist_overlap, :opt_redist_nbk, :opt_redist_init, :intmom_sub, :intspec_sub, #:local_field_solve, :do_smart_update, :field_subgath, :field_option, :field_local_allreduce, :field_local_allreduce_sub, :minnrow, :opt_init_bc, :opt_source ] end # A hash which gives the actual numbers of gridpoints indexed by their corresponding letters in the layout string. def gridpoints gridpoints = {'l' => @ngauss, 'e' => @negrid, 's' => @nspec} if @grid_option == "single" gridpoints.absorb({'x'=>1, 'y'=>1}) else gridpoints.absorb({'x' => (@ntheta0 or (2.0 * (@nx - 1.0) / 3.0 + 1.0).floor), 'y' => (@naky or ((@ny - 1.0) / 3.0 + 1.0).floor)}) end return gridpoints end def cumulative_gridpoints c = 1 error("Please specify layout") unless @layout @layout.split(//).reverse.inject({}){|hash, let| c*=gridpoints[let]; hash[let] = c; hash} end # ep parallelisation def max_nprocs_no_x parallelisation = cumulative_gridpoints parallelisation[parallelisation.keys[parallelisation.keys.index('x') - 1]] end def diagnostics_namelist :gs2_diagnostics_knobs end # Run the ingen tool on the input file def ingen Dir.chdir(@directory) do ing = File.dirname(File.expand_path(@executable)) + '/ingen' success = system "#{ing} #@run_name.in" warning("Could not run ingen... make sure that ingen is in the same folder as @executable and can be run on the login nodes if you want this to work") unless success end end end end