Class: Inlist

Inherits:
Object
  • Object
show all
Defined in:
lib/mesa_script.rb

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(use_star_as_fallback: true) ⇒ Inlist

Returns a new instance of Inlist.



809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
# File 'lib/mesa_script.rb', line 809

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_filesObject

Returns the value of attribute defaults_files.



227
228
229
# File 'lib/mesa_script.rb', line 227

def defaults_files
  @defaults_files
end

.have_dataObject

Returns the value of attribute have_data.



226
227
228
# File 'lib/mesa_script.rb', line 226

def have_data
  @have_data
end

.inlist_dataObject

Returns the value of attribute inlist_data.



227
228
229
# File 'lib/mesa_script.rb', line 227

def inlist_data
  @inlist_data
end

.namelistsObject

Returns the value of attribute namelists.



227
228
229
# File 'lib/mesa_script.rb', line 227

def namelists
  @namelists
end

.source_filesObject

Returns the value of attribute source_files.



227
228
229
# File 'lib/mesa_script.rb', line 227

def source_files
  @source_files
end

Instance Attribute Details

#data_hashObject

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.



807
808
809
# File 'lib/mesa_script.rb', line 807

def data_hash
  @data_hash
end

#namesObject (readonly)

Returns the value of attribute names.



808
809
810
# File 'lib/mesa_script.rb', line 808

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



177
178
179
180
181
182
183
184
185
186
# File 'lib/mesa_script.rb', line 177

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_defaultsObject

quickly add both major namelists for binary module (binary_job and binary_controls)



211
212
213
214
# File 'lib/mesa_script.rb', line 211

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



190
191
192
193
194
195
196
197
198
199
# File 'lib/mesa_script.rb', line 190

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



150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/mesa_script.rb', line 150

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_namelist(new_namelist) ⇒ Object



61
62
63
64
65
66
67
# File 'lib/mesa_script.rb', line 61

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



164
165
166
167
168
169
170
171
172
173
# File 'lib/mesa_script.rb', line 164

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_defaultsObject

quickly add all three major namelists for star module (star_job, controls, and pgstar)



203
204
205
206
207
# File 'lib/mesa_script.rb', line 203

def self.add_star_defaults
  add_star_job_defaults
  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



138
139
140
141
142
143
144
145
146
147
# File 'lib/mesa_script.rb', line 138

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



47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/mesa_script.rb', line 47

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_namelistsObject

Delete all namelists and associated data.



94
95
96
97
# File 'lib/mesa_script.rb', line 94

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.



125
126
127
128
129
130
# File 'lib/mesa_script.rb', line 125

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)



115
116
117
118
119
120
121
# File 'lib/mesa_script.rb', line 115

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



253
254
255
256
257
258
259
# File 'lib/mesa_script.rb', line 253

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



133
134
135
# File 'lib/mesa_script.rb', line 133

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



100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/mesa_script.rb', line 100

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



416
417
418
419
420
421
# File 'lib/mesa_script.rb', line 416

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



460
461
462
463
464
465
466
# File 'lib/mesa_script.rb', line 460

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_endObject

Determine proper file suffix for fortran source



14
15
16
17
18
19
20
# File 'lib/mesa_script.rb', line 14

def self.f_end
  if Inlist.version >= 7380
    'f90'
  else
    'f'
  end
end

.full_line(lines, indx) ⇒ Object



785
786
787
788
# File 'lib/mesa_script.rb', line 785

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.



231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/mesa_script.rb', line 231

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.



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
739
740
741
742
743
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
# File 'lib/mesa_script.rb', line 709

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.



629
630
631
632
# File 'lib/mesa_script.rb', line 629

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



634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
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
# File 'lib/mesa_script.rb', line 634

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|
      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

Returns:

  • (Boolean)


798
799
800
# File 'lib/mesa_script.rb', line 798

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.

Returns:

  • (Boolean)


617
618
619
# File 'lib/mesa_script.rb', line 617

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.



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
554
555
556
557
558
559
560
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
# File 'lib/mesa_script.rb', line 527

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

Returns:

  • (Boolean)


794
795
796
# File 'lib/mesa_script.rb', line 794

def self.is_blank?(line)
  not (line =~/[a-z0-9]+/)
end

.is_comment?(line) ⇒ Boolean

Returns:

  • (Boolean)


790
791
792
# File 'lib/mesa_script.rb', line 790

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.



609
610
611
612
613
614
# File 'lib/mesa_script.rb', line 609

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



245
246
247
248
249
250
251
# File 'lib/mesa_script.rb', line 245

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.

  1. Standard array way like

    xa_lower_limit_species[1] = 'h1'
    

    (note square braces, NOT parentheses). Returns new value.

  2. Just access (and flag), but don’t change via array access, like

    xa_lower_limit_species[1]
    

    (again, note square braces). Returns current value

  3. 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


309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
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
# File 'lib/mesa_script.rb', line 309

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.

  1. 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.

  2. 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.



447
448
449
450
451
452
453
454
455
456
457
458
# File 'lib/mesa_script.rb', line 447

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.



472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
# File 'lib/mesa_script.rb', line 472

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



87
88
89
90
91
# File 'lib/mesa_script.rb', line 87

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



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/mesa_script.rb', line 69

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_dataObject

Determine proper file location for star-related .inc files



23
24
25
26
27
28
29
# File 'lib/mesa_script.rb', line 23

def self.star_or_star_data
  if Inlist.version >= 12245
    'star_data'
  else
    'star'
  end
end

.versionObject

Get access to current MESA version.



9
10
11
# File 'lib/mesa_script.rb', line 9

def self.version
  IO.read(File.join(ENV['MESA_DIR'], 'data', 'version_number')).to_i
end

Instance Method Details

#flag_command(name) ⇒ Object



839
840
841
# File 'lib/mesa_script.rb', line 839

def flag_command(name)
  @data_hash[name].flagged = true
end

#flaggedObject

Marks a data category so that it can be staged into an inlist



873
874
875
# File 'lib/mesa_script.rb', line 873

def flagged
  @data_hash.keys.select { |key| @data_hash[key].flagged }
end

#make_fresh_writelistObject

Zeroes out all staged data and blank lines



828
829
830
831
832
833
# File 'lib/mesa_script.rb', line 828

def make_fresh_writelist
  @to_write = {}
  @data.keys.each do |namelist|
    @to_write[namelist] = Array.new(@data[namelist].size, '')
  end
end

#namelistsObject



835
836
837
# File 'lib/mesa_script.rb', line 835

def namelists
  @data.keys
end

#stage_flaggedObject

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).



880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
# File 'lib/mesa_script.rb', line 880

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



847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
# File 'lib/mesa_script.rb', line 847

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_sObject

Takes the staged data categories and formats them into a string series of namelists that are MESA-readable.



903
904
905
906
907
908
909
910
911
# File 'lib/mesa_script.rb', line 903

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



843
844
845
# File 'lib/mesa_script.rb', line 843

def unflag_command(name)
  @data_hash[name].flagged = false
end