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.



800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
# File 'lib/mesa_script.rb', line 800

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.



218
219
220
# File 'lib/mesa_script.rb', line 218

def defaults_files
  @defaults_files
end

.have_dataObject

Returns the value of attribute have_data.



217
218
219
# File 'lib/mesa_script.rb', line 217

def have_data
  @have_data
end

.inlist_dataObject

Returns the value of attribute inlist_data.



218
219
220
# File 'lib/mesa_script.rb', line 218

def inlist_data
  @inlist_data
end

.namelistsObject

Returns the value of attribute namelists.



218
219
220
# File 'lib/mesa_script.rb', line 218

def namelists
  @namelists
end

.source_filesObject

Returns the value of attribute source_files.



218
219
220
# File 'lib/mesa_script.rb', line 218

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.



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

def data_hash
  @data_hash
end

#namesObject (readonly)

Returns the value of attribute names.



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

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



168
169
170
171
172
173
174
175
176
177
# File 'lib/mesa_script.rb', line 168

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)



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

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



181
182
183
184
185
186
187
188
189
190
# File 'lib/mesa_script.rb', line 181

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



141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/mesa_script.rb', line 141

def self.add_controls_defaults(verbose: false)
  config_namelist(
    namelist: :controls,
    source_files: [File.join(ENV['MESA_DIR'], 'star', '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



52
53
54
55
56
57
58
# File 'lib/mesa_script.rb', line 52

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



155
156
157
158
159
160
161
162
163
164
# File 'lib/mesa_script.rb', line 155

def self.add_pgstar_defaults(verbose: false)
  config_namelist(
    namelist: :pgstar,
    source_files: File.join(ENV['MESA_DIR'], 'star', '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)



194
195
196
197
198
# File 'lib/mesa_script.rb', line 194

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



129
130
131
132
133
134
135
136
137
138
# File 'lib/mesa_script.rb', line 129

def self.add_star_job_defaults(verbose: false)
  config_namelist(
    namelist: :star_job,
    source_files: File.join(ENV['MESA_DIR'], 'star', '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



38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/mesa_script.rb', line 38

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.



85
86
87
88
# File 'lib/mesa_script.rb', line 85

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.



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

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)



106
107
108
109
110
111
112
# File 'lib/mesa_script.rb', line 106

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



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

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



124
125
126
# File 'lib/mesa_script.rb', line 124

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



91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/mesa_script.rb', line 91

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



407
408
409
410
411
412
# File 'lib/mesa_script.rb', line 407

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



451
452
453
454
455
456
457
# File 'lib/mesa_script.rb', line 451

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



776
777
778
779
# File 'lib/mesa_script.rb', line 776

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.



222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/mesa_script.rb', line 222

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.



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
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
# File 'lib/mesa_script.rb', line 700

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.



620
621
622
623
# File 'lib/mesa_script.rb', line 620

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



625
626
627
628
629
630
631
632
633
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
# File 'lib/mesa_script.rb', line 625

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)


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

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)


608
609
610
# File 'lib/mesa_script.rb', line 608

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.



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
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
# File 'lib/mesa_script.rb', line 518

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!
      results = 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)


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

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

.is_comment?(line) ⇒ Boolean

Returns:

  • (Boolean)


781
782
783
# File 'lib/mesa_script.rb', line 781

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.



600
601
602
603
604
605
# File 'lib/mesa_script.rb', line 600

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



236
237
238
239
240
241
242
# File 'lib/mesa_script.rb', line 236

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


300
301
302
303
304
305
306
307
308
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
# File 'lib/mesa_script.rb', line 300

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.



438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/mesa_script.rb', line 438

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.



463
464
465
466
467
468
469
470
471
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
# File 'lib/mesa_script.rb', line 463

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



78
79
80
81
82
# File 'lib/mesa_script.rb', line 78

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



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/mesa_script.rb', line 60

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

.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



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

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

#flaggedObject

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



864
865
866
# File 'lib/mesa_script.rb', line 864

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

#make_fresh_writelistObject

Zeroes out all staged data and blank lines



819
820
821
822
823
824
# File 'lib/mesa_script.rb', line 819

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

#namelistsObject



826
827
828
# File 'lib/mesa_script.rb', line 826

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



871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
# File 'lib/mesa_script.rb', line 871

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



838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
# File 'lib/mesa_script.rb', line 838

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.



894
895
896
897
898
899
900
901
902
# File 'lib/mesa_script.rb', line 894

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



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

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