Class: Inlist
- Inherits:
-
Object
- Object
- Inlist
- Defined in:
- lib/mesa_script.rb
Class Attribute Summary collapse
-
.defaults_files ⇒ Object
Returns the value of attribute defaults_files.
-
.have_data ⇒ Object
Returns the value of attribute have_data.
-
.inlist_data ⇒ Object
Returns the value of attribute inlist_data.
-
.namelists ⇒ Object
Returns the value of attribute namelists.
-
.source_files ⇒ Object
Returns the value of attribute source_files.
Instance Attribute Summary collapse
-
#data_hash ⇒ Object
Making an instance of Inlist first checks to see if the class methods are set up for the namelists in Inlist.namelists.
-
#names ⇒ Object
readonly
Returns the value of attribute names.
Class Method Summary collapse
-
.add_binary_controls_defaults(verbose: false) ⇒ Object
short hand for adding binary_controls_defaults namelist using sensible defaults as of 10108.
-
.add_binary_defaults ⇒ Object
quickly add both major namelists for binary module (binary_job and binary_controls).
-
.add_binary_job_defaults(verbose: false) ⇒ Object
short hand for adding binary_job_defaults namelist using sensible defaults as of 10108.
-
.add_controls_defaults(verbose: false) ⇒ Object
short hand for adding controls namelist using sensible defaults as of 10108.
-
.add_eos_defaults(verbose: false) ⇒ Object
short hand for adding eos namelist using sensible defaults as of 22.11.1.
-
.add_kap_defaults(verbose: false) ⇒ Object
short hand for adding kap namelist using sensible defaults as of 22.11.1.
- .add_namelist(new_namelist) ⇒ Object
-
.add_pgstar_defaults(verbose: false) ⇒ Object
short hand for adding pgstar namelist using sensible defaults as of 10108.
-
.add_star_defaults ⇒ Object
quickly add all five (three for older versions) major namelists for star module (star_job, controls, and pgstar).
-
.add_star_job_defaults(verbose: false) ⇒ Object
short hand for adding star_job namelist using sensible defaults as of 10108.
-
.config_namelist(namelist: nil, source_files: nil, defaults_file: nil, verbose: true) ⇒ Object
used to turn on a namelist; need to provide namelist name as well as locations for source file (usually a .inc or .f90 file that defines allowable controls) and a defaults file (usually a .defaults file that lists all controls and their default values).
-
.delete_all_namelists ⇒ Object
Delete all namelists and associated data.
-
.delete_data(namelist) ⇒ Object
see if data has been loaded, and if it has, delete all the methods associated with it and then wipe the data, too.
-
.delete_files(namelist) ⇒ Object
just remove associated source and defaults files; don’t touch underlying data or methods (if any exist yet).
- .delete_method(datum) ⇒ Object
-
.delete_methods(namelist) ⇒ Object
just delete the methods.
-
.delete_namelist(namelist) ⇒ Object
delete namelist and associated data.
- .delete_parentheses_method(datum) ⇒ Object
- .delete_regular_method(datum) ⇒ Object
-
.f_end ⇒ Object
Determine proper file suffix for fortran source.
- .full_line(lines, indx) ⇒ Object
-
.get_data(use_star_as_fallback: true) ⇒ Object
Generate methods for the Inlist class that set various namelist parameters.
-
.get_defaults(temp_data, namelist, whine = false) ⇒ Object
Similar to Inlist.get_names_and_types, but takes the output of Inlist.get_names_and_types and assigns defaults and orders to each item.
-
.get_namelist_data(namelist) ⇒ Object
Reads names and types for a specified namelist from given file (intended to be of the form of something like star/private/star_controls.inc).
- .get_names_and_types(namelist) ⇒ Object
- .has_comment?(line) ⇒ Boolean
-
.have_data? ⇒ Boolean
Checks to see if the data/methods for the Inlist class has been initialized.
-
.inlist_to_mesascript(inlist_file, script_file, dbg = false) ⇒ Object
Converts a standard inlist to its equivalent mesascript formulation.
- .is_blank?(line) ⇒ Boolean
- .is_comment?(line) ⇒ Boolean
-
.make_inlist(name = 'inlist', &block) ⇒ Object
Create an Inlist object, execute block of commands that presumably populate the inlist, then write the inlist to a file with the given name.
- .make_method(datum) ⇒ Object
-
.make_parentheses_method(datum) ⇒ Object
Three ways to access array categories.
-
.make_regular_method(datum) ⇒ Object
Two ways to access/change scalars.
-
.parse_input(name, value, type) ⇒ Object
Ensure provided value’s data type matches expected data type.
- .set_defaults_file(namelist, new_defaults_file) ⇒ Object
- .set_source_files(namelist, new_sources) ⇒ Object
-
.star_or_star_data ⇒ Object
Determine proper file location for star-related .inc files.
-
.version ⇒ Object
Get access to current MESA version.
-
.version_is_git? ⇒ Boolean
check if this version came from a github repository (newer than 15140).
Instance Method Summary collapse
- #flag_command(name) ⇒ Object
-
#flagged ⇒ Object
Marks a data category so that it can be staged into an inlist.
-
#initialize(use_star_as_fallback: true) ⇒ Inlist
constructor
A new instance of Inlist.
-
#make_fresh_writelist ⇒ Object
Zeroes out all staged data and blank lines.
- #namelists ⇒ Object
-
#stage_flagged ⇒ Object
Collects all data categories into a hash of arrays (each array is a namelist) that is read whenever the inlist is converted to a string (i.e. when it is printed to a file or the screen).
- #stage_namelist_command(name) ⇒ Object
-
#to_s ⇒ Object
Takes the staged data categories and formats them into a string series of namelists that are MESA-readable.
- #unflag_command(name) ⇒ Object
Constructor Details
#initialize(use_star_as_fallback: true) ⇒ Inlist
Returns a new instance of Inlist.
844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 |
# File 'lib/mesa_script.rb', line 844 def initialize(use_star_as_fallback: true) unless Inlist.have_data? Inlist.get_data(use_star_as_fallback: use_star_as_fallback) end @data = Inlist.inlist_data @data_hash = {} @data.each_value do |namelist_data| namelist_data.each do |datum| @data_hash[datum.name] = datum.dup end end @names = @data_hash.keys @data = {} Inlist.namelists.each do |namelist| @data[namelist] = Array.new(Inlist.inlist_data[namelist].size, '') end end |
Class Attribute Details
.defaults_files ⇒ Object
Returns the value of attribute defaults_files.
261 262 263 |
# File 'lib/mesa_script.rb', line 261 def defaults_files @defaults_files end |
.have_data ⇒ Object
Returns the value of attribute have_data.
260 261 262 |
# File 'lib/mesa_script.rb', line 260 def have_data @have_data end |
.inlist_data ⇒ Object
Returns the value of attribute inlist_data.
261 262 263 |
# File 'lib/mesa_script.rb', line 261 def inlist_data @inlist_data end |
.namelists ⇒ Object
Returns the value of attribute namelists.
261 262 263 |
# File 'lib/mesa_script.rb', line 261 def namelists @namelists end |
.source_files ⇒ Object
Returns the value of attribute source_files.
261 262 263 |
# File 'lib/mesa_script.rb', line 261 def source_files @source_files end |
Instance Attribute Details
#data_hash ⇒ Object
Making an instance of Inlist first checks to see if the class methods are set up for the namelists in Inlist.namelists. If they aren’t ready, it creates them. Then creates a hash with an array associated to each namelist that is the exact size of the number of entries available in that namelist.
842 843 844 |
# File 'lib/mesa_script.rb', line 842 def data_hash @data_hash end |
#names ⇒ Object (readonly)
Returns the value of attribute names.
843 844 845 |
# File 'lib/mesa_script.rb', line 843 def names @names end |
Class Method Details
.add_binary_controls_defaults(verbose: false) ⇒ Object
short hand for adding binary_controls_defaults namelist using sensible defaults as of 10108
207 208 209 210 211 212 213 214 215 216 |
# File 'lib/mesa_script.rb', line 207 def self.add_binary_controls_defaults(verbose: false) config_namelist( namelist: :binary_controls, source_files: File.join(ENV['MESA_DIR'], 'binary', 'public', 'binary_controls.inc'), defaults_file: File.join(ENV['MESA_DIR'], 'binary', 'defaults', 'binary_controls.defaults'), verbose: verbose ) end |
.add_binary_defaults ⇒ Object
quickly add both major namelists for binary module (binary_job and binary_controls)
245 246 247 248 |
# File 'lib/mesa_script.rb', line 245 def self.add_binary_defaults add_binary_job_defaults add_binary_controls_defaults end |
.add_binary_job_defaults(verbose: false) ⇒ Object
short hand for adding binary_job_defaults namelist using sensible defaults as of 10108
220 221 222 223 224 225 226 227 228 229 |
# File 'lib/mesa_script.rb', line 220 def self.add_binary_job_defaults(verbose: false) config_namelist( namelist: :binary_job, source_files: File.join(ENV['MESA_DIR'], 'binary', 'private', 'binary_job_controls.inc'), defaults_file: File.join(ENV['MESA_DIR'], 'binary', 'defaults', 'binary_job.defaults'), verbose: verbose ) end |
.add_controls_defaults(verbose: false) ⇒ Object
short hand for adding controls namelist using sensible defaults as of 10108
156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/mesa_script.rb', line 156 def self.add_controls_defaults(verbose: false) config_namelist( namelist: :controls, source_files: [File.join(ENV['MESA_DIR'], star_or_star_data, 'private', 'star_controls.inc'), File.join(ENV['MESA_DIR'], 'star', 'private', "ctrls_io.#{f_end}")], defaults_file: File.join(ENV['MESA_DIR'], 'star', 'defaults', 'controls.defaults'), verbose: verbose ) end |
.add_eos_defaults(verbose: false) ⇒ Object
short hand for adding eos namelist using sensible defaults as of 22.11.1
182 183 184 185 186 187 188 189 190 191 |
# File 'lib/mesa_script.rb', line 182 def self.add_eos_defaults(verbose: false) config_namelist( namelist: :eos, source_files: [File.join(ENV['MESA_DIR'], 'eos', 'private', "eos_ctrls_io.#{f_end}")], defaults_file: File.join(ENV['MESA_DIR'], 'eos', 'defaults', 'eos.defaults'), verbose: verbose ) end |
.add_kap_defaults(verbose: false) ⇒ Object
short hand for adding kap namelist using sensible defaults as of 22.11.1
170 171 172 173 174 175 176 177 178 179 |
# File 'lib/mesa_script.rb', line 170 def self.add_kap_defaults(verbose: false) config_namelist( namelist: :kap, source_files: [File.join(ENV['MESA_DIR'], 'kap', 'private', "kap_ctrls_io.#{f_end}")], defaults_file: File.join(ENV['MESA_DIR'], 'kap', 'defaults', 'kap.defaults'), verbose: verbose ) end |
.add_namelist(new_namelist) ⇒ Object
67 68 69 70 71 72 73 |
# File 'lib/mesa_script.rb', line 67 def self.add_namelist(new_namelist) if new_namelist.nil? || new_namelist.empty? raise(NamelistError.new, 'Must provide a namelist name.') end return if @namelists.include? namelist_sym(new_namelist) @namelists << namelist_sym(new_namelist) end |
.add_pgstar_defaults(verbose: false) ⇒ Object
short hand for adding pgstar namelist using sensible defaults as of 10108
194 195 196 197 198 199 200 201 202 203 |
# File 'lib/mesa_script.rb', line 194 def self.add_pgstar_defaults(verbose: false) config_namelist( namelist: :pgstar, source_files: File.join(ENV['MESA_DIR'], star_or_star_data, 'private', 'pgstar_controls.inc'), defaults_file: File.join(ENV['MESA_DIR'], 'star', 'defaults', 'pgstar.defaults'), verbose: verbose ) end |
.add_star_defaults ⇒ Object
quickly add all five (three for older versions) major namelists for star module (star_job, controls, and pgstar)
233 234 235 236 237 238 239 240 241 |
# File 'lib/mesa_script.rb', line 233 def self.add_star_defaults add_star_job_defaults if Inlist.version_is_git? || Inlist.version.to_i > 15140 add_kap_defaults add_eos_defaults end add_controls_defaults add_pgstar_defaults end |
.add_star_job_defaults(verbose: false) ⇒ Object
short hand for adding star_job namelist using sensible defaults as of 10108
144 145 146 147 148 149 150 151 152 153 |
# File 'lib/mesa_script.rb', line 144 def self.add_star_job_defaults(verbose: false) config_namelist( namelist: :star_job, source_files: File.join(ENV['MESA_DIR'], star_or_star_data, 'private', 'star_job_controls.inc'), defaults_file: File.join(ENV['MESA_DIR'], 'star', 'defaults', 'star_job.defaults'), verbose: verbose ) end |
.config_namelist(namelist: nil, source_files: nil, defaults_file: nil, verbose: true) ⇒ Object
used to turn on a namelist; need to provide namelist name as well as locations for source file (usually a .inc or .f90 file that defines allowable controls) and a defaults file (usually a .defaults file that lists all controls and their default values). Shorthand versions for the three common star namelists and the one common binary namelist are defined below for convenience
53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/mesa_script.rb', line 53 def self.config_namelist(namelist: nil, source_files: nil, defaults_file: nil, verbose: true) new_namelist = namelist_sym(namelist) add_namelist(new_namelist) set_source_files(new_namelist, source_files) set_defaults_file(new_namelist, defaults_file) return unless verbose puts 'Added the following namelist data:' puts " namelist: #{new_namelist}" puts " source: #{@source_files[new_namelist].join(', ')}" puts " defaults: #{@defaults_files[namelist_sym(namelist)]}" puts "Did not load data yet, though.\n\n" end |
.delete_all_namelists ⇒ Object
Delete all namelists and associated data.
100 101 102 103 |
# File 'lib/mesa_script.rb', line 100 def self.delete_all_namelists namelists.each { |namelist| remove_namelist(namelist) } @have_data = false end |
.delete_data(namelist) ⇒ Object
see if data has been loaded, and if it has, delete all the methods associated with it and then wipe the data, too.
131 132 133 134 135 136 |
# File 'lib/mesa_script.rb', line 131 def self.delete_data(namelist) to_delete = namelist_sym(namelist) return false unless inlist_data.include? namelist_sym(to_delete) delete_methods(to_delete) inlist_data.delete(to_delete) end |
.delete_files(namelist) ⇒ Object
just remove associated source and defaults files; don’t touch underlying data or methods (if any exist yet)
121 122 123 124 125 126 127 |
# File 'lib/mesa_script.rb', line 121 def self.delete_files(namelist) found_something = false [source_files, defaults_files].each do |files| found_something ||= files.delete(namelist_sym(namelist)) end found_something end |
.delete_method(datum) ⇒ Object
287 288 289 290 291 292 293 |
# File 'lib/mesa_script.rb', line 287 def self.delete_method(datum) if datum.is_arr Inlist.delete_parentheses_method(datum) else Inlist.delete_regular_method(datum) end end |
.delete_methods(namelist) ⇒ Object
just delete the methods. This will throw errors if they don’t exist
139 140 141 |
# File 'lib/mesa_script.rb', line 139 def self.delete_methods(namelist) @inlist_data[namelist_sym(namelist)].each { |datum| delete_method(datum) } end |
.delete_namelist(namelist) ⇒ Object
delete namelist and associated data
106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/mesa_script.rb', line 106 def self.delete_namelist(namelist) to_delete = namelist_sym(namelist) found_something = namelists.delete(to_delete) found_something = delete_files(to_delete) || found_something # this also undefines methods found_something = delete_data(to_delete) || found_something unless found_something puts "WARNING: Attempting to delete namelist #{namelist} data, but it " \ "wasn't present in existing Inlist data. Nothing happened." end namelist end |
.delete_parentheses_method(datum) ⇒ Object
450 451 452 453 454 455 |
# File 'lib/mesa_script.rb', line 450 def self.delete_parentheses_method(datum) base_name = datum.name method_names = [base_name, base_name + '[]', base_name + '[]='] alias_names = method_names.map(&:downcase) [method_names, alias_names].flatten.uniq.each { |meth| remove_method(meth) } end |
.delete_regular_method(datum) ⇒ Object
494 495 496 497 498 499 500 |
# File 'lib/mesa_script.rb', line 494 def self.delete_regular_method(datum) method_name = datum.name aliases = [method_name + '=', method_name.downcase + '=', method_name.downcase] [method_name, aliases].flatten.uniq.each { |meth| remove_method meth } end |
.f_end ⇒ Object
Determine proper file suffix for fortran source
20 21 22 23 24 25 26 |
# File 'lib/mesa_script.rb', line 20 def self.f_end if Inlist.version_is_git? || Inlist.version.to_i >= 7380 'f90' else 'f' end end |
.full_line(lines, indx) ⇒ Object
820 821 822 823 |
# File 'lib/mesa_script.rb', line 820 def self.full_line(lines, indx) return lines[indx] unless lines[indx][-1] == '&' [lines[indx].sub('&', ''), full_line(lines, indx + 1)].join(' ') end |
.get_data(use_star_as_fallback: true) ⇒ Object
Generate methods for the Inlist class that set various namelist parameters.
265 266 267 268 269 270 271 272 273 274 275 276 277 |
# File 'lib/mesa_script.rb', line 265 def self.get_data(use_star_as_fallback: true) # might need to add star data; preserves expected behavior (minus binary) Inlist.add_star_defaults if use_star_as_fallback && Inlist.namelists.empty? Inlist.namelists.each do |namelist| @inlist_data[namelist] = Inlist.get_namelist_data(namelist) end # create methods (interface) for each data category @inlist_data.each_value do |namelist_data| namelist_data.each { |datum| Inlist.make_method(datum) } end # don't do this nonsense again unles specifically told to do so Inlist.have_data = true end |
.get_defaults(temp_data, namelist, whine = false) ⇒ Object
Similar to Inlist.get_names_and_types, but takes the output of Inlist.get_names_and_types and assigns defaults and orders to each item. Looks for this information in the specified defaults filename.
744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 |
# File 'lib/mesa_script.rb', line 744 def self.get_defaults(temp_data, namelist, whine = false) defaults_file = defaults_files[namelist] unless File.exist?(defaults_file) raise "Couldn't find file #{defaults_file}" end contents = File.readlines(defaults_file) # throw out comments and blank lines contents.reject! { |line| is_comment?(line) || is_blank?(line) } # remaining lines should only be assignments. Only use the part of the line # up to the comment character, then strip all whitespace contents.map! do |line| my_line = line.dup my_line = my_line[0...my_line.index('!')] if has_comment?(line) unless my_line =~ /=/ raise "Equal sign missing in line:\n\t #{my_line}\n in file " \ "#{full_path}." end my_line.strip! end # divide lines into two element arrays: name and value pairs = contents.map { |line| line.split('=').map(&:strip) } n_d_hash = {} # maps names to default values n_o_hash = {} # maps names to default order in inlist pairs.each_with_index do |pair, i| name = pair[0] default = pair[1] # look for parentheses in name, indicating an array if name =~ /\(.*\)/ # make selector be the stuff in the parentheses selector = name[/\(.*\)/][1..-2] # make name just be the part without parentheses name.sub!(/\(.*\)/, '') # colon indicates mass assignment if selector.include?(':') default = Hash.new(default) # lack of a comma indicates dimension = 1 elsif selector.count(',').zero? default = { selector.to_i => default } # at least one comma, so dimension > 1 else # reformat the selector (now a key in the default hash) to an # array of integers selector = selector.split(',').map { |index| index.strip.to_i } default = { selector => default } end end # if the default value is a hash, we probably don't have every possible # value, so just merge scraped values with the automatically chosen # defaults if n_d_hash[name].is_a?(Hash) n_d_hash[name].merge!(default) # scalar values get a simple assignment else n_d_hash[name] = default end # order is just the same as the order it appeared in its defaults file n_o_hash[name] ||= i end temp_data.each do |datum| unless n_d_hash.key?(datum.name) if whine puts "WARNING: no default found for control #{datum.name}. Using " \ 'standard defaults.' end end default = n_d_hash[datum.name] datum.value = if default.is_a?(Hash) && datum.value.is_a?(Hash) datum.value.merge(default) else default || datum.value end datum.order = n_o_hash[datum.name] || datum.order end temp_data end |
.get_namelist_data(namelist) ⇒ Object
Reads names and types for a specified namelist from given file (intended to be of the form of something like star/private/star_controls.inc).
Returns an array of InlistItem Struct instances that contain a parameter’s name, type (:bool, :string, :float, :int, or :type), the namelist it belongs to, and its relative ordering in that namelist. Bogus defaults are assigned according to the object’s type, and the ordering is unknown.
663 664 665 666 |
# File 'lib/mesa_script.rb', line 663 def self.get_namelist_data(namelist) temp_data = Inlist.get_names_and_types(namelist) Inlist.get_defaults(temp_data, namelist) end |
.get_names_and_types(namelist) ⇒ Object
668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 |
# File 'lib/mesa_script.rb', line 668 def self.get_names_and_types(namelist) namelist_data = [] source_files[namelist].each do |source_file| raise "Couldn't find file #{source_file}" unless File.exist?(source_file) contents = File.readlines(source_file) # Throw out comments and blank lines, ensure remaining lines are a proper # Fortran assignment, then remove leading and trailing white space contents.reject! { |line| is_comment?(line) || is_blank?(line) } contents.map! do |line| my_line = line.dup my_line = my_line[0...my_line.index('!')] if has_comment?(my_line) my_line.strip! end full_lines = [] contents.each_with_index do |line, i| break if line =~ /\A\s*contains/ next unless line =~ /::/ full_lines << Inlist.full_line(contents, i) end pairs = full_lines.map do |line| line.split('::').map(&:strip) end pairs.each do |pair| next if pair[0] == 'public' type = case pair[0] when /logical/ then :bool when /character/ then :string when /real/ then :float when /integer/ then :int when /type/ then :type else raise "Couldn't determine type of entry \"#{pair[0]}\" in " \ "#{source_file}." end name_chars = pair[1].split('') names = [] paren_level = 0 name_chars.each do |char| if paren_level > 0 && char == ',' names << '!' next elsif char == '(' paren_level += 1 elsif char == ')' paren_level -= 1 end names << char end names = names.join.split(',').map(&:strip) names.each do |name| is_arr = false num_indices = 0 if name =~ /\(.*\)/ is_arr = true num_indices = name.count('!') + 1 name.sub!(/\(.*\)/, '') elsif pair[0] =~ /dimension\((.*)\)/i is_arr = true num_indices = Regexp.last_match[1].count(',') + 1 end type_default = { bool: false, string: '', float: 0.0, int: 0 } dft = is_arr ? Hash.new(type_default[type]) : type_default[type] namelist_data << InlistItem.new(name, type, dft, namelist, -1, is_arr, num_indices) end end end namelist_data end |
.has_comment?(line) ⇒ Boolean
833 834 835 |
# File 'lib/mesa_script.rb', line 833 def self.has_comment?(line) line.include?('!') end |
.have_data? ⇒ Boolean
Checks to see if the data/methods for the Inlist class has been initialized.
651 652 653 |
# File 'lib/mesa_script.rb', line 651 def self.have_data? @have_data end |
.inlist_to_mesascript(inlist_file, script_file, dbg = false) ⇒ Object
Converts a standard inlist to its equivalent mesascript formulation. Comments are preserved and namelist separators are converted to comments. Note that comments do NOT get put back into the fortran inlist through mesascript. Converting an inlist to mesascript and then back again will clean up and re-order your inlist, but all comments will be lost. All other information SHOULD remain intact.
561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 |
# File 'lib/mesa_script.rb', line 561 def self.inlist_to_mesascript(inlist_file, script_file, dbg = false) Inlist.get_data unless Inlist.have_data # ensure we have inlist data inlist_contents = File.readlines(inlist_file) # make namelist separators comments new_contents = inlist_contents.map do |line| case line when /^\s*&/ then '# ' + line.chomp # start namelist when /^\s*\// then '# ' + line.chomp # end namelist else line.sub('!', '#').chomp # fix comments end end new_contents.map! do |line| if line =~ /^\s*#/ or line.strip.empty? # leave comments and blanks result = line else if dbg puts "parsing line:" puts line end comment_pivot = line.index('#') if comment_pivot command = line[0...comment_pivot] comment = line[comment_pivot..-1].to_s.strip else command = line comment = '' end command =~ /(^\s*)/ # save leading space leading_space = Regexp.last_match(1) command =~ /(\s*$)/ # save buffer space buffer_space = Regexp.last_match(1) command.strip! # remove white space name, value = command.split('=').map(&:strip) if dbg puts "name: #{name}" puts "value: #{value}" end if name =~ /\((\d+)\)/ # fix 1D array assignments name.sub!('(', '[') name.sub!(')', ']') name += ' =' elsif name =~ /\((\s*\d+\s*,\s*)+\d\s*\)/ # fix multi-D arrays # arrays become hashes in MesaScript, so rather than having multiple # indices, the key becomes the array of indices themselves, hence # the double braces replacing single parentheses name.sub!('(', '[[') name.sub!(')', ']]') name += ' =' end name.downcase! result = if value =~ /'.*'/ || value =~ /".*"/ name + ' ' + value # leave strings alone elsif %w[.true. .false.].include?(value.downcase) name + ' ' + value.downcase.delete('.') # fix booleans elsif value =~ /\d+\.?\d*([eEdD]\d+)?/ name + ' ' + value.downcase.sub('d', 'e') # fix floats else name + ' ' + value # leave everything else alone end result = leading_space + result + buffer_space + comment if dbg puts 'parsed to:' puts result puts '' end end result end File.open(script_file, 'w') do |f| f.puts "require 'mesa_script'" f.puts '' f.puts "Inlist.make_inlist('#{File.basename(inlist_file)}') do" new_contents.each { |line| f.puts ' ' + line } f.puts 'end' end end |
.is_blank?(line) ⇒ Boolean
829 830 831 |
# File 'lib/mesa_script.rb', line 829 def self.is_blank?(line) not (line =~/[a-z0-9]+/) end |
.is_comment?(line) ⇒ Boolean
825 826 827 |
# File 'lib/mesa_script.rb', line 825 def self.is_comment?(line) line =~ /\A\s*!/ end |
.make_inlist(name = 'inlist', &block) ⇒ Object
Create an Inlist object, execute block of commands that presumably populate the inlist, then write the inlist to a file with the given name. This is the money routine with user-supplied commands in the instance_eval block.
643 644 645 646 647 648 |
# File 'lib/mesa_script.rb', line 643 def self.make_inlist(name = 'inlist', &block) inlist = Inlist.new inlist.instance_eval(&block) inlist.stage_flagged File.open(name, 'w') { |f| f.write(inlist) } end |
.make_method(datum) ⇒ Object
279 280 281 282 283 284 285 |
# File 'lib/mesa_script.rb', line 279 def self.make_method(datum) if datum.is_arr Inlist.make_parentheses_method(datum) else Inlist.make_regular_method(datum) end end |
.make_parentheses_method(datum) ⇒ Object
Three ways to access array categories. All methods will cause the data category to be staged into your inlist, even if you do not change it Basically, if it appears in your mesascript, it will definitely appear in your inlist. A command can be unflagged by calling ‘unflag_command(’COMMAND_NAME’)‘ where COMMAND_NAME is the case-sensitive name of the command to be unflagged.
-
Standard array way like
xa_lower_limit_species[1] = 'h1'
(note square braces, NOT parentheses). Returns new value.
-
Just access (and flag), but don’t change via array access, like
xa_lower_limit_species[1]
(again, note square braces). Returns current value
-
No braces method, like
xa_lower_limit_species() # flags and returns hash of values xa_lower_limit_species # same, but more ruby-esque xa_lower_limit_species(1) # flags and returns value 1 xa_lower_limit_species 1 # Same xa_lower_limit_species(1, 'h1') # flags and sets value 1 xa_lower_limit_species 1, 'h1' # same
For multi-dimensional arrays, things are even more vaired. You can treat them like 1-dimensional arrays with the “index” just being an array of indices, for instance:
text_summary1_name[[1,2]] = 'star_mass' # flags ALL values and sets
text_summary1_name([1,2], 'star_mass') # text_summary1_name(1,2)
text_summary1_name [1,2], 'star_mass # to 'star_mass'
text_summary1_name [1,2] # flags ALL values and
text_summary1_name([1,2]) # returns
# text_sumarry_name(1,2)
text_summary_name() # flags ALL values and
text_summary_name # returns entire hash for
# text_summary_name
Alternatively, can use the more intuitive form where indices are separate and don’t need to be in an array, but this only works with the parentheses versions (i.e. the first option directly above has no counterpart):
text_summary1_name(1, 2, 'star_mass')
text_summary1_name 1, 2, 'star_mass' # same as above (first 3)
text_summary1_name
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 |
# File 'lib/mesa_script.rb', line 343 def self.make_parentheses_method(datum) method_name = datum.name num_indices = datum.num_indices # assignment array form define_method(method_name + '[]=') do |arg1, arg2| if num_indices > 1 unless arg1.is_a?(Array) && arg1.length == num_indices raise "First argument of #{method_name}[]= (part in brackets) must "\ "be an array with #{num_indices} indices since #{method_name}"\ ' is a multi-dimensional array.' end end flag_command(method_name) data_hash[method_name].value[arg1] = arg2 end # de-referencing array form define_method(method_name + '[]') do |arg| if num_indices > 1 unless arg.is_a?(Array) && arg.length == num_indices raise "Argument of #{method_name}[] (part in brackets) must be an " \ "array with #{num_indices} indices since #{method_name} is a "\ 'multi-dimensional array.' end end flag_command(method_name) data_hash[method_name].value[arg] end # imperative multi-purpose form define_method(method_name) do |*args| flag_command(method_name) case args.length # just retrieve whole value (de-reference) when 0 then data_hash[method_name].value # just retrieve part of value (de-reference) when 1 if num_indices > 1 unless args[0].is_a?(Array) && args[0].length == num_indices raise "First argument of #{method_name} must be an array with " \ "#{num_indices} indices since #{method_name} is a " \ 'multi-dimensional array OR must provide all indices as ' \ 'separate arguments.' end end data_hash[method_name].value[args[0]] # might be trying to access or a multi-d array OR assign to an array. when 2 # 1-D array with scalar value; simple assignement if num_indices == 1 && !args[0].is_a?(Array) data_hash[method_name].value[args[0]] = args[1] # 2-D array, de-reference single value (NOT AN ASSIGNMENT!) elsif num_indices == 2 && !args[0].is_a?(Array) && args[1].is_a?(Integer) data_hash[method_name].value[args] # Multi-d array with first argument being an array, second a value to # assign; simple assignment elsif num_indices > 1 unless args[0].is_a?(Array) && args[0].length == num_indices raise "First argument of #{method_name} must be an array with " \ "#{num_indices} indices since #{method_name} is a " \ 'multi-dimensional array OR must provide all indices as ' \ 'separate arguments.' end data_hash[method_name].value[args[0]] = args[1] # Can't parse... throw hands up. else raise "First argument of #{method_name} must be an array with "\ "#{num_indices} indices since #{method_name} is a "\ 'multi-dimensional array OR must provide all indices as '\ 'separate arguments. The optional final argument is what the '\ "#{method_name} would be set to. Omission of this argument "\ "will simply flag #{method_name} to appear in the inlist." end # one more argument than number of indices; first n are location to be # assigned, last one is value to be assigned when num_indices + 1 if args[0].is_a?(Array) raise "Bad arguments for #{method_name}. Either provide an array " \ "of #{num_indices} indices for the first argument or provide "\ 'each index in succession, optionally specifying the desired '\ 'value for the last argument.' end data_hash[method_name].value[args[0..-2]] = args[-1] # same number of arguments as indices; assume we are de-referencing a # value when num_indices then data_hash[method_name].value[args] # give up... who knows what the user is doing?! else raise "Wrong number of arguments for #{method_name}. Can provide " \ 'zero arguments (just flag command), one argument (array of ' \ 'indices for multi-d array or one index for 1-d array), two ' \ 'arguments (array of indices/single index for multi-/1-d array '\ 'and a new value for the value), #{num_indices} arguments ' \ 'where the elements themselves are the right indices (returns ' \ "the specified element of the array), or #{num_indices + 1} " \ 'arguments to set the specific value and return it.' end end alias_method method_name.downcase.to_sym, method_name.to_sym alias_method((method_name.downcase + '[]').to_sym, (method_name + '[]').to_sym) alias_method((method_name.downcase + '[]=').to_sym, (method_name + '[]=').to_sym) end |
.make_regular_method(datum) ⇒ Object
Two ways to access/change scalars. All methods will cause the data category to be staged into your inlist, even if you do not change the value. Basically, if it appears in your mesascript, it will definitely appear in your inlist.
-
Change value, like
initial_mass(1.0) initial_mass 1.0
This flags the category to go in your inlist and changes the value. There is no difference between these two syntaxes (it’s built into ruby). Returns new value.
-
Just access, like
initial_mass() initial_mass
This flags the category, but does not change the value. Again, both syntaxes are allowed, though the one without parentheses is more traditional for ruby (why do you want empty parentheses anyway?). Returns current value.
A command can be unflagged by calling ‘unflag_command(’COMMAND_NAME’)‘ where COMMAND_NAME is the case-sensitive name of the command to be unflagged.
481 482 483 484 485 486 487 488 489 490 491 492 |
# File 'lib/mesa_script.rb', line 481 def self.make_regular_method(datum) method_name = datum.name define_method(method_name) do |*args| self.flag_command(method_name) return self.data_hash[method_name].value if args.empty? self.data_hash[method_name].value = args[0] end aliases = [(method_name + '=').to_sym, (method_name.downcase + '=').to_sym, method_name.downcase.to_sym] aliases.each { |ali| alias_method ali, method_name.to_sym } end |
.parse_input(name, value, type) ⇒ Object
Ensure provided value’s data type matches expected data type. Then convert to string for printing to an inlist. If value is a string, change nothing (no protection). If value is a string and SHOULD be a string, wrap it in single quotes.
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 |
# File 'lib/mesa_script.rb', line 506 def self.parse_input(name, value, type) if value.class == String if type == :string value = "'#{value}'" unless value[0] == "'" && value[-1] == "'" end value elsif type == :bool unless [TrueClass, FalseClass].include?(value.class) raise "Invalid value for namelist item #{name}: #{value}. Use " \ "'.true.', '.false.', or a Ruby boolean (true/false)." end if value == true '.true.' elsif value == false '.false.' else raise "Error converting value #{value} of #{name} to a boolean." end elsif type == :int unless value.is_a?(Integer) || value.is_a?(Float) raise "Invalid value for namelist item #{name}: #{value}. Must " \ 'provide an int or float.' end if value.is_a?(Float) puts "WARNING: Expected integer for #{name} but got #{value}. Value" \ ' will be converted to an integer.' end value.to_i.to_s elsif type == :float unless value.is_a?(Integer) || value.is_a?(Float) raise "Invalid value for namelist item #{name}: #{value}. Must "\ 'provide an int or float.' end res = format('%g', value).sub('e', 'd') res += 'd0' unless res.include?('d') res elsif type == :type puts "WARNING: 'type' values are currently unsupported " \ "(regarding #{name}) because your humble author has no idea what " \ 'they look like in an inlist. You should tell him what to do at ' \ "[email protected]. Your input, #{value}, has been passed through to "\ 'your inlist verbatim.' value.to_s else raise "Error parsing value for namelist item #{name}: #{value}. " \ "Expected type was #{type}." end end |
.set_defaults_file(namelist, new_defaults_file) ⇒ Object
93 94 95 96 97 |
# File 'lib/mesa_script.rb', line 93 def self.set_defaults_file(namelist, new_defaults_file) # set defaults file. This is limited to being scalar string for now. return unless new_defaults_file @defaults_files[namelist_sym(namelist)] = new_defaults_file.to_s end |
.set_source_files(namelist, new_sources) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/mesa_script.rb', line 75 def self.set_source_files(namelist, new_sources) # set source files. There may be more than one, so we ALWAYS make it an # array. Flatten magic allows for users to supply an array or a scalar # (single string) if new_sources.nil? || new_sources.empty? raise NamelistError.new, "Must provide a source file for namelist #{namelist}. For " \ 'example, $MESA_DIR/star/private/star_job_controls.inc for ' \ 'star_job.' end source_to_add = if new_sources.respond_to?(:map) new_sources.map(&:to_s) else new_sources.to_s end @source_files[namelist_sym(namelist)] = [source_to_add].flatten end |
.star_or_star_data ⇒ Object
Determine proper file location for star-related .inc files
29 30 31 32 33 34 35 |
# File 'lib/mesa_script.rb', line 29 def self.star_or_star_data if Inlist.version_is_git? || Inlist.version.to_i >= 12245 'star_data' else 'star' end end |
.version ⇒ Object
Get access to current MESA version.
15 16 17 |
# File 'lib/mesa_script.rb', line 15 def self.version IO.read(File.join(ENV['MESA_DIR'], 'data', 'version_number')).sub('.', '').sub('r', '') end |
.version_is_git? ⇒ Boolean
check if this version came from a github repository (newer than 15140)
10 11 12 |
# File 'lib/mesa_script.rb', line 10 def self.version_is_git? File.exist?(File.join(ENV['MESA_DIR'], '.gitignore')) or File.exist?(File.join(ENV['MESA_DIR'], '.github')) end |
Instance Method Details
#flag_command(name) ⇒ Object
874 875 876 |
# File 'lib/mesa_script.rb', line 874 def flag_command(name) @data_hash[name].flagged = true end |
#flagged ⇒ Object
Marks a data category so that it can be staged into an inlist
908 909 910 |
# File 'lib/mesa_script.rb', line 908 def flagged @data_hash.keys.select { |key| @data_hash[key].flagged } end |
#make_fresh_writelist ⇒ Object
Zeroes out all staged data and blank lines
863 864 865 866 867 868 |
# File 'lib/mesa_script.rb', line 863 def make_fresh_writelist @to_write = {} @data.keys.each do |namelist| @to_write[namelist] = Array.new(@data[namelist].size, '') end end |
#namelists ⇒ Object
870 871 872 |
# File 'lib/mesa_script.rb', line 870 def namelists @data.keys end |
#stage_flagged ⇒ Object
Collects all data categories into a hash of arrays (each array is a namelist) that is read whenever the inlist is converted to a string (i.e. when it is printed to a file or the screen).
915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 |
# File 'lib/mesa_script.rb', line 915 def stage_flagged make_fresh_writelist # start from scratch flagged.each { |name| stage_namelist_command(name) } # stage each datum # blank lines between disparate data namelists.each do |namelist| @to_write[namelist].each_index do |i| next if [0, @to_write[namelist].size - 1].include? i this_line = @to_write[namelist][i] prev_line = @to_write[namelist][i - 1] this_line = '' if this_line.nil? prev_line = '' if prev_line.nil? if this_line.empty? && !(prev_line.empty? || prev_line == "\n") @to_write[namelist][i] = "\n" end end end end |
#stage_namelist_command(name) ⇒ Object
882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 |
# File 'lib/mesa_script.rb', line 882 def stage_namelist_command(name) datum = @data_hash[name] if datum.is_arr lines = @data_hash[name].value.keys.map do |key| prefix = " #{datum.name}(" suffix = ') = ' + Inlist.parse_input(datum.name, datum.value[key], datum.type) + "\n" indices = if key.respond_to?(:inject) key[1..-1].inject(key[0].to_s) do |res, elt| "#{res}, #{elt}" end else key.to_s end prefix + indices + suffix end lines = lines.join @to_write[datum.namelist][datum.order] = lines else @to_write[datum.namelist][datum.order] = ' ' + datum.name + ' = ' + Inlist.parse_input(datum.name, datum.value, datum.type) + "\n" end end |
#to_s ⇒ Object
Takes the staged data categories and formats them into a string series of namelists that are MESA-readable.
938 939 940 941 942 943 944 945 946 |
# File 'lib/mesa_script.rb', line 938 def to_s result = '' namelists.each do |namelist| result += "\n&#{namelist}\n" result += @to_write[namelist].join('') result += "\n/ ! end of #{namelist} namelist\n" end result.sub("\n\n\n", "\n\n") end |
#unflag_command(name) ⇒ Object
878 879 880 |
# File 'lib/mesa_script.rb', line 878 def unflag_command(name) @data_hash[name].flagged = false end |