mesa_script 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/mesa_script.rb +441 -200
- metadata +6 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61f0c3a50b013b45d21a7cb35923258fa442e28e
|
4
|
+
data.tar.gz: ae3672c28b5ac2b79e20251457a841455f363951
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9fc6a8d0051a2095e5e9cbc53d255d91d48b508c26702175ad3afd3fc7341787a8503ea2c261323d5465b387e2ef025e508269391bc5f6f4d508b661b39ac5f5
|
7
|
+
data.tar.gz: 329a2b712e47b20cd553869daa01b2548ac1bb900b229d3961627828de0c420438db5f735f40f6f0b7b5ceb9a1bfae62c2e48a3be63ef9e1528955c1621f728b
|
data/lib/mesa_script.rb
CHANGED
@@ -1,50 +1,187 @@
|
|
1
1
|
InlistItem = Struct.new(:name, :type, :value, :namelist, :order, :is_arr,
|
2
2
|
:num_indices, :flagged)
|
3
|
+
def namelist_sym(namelist)
|
4
|
+
namelist.to_s.downcase.to_sym
|
5
|
+
end
|
3
6
|
|
4
7
|
class Inlist
|
5
|
-
|
6
8
|
# Get access to current MESA version.
|
7
9
|
def self.version
|
8
|
-
|
9
|
-
return v_num
|
10
|
+
IO.read(File.join(ENV['MESA_DIR'], 'data', 'version_number')).to_i
|
10
11
|
end
|
11
12
|
|
12
13
|
# Determine proper file suffix for fortran source
|
13
14
|
def self.f_end
|
14
15
|
if Inlist.version >= 7380
|
15
|
-
|
16
|
+
'f90'
|
16
17
|
else
|
17
|
-
|
18
|
+
'f'
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
22
|
+
# these hold the names of the namelists as well as the locations of the
|
23
|
+
# fortran files that define their controls as well as the defaults files
|
24
|
+
# that define their default values and order in formatted inlists
|
25
|
+
@namelists = []
|
26
|
+
@source_files = Hash.new([])
|
27
|
+
@defaults_files = {}
|
21
28
|
|
29
|
+
# this holds the "guts"; control names, types, defaults, and orders
|
22
30
|
@inlist_data = {}
|
23
|
-
# Different namelists can be added or subtracted if MESA should change or
|
24
|
-
# proprietary inlists are required. Later hashes should be edited in a
|
25
|
-
# similar way to get the desired behavior for additional namelists.
|
26
31
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
# used to turn on a namelist; need to provide namelist name as well as
|
33
|
+
# locations for source file (usually a .inc or .f90 file that defines
|
34
|
+
# allowable controls) and a defaults file (usually a .defaults file that
|
35
|
+
# lists all controls and their default values). Shorthand versions for
|
36
|
+
# the three common star namelists and the one common binary namelist are
|
37
|
+
# defined below for convenience
|
38
|
+
def self.config_namelist(namelist: nil, source_files: nil,
|
39
|
+
defaults_file: nil, verbose: true)
|
40
|
+
new_namelist = namelist_sym(namelist)
|
41
|
+
add_namelist(new_namelist)
|
42
|
+
set_source_files(new_namelist, source_files)
|
43
|
+
set_defaults_file(new_namelist, defaults_file)
|
44
|
+
return unless verbose
|
45
|
+
puts 'Added the following namelist data:'
|
46
|
+
puts " namelist: #{new_namelist}"
|
47
|
+
puts " source: #{@source_files[new_namelist].join(', ')}"
|
48
|
+
puts " defaults: #{@defaults_files[namelist_sym(namelist)]}"
|
49
|
+
puts "Did not load data yet, though.\n\n"
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.add_namelist(new_namelist)
|
53
|
+
if new_namelist.nil? || new_namelist.empty?
|
54
|
+
raise(NamelistError.new, 'Must provide a namelist name.')
|
55
|
+
end
|
56
|
+
return if @namelists.include? namelist_sym(new_namelist)
|
57
|
+
@namelists << namelist_sym(new_namelist)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.set_source_files(namelist, new_sources)
|
61
|
+
# set source files. There may be more than one, so we ALWAYS make it an
|
62
|
+
# array. Flatten magic allows for users to supply an array or a scalar
|
63
|
+
# (single string)
|
64
|
+
if new_sources.nil? || new_sources.empty?
|
65
|
+
raise NamelistError.new,
|
66
|
+
"Must provide a source file for namelist #{namelist}. For " \
|
67
|
+
'example, $MESA_DIR/star/private/star_job_controls.inc for ' \
|
68
|
+
'star_job.'
|
69
|
+
end
|
70
|
+
source_to_add = if new_sources.respond_to?(:map)
|
71
|
+
new_sources.map(&:to_s)
|
72
|
+
else
|
73
|
+
new_sources.to_s
|
74
|
+
end
|
75
|
+
@source_files[namelist_sym(namelist)] = [source_to_add].flatten
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.set_defaults_file(namelist, new_defaults_file)
|
79
|
+
# set defaults file. This is limited to being scalar string for now.
|
80
|
+
return unless new_defaults_file
|
81
|
+
@defaults_files[namelist_sym(namelist)] = new_defaults_file.to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
# Delete all namelists and associated data.
|
85
|
+
def self.delete_all_namelists
|
86
|
+
namelists.each { |namelist| remove_namelist(namelist) }
|
87
|
+
@have_data = false
|
88
|
+
end
|
89
|
+
|
90
|
+
# delete namelist and associated data
|
91
|
+
def self.delete_namelist(namelist)
|
92
|
+
to_delete = namelist_sym(namelist)
|
93
|
+
found_something = namelists.delete(to_delete)
|
94
|
+
found_something = delete_files(to_delete) || found_something
|
95
|
+
# this also undefines methods
|
96
|
+
found_something = delete_data(to_delete) || found_something
|
97
|
+
unless found_something
|
98
|
+
puts "WARNING: Attempting to delete namelist #{namelist} data, but it " \
|
99
|
+
"wasn't present in existing Inlist data. Nothing happened."
|
100
|
+
end
|
101
|
+
namelist
|
102
|
+
end
|
103
|
+
|
104
|
+
# just remove associated source and defaults files; don't touch underlying
|
105
|
+
# data or methods (if any exist yet)
|
106
|
+
def self.delete_files(namelist)
|
107
|
+
found_something = false
|
108
|
+
[source_files, defaults_files].each do |files|
|
109
|
+
found_something ||= files.delete(namelist_sym(namelist))
|
110
|
+
end
|
111
|
+
found_something
|
112
|
+
end
|
113
|
+
|
114
|
+
# see if data has been loaded, and if it has, delete all the methods
|
115
|
+
# associated with it and then wipe the data, too.
|
116
|
+
def self.delete_data(namelist)
|
117
|
+
to_delete = namelist_sym(namelist)
|
118
|
+
return false unless inlist_data.include? namelist_sym(to_delete)
|
119
|
+
delete_methods(to_delete)
|
120
|
+
inlist_data.delete(to_delete)
|
121
|
+
end
|
122
|
+
|
123
|
+
# just delete the methods. This will throw errors if they don't exist
|
124
|
+
def self.delete_methods(namelist)
|
125
|
+
@inlist_data[namelist_sym(namelist)].each { |datum| delete_method(datum) }
|
126
|
+
end
|
38
127
|
|
39
|
-
|
40
|
-
|
128
|
+
# short hand for adding star_job namelist using sensible defaults as of 10108
|
129
|
+
def self.add_star_job_defaults(verbose: false)
|
130
|
+
config_namelist(
|
131
|
+
namelist: :star_job,
|
132
|
+
source_files: File.join(ENV['MESA_DIR'], 'star', 'private',
|
133
|
+
'star_job_controls.inc'),
|
134
|
+
defaults_file: File.join(ENV['MESA_DIR'], 'star', 'defaults',
|
135
|
+
'star_job.defaults'),
|
136
|
+
verbose: verbose
|
137
|
+
)
|
138
|
+
end
|
41
139
|
|
42
|
-
#
|
140
|
+
# short hand for adding controls namelist using sensible defaults as of 10108
|
141
|
+
def self.add_controls_defaults(verbose: false)
|
142
|
+
config_namelist(
|
143
|
+
namelist: :controls,
|
144
|
+
source_files: [File.join(ENV['MESA_DIR'], 'star', 'private',
|
145
|
+
'star_controls.inc'),
|
146
|
+
File.join(ENV['MESA_DIR'], 'star', 'private',
|
147
|
+
"ctrls_io.#{f_end}")],
|
148
|
+
defaults_file: File.join(ENV['MESA_DIR'], 'star', 'defaults',
|
149
|
+
'controls.defaults'),
|
150
|
+
verbose: verbose
|
151
|
+
)
|
152
|
+
end
|
43
153
|
|
44
|
-
|
45
|
-
|
46
|
-
|
154
|
+
# short hand for adding pgstar namelist using sensible defaults as of 10108
|
155
|
+
def self.add_pgstar_defaults(verbose: false)
|
156
|
+
config_namelist(
|
157
|
+
namelist: :pgstar,
|
158
|
+
source_files: File.join(ENV['MESA_DIR'], 'star', 'private',
|
159
|
+
'pgstar_controls.inc'),
|
160
|
+
defaults_file: File.join(ENV['MESA_DIR'], 'star', 'defaults',
|
161
|
+
'pgstar.defaults'),
|
162
|
+
verbose: verbose
|
163
|
+
)
|
164
|
+
end
|
47
165
|
|
166
|
+
# short hand for adding binary_defaults namelist using sensible defaults as
|
167
|
+
# of 10108
|
168
|
+
def self.add_binary_defaults
|
169
|
+
config_namelist(
|
170
|
+
namelists: :binary_controls,
|
171
|
+
source_files: File.join(ENV['MESA_DIR'], 'binary', 'public',
|
172
|
+
'binary_controls.inc'),
|
173
|
+
defaults_file: File.join(ENV['MESA_DIR'], 'binary', 'defaults',
|
174
|
+
'binary_controls.defaults')
|
175
|
+
)
|
176
|
+
end
|
177
|
+
|
178
|
+
# quickly add all three major namelists for star module (star_job, controls,
|
179
|
+
# and pgstar)
|
180
|
+
def self.add_star_defaults
|
181
|
+
add_star_job_defaults
|
182
|
+
add_controls_defaults
|
183
|
+
add_pgstar_defaults
|
184
|
+
end
|
48
185
|
|
49
186
|
############### NO MORE [SIMPLE] USER-CUSTOMIZABLE FEATURES BELOW ##############
|
50
187
|
|
@@ -57,34 +194,44 @@ class Inlist
|
|
57
194
|
# Establish class instance variables
|
58
195
|
class << self
|
59
196
|
attr_accessor :have_data
|
60
|
-
attr_accessor :namelists, :
|
61
|
-
:nt_files
|
197
|
+
attr_accessor :namelists, :source_files, :defaults_files, :inlist_data
|
62
198
|
end
|
63
199
|
|
64
200
|
# Generate methods for the Inlist class that set various namelist parameters.
|
65
|
-
def self.get_data
|
201
|
+
def self.get_data(use_star_as_fallback: true)
|
202
|
+
# might need to add star data; preserves expected behavior (minus binary)
|
203
|
+
Inlist.add_star_defaults if use_star_as_fallback && Inlist.namelists.empty?
|
66
204
|
Inlist.namelists.each do |namelist|
|
67
|
-
@inlist_data[namelist] = Inlist.get_namelist_data(namelist
|
68
|
-
Inlist.nt_files[namelist], Inlist.d_files[namelist])
|
205
|
+
@inlist_data[namelist] = Inlist.get_namelist_data(namelist)
|
69
206
|
end
|
70
207
|
# create methods (interface) for each data category
|
71
208
|
@inlist_data.each_value do |namelist_data|
|
72
|
-
namelist_data.each
|
73
|
-
if datum.is_arr
|
74
|
-
Inlist.make_parentheses_method(datum)
|
75
|
-
else
|
76
|
-
Inlist.make_regular_method(datum)
|
77
|
-
end
|
78
|
-
end
|
209
|
+
namelist_data.each { |datum| Inlist.make_method(datum) }
|
79
210
|
end
|
80
211
|
# don't do this nonsense again unles specifically told to do so
|
81
212
|
Inlist.have_data = true
|
82
213
|
end
|
83
214
|
|
215
|
+
def self.make_method(datum)
|
216
|
+
if datum.is_arr
|
217
|
+
Inlist.make_parentheses_method(datum)
|
218
|
+
else
|
219
|
+
Inlist.make_regular_method(datum)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.delete_method(datum)
|
224
|
+
if datum.is_arr
|
225
|
+
Inlist.delete_parentheses_method(datum)
|
226
|
+
else
|
227
|
+
Inlist.delete_regular_method(datum)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
84
231
|
# Three ways to access array categories. All methods will cause the
|
85
232
|
# data category to be staged into your inlist, even if you do not change it
|
86
233
|
# Basically, if it appears in your mesascript, it will definitely appear
|
87
|
-
# in your inlist. A command can be unflagged by calling
|
234
|
+
# in your inlist. A command can be unflagged by calling
|
88
235
|
# `unflag_command('COMMAND_NAME')` where COMMAND_NAME is the case-sensitive
|
89
236
|
# name of the command to be unflagged.
|
90
237
|
#
|
@@ -104,19 +251,19 @@ class Inlist
|
|
104
251
|
# xa_lower_limit_species(1, 'h1') # flags and sets value 1
|
105
252
|
# xa_lower_limit_species 1, 'h1' # same
|
106
253
|
#
|
107
|
-
# For multi-dimensional arrays, things are even more vaired. You can treat
|
254
|
+
# For multi-dimensional arrays, things are even more vaired. You can treat
|
108
255
|
# them like 1-dimensional arrays with the "index" just being an array of
|
109
256
|
# indices, for instance:
|
110
|
-
#
|
257
|
+
#
|
111
258
|
# text_summary1_name[[1,2]] = 'star_mass' # flags ALL values and sets
|
112
259
|
# text_summary1_name([1,2], 'star_mass') # text_summary1_name(1,2)
|
113
260
|
# text_summary1_name [1,2], 'star_mass # to 'star_mass'
|
114
261
|
#
|
115
|
-
# text_summary1_name [1,2] # flags ALL values and
|
116
|
-
# text_summary1_name([1,2]) # returns
|
262
|
+
# text_summary1_name [1,2] # flags ALL values and
|
263
|
+
# text_summary1_name([1,2]) # returns
|
117
264
|
# # text_sumarry_name(1,2)
|
118
|
-
#
|
119
|
-
# text_summary_name() # flags ALL values and
|
265
|
+
#
|
266
|
+
# text_summary_name() # flags ALL values and
|
120
267
|
# text_summary_name # returns entire hash for
|
121
268
|
# # text_summary_name
|
122
269
|
#
|
@@ -126,64 +273,127 @@ class Inlist
|
|
126
273
|
#
|
127
274
|
# text_summary1_name(1, 2, 'star_mass')
|
128
275
|
# text_summary1_name 1, 2, 'star_mass' # same as above (first 3)
|
129
|
-
#
|
276
|
+
#
|
130
277
|
# text_summary1_name
|
131
|
-
|
278
|
+
|
132
279
|
def self.make_parentheses_method(datum)
|
133
|
-
|
280
|
+
method_name = datum.name
|
134
281
|
num_indices = datum.num_indices
|
135
|
-
|
282
|
+
|
283
|
+
# assignment array form
|
284
|
+
define_method(method_name + '[]=') do |arg1, arg2|
|
136
285
|
if num_indices > 1
|
137
|
-
|
286
|
+
unless arg1.is_a?(Array) && arg1.length == num_indices
|
287
|
+
raise "First argument of #{method_name}[]= (part in brackets) must "\
|
288
|
+
"be an array with #{num_indices} indices since #{method_name}"\
|
289
|
+
' is a multi-dimensional array.'
|
290
|
+
end
|
138
291
|
end
|
139
|
-
|
140
|
-
|
292
|
+
flag_command(method_name)
|
293
|
+
data_hash[method_name].value[arg1] = arg2
|
141
294
|
end
|
142
|
-
|
295
|
+
|
296
|
+
# de-referencing array form
|
297
|
+
define_method(method_name + '[]') do |arg|
|
143
298
|
if num_indices > 1
|
144
|
-
|
299
|
+
unless arg.is_a?(Array) && arg.length == num_indices
|
300
|
+
raise "Argument of #{method_name}[] (part in brackets) must be an " \
|
301
|
+
"array with #{num_indices} indices since #{method_name} is a "\
|
302
|
+
'multi-dimensional array.'
|
303
|
+
end
|
145
304
|
end
|
146
|
-
|
147
|
-
|
305
|
+
flag_command(method_name)
|
306
|
+
data_hash[method_name].value[arg]
|
148
307
|
end
|
149
|
-
|
150
|
-
|
308
|
+
|
309
|
+
# imperative multi-purpose form
|
310
|
+
define_method(method_name) do |*args|
|
311
|
+
flag_command(method_name)
|
151
312
|
case args.length
|
152
|
-
|
313
|
+
# just retrieve whole value (de-reference)
|
314
|
+
when 0 then data_hash[method_name].value
|
315
|
+
# just retrieve part of value (de-reference)
|
153
316
|
when 1
|
154
317
|
if num_indices > 1
|
155
|
-
|
318
|
+
unless args[0].is_a?(Array) && args[0].length == num_indices
|
319
|
+
raise "First argument of #{method_name} must be an array with " \
|
320
|
+
"#{num_indices} indices since #{method_name} is a " \
|
321
|
+
'multi-dimensional array OR must provide all indices as ' \
|
322
|
+
'separate arguments.'
|
323
|
+
end
|
156
324
|
end
|
157
|
-
|
325
|
+
data_hash[method_name].value[args[0]]
|
326
|
+
# might be trying to access or a multi-d array OR assign to an array.
|
158
327
|
when 2
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
328
|
+
# 1-D array with scalar value; simple assignement
|
329
|
+
if num_indices == 1 && !args[0].is_a?(Array)
|
330
|
+
data_hash[method_name].value[args[0]] = args[1]
|
331
|
+
# 2-D array, de-reference single value (NOT AN ASSIGNMENT!)
|
332
|
+
elsif num_indices == 2 && !args[0].is_a?(Array) &&
|
333
|
+
args[1].is_a?(Integer)
|
334
|
+
data_hash[method_name].value[args]
|
335
|
+
# Multi-d array with first argument being an array, second a value to
|
336
|
+
# assign; simple assignment
|
163
337
|
elsif num_indices > 1
|
164
|
-
|
165
|
-
|
338
|
+
unless args[0].is_a?(Array) && args[0].length == num_indices
|
339
|
+
raise "First argument of #{method_name} must be an array with " \
|
340
|
+
"#{num_indices} indices since #{method_name} is a " \
|
341
|
+
'multi-dimensional array OR must provide all indices as ' \
|
342
|
+
'separate arguments.'
|
343
|
+
end
|
344
|
+
data_hash[method_name].value[args[0]] = args[1]
|
345
|
+
# Can't parse... throw hands up.
|
166
346
|
else
|
167
|
-
raise "First argument of #{
|
168
|
-
|
169
|
-
|
170
|
-
|
347
|
+
raise "First argument of #{method_name} must be an array with "\
|
348
|
+
"#{num_indices} indices since #{method_name} is a "\
|
349
|
+
'multi-dimensional array OR must provide all indices as '\
|
350
|
+
'separate arguments. The optional final argument is what the '\
|
351
|
+
"#{method_name} would be set to. Omission of this argument "\
|
352
|
+
"will simply flag #{method_name} to appear in the inlist."
|
353
|
+
end
|
354
|
+
# one more argument than number of indices; first n are location to be
|
355
|
+
# assigned, last one is value to be assigned
|
171
356
|
when num_indices + 1
|
172
|
-
|
173
|
-
|
357
|
+
if args[0].is_a?(Array)
|
358
|
+
raise "Bad arguments for #{method_name}. Either provide an array " \
|
359
|
+
"of #{num_indices} indices for the first argument or provide "\
|
360
|
+
'each index in succession, optionally specifying the desired '\
|
361
|
+
'value for the last argument.'
|
362
|
+
end
|
363
|
+
data_hash[method_name].value[args[0..-2]] = args[-1]
|
364
|
+
# same number of arguments as indices; assume we are de-referencing a
|
365
|
+
# value
|
366
|
+
when num_indices then data_hash[method_name].value[args]
|
367
|
+
# give up... who knows what the user is doing?!
|
174
368
|
else
|
175
|
-
raise "Wrong number of arguments for #{
|
176
|
-
|
369
|
+
raise "Wrong number of arguments for #{method_name}. Can provide " \
|
370
|
+
'zero arguments (just flag command), one argument (array of ' \
|
371
|
+
'indices for multi-d array or one index for 1-d array), two ' \
|
372
|
+
'arguments (array of indices/single index for multi-/1-d array '\
|
373
|
+
'and a new value for the value), #{num_indices} arguments ' \
|
374
|
+
'where the elements themselves are the right indices (returns ' \
|
375
|
+
"the specified element of the array), or #{num_indices + 1} " \
|
376
|
+
'arguments to set the specific value and return it.'
|
377
|
+
end
|
177
378
|
end
|
178
|
-
alias_method
|
179
|
-
alias_method
|
180
|
-
|
379
|
+
alias_method method_name.downcase.to_sym, method_name.to_sym
|
380
|
+
alias_method((method_name.downcase + '[]').to_sym,
|
381
|
+
(method_name + '[]').to_sym)
|
382
|
+
alias_method((method_name.downcase + '[]=').to_sym,
|
383
|
+
(method_name + '[]=').to_sym)
|
384
|
+
end
|
385
|
+
|
386
|
+
def self.delete_parentheses_method(datum)
|
387
|
+
base_name = datum.name
|
388
|
+
method_names = [base_name, base_name + '[]', base_name + '[]=']
|
389
|
+
alias_names = method_names.map(&:downcase)
|
390
|
+
[method_names, alias_names].flatten.uniq.each { |meth| remove_method(meth) }
|
181
391
|
end
|
182
392
|
|
183
393
|
# Two ways to access/change scalars. All methods will cause the data category
|
184
394
|
# to be staged into your inlist, even if you do not change the value.
|
185
395
|
# Basically, if it appears in your mesascript, it will definitely appear in
|
186
|
-
# your inlist.
|
396
|
+
# your inlist.
|
187
397
|
#
|
188
398
|
# 1. Change value, like
|
189
399
|
# initial_mass(1.0)
|
@@ -200,23 +410,30 @@ class Inlist
|
|
200
410
|
# traditional for ruby (why do you want empty parentheses anyway?). Returns
|
201
411
|
# current value.
|
202
412
|
#
|
203
|
-
# A command can be unflagged by calling `unflag_command('COMMAND_NAME')`
|
413
|
+
# A command can be unflagged by calling `unflag_command('COMMAND_NAME')`
|
204
414
|
# where COMMAND_NAME is the case-sensitive name of the command to be
|
205
415
|
# unflagged.
|
206
416
|
|
207
417
|
def self.make_regular_method(datum)
|
208
|
-
|
209
|
-
define_method(
|
210
|
-
self.flag_command(
|
211
|
-
return self.data_hash[
|
212
|
-
self.data_hash[
|
418
|
+
method_name = datum.name
|
419
|
+
define_method(method_name) do |*args|
|
420
|
+
self.flag_command(method_name)
|
421
|
+
return self.data_hash[method_name].value if args.empty?
|
422
|
+
self.data_hash[method_name].value = args[0]
|
213
423
|
end
|
214
|
-
aliases = [(
|
215
|
-
(
|
216
|
-
|
217
|
-
aliases.each { |ali| alias_method ali,
|
424
|
+
aliases = [(method_name + '=').to_sym,
|
425
|
+
(method_name.downcase + '=').to_sym,
|
426
|
+
method_name.downcase.to_sym]
|
427
|
+
aliases.each { |ali| alias_method ali, method_name.to_sym }
|
218
428
|
end
|
219
429
|
|
430
|
+
def self.delete_regular_method(datum)
|
431
|
+
method_name = datum.name
|
432
|
+
aliases = [method_name + '=',
|
433
|
+
method_name.downcase + '=',
|
434
|
+
method_name.downcase]
|
435
|
+
[method_name, aliases].flatten.uniq.each { |meth| remove_method meth }
|
436
|
+
end
|
220
437
|
|
221
438
|
# Ensure provided value's data type matches expected data type. Then convert
|
222
439
|
# to string for printing to an inlist. If value is a string, change nothing
|
@@ -225,39 +442,49 @@ class Inlist
|
|
225
442
|
def self.parse_input(name, value, type)
|
226
443
|
if value.class == String
|
227
444
|
if type == :string
|
228
|
-
value = "'#{value}'" unless value[0] == "'"
|
445
|
+
value = "'#{value}'" unless value[0] == "'" && value[-1] == "'"
|
229
446
|
end
|
230
|
-
|
447
|
+
value
|
231
448
|
elsif type == :bool
|
232
449
|
unless [TrueClass, FalseClass].include?(value.class)
|
233
|
-
raise "Invalid value for namelist item #{name}: #{value}. Use "
|
234
|
-
|
450
|
+
raise "Invalid value for namelist item #{name}: #{value}. Use " \
|
451
|
+
"'.true.', '.false.', or a Ruby boolean (true/false)."
|
235
452
|
end
|
236
453
|
if value == true
|
237
|
-
|
454
|
+
'.true.'
|
238
455
|
elsif value == false
|
239
|
-
|
456
|
+
'.false.'
|
240
457
|
else
|
241
458
|
raise "Error converting value #{value} of #{name} to a boolean."
|
242
459
|
end
|
243
460
|
elsif type == :int
|
244
|
-
|
245
|
-
|
461
|
+
unless value.is_a?(Integer) || value.is_a?(Float)
|
462
|
+
raise "Invalid value for namelist item #{name}: #{value}. Must " \
|
463
|
+
'provide an int or float.'
|
464
|
+
end
|
246
465
|
if value.is_a?(Float)
|
247
|
-
puts "WARNING: Expected integer for #{name} but got #{value}. Value"
|
248
|
-
|
466
|
+
puts "WARNING: Expected integer for #{name} but got #{value}. Value" \
|
467
|
+
' will be converted to an integer.'
|
249
468
|
end
|
250
|
-
|
469
|
+
value.to_i.to_s
|
251
470
|
elsif type == :float
|
252
|
-
|
253
|
-
|
254
|
-
|
471
|
+
unless value.is_a?(Integer) || value.is_a?(Float)
|
472
|
+
raise "Invalid value for namelist item #{name}: #{value}. Must "\
|
473
|
+
'provide an int or float.'
|
474
|
+
end
|
475
|
+
res = format('%g', value).sub('e', 'd')
|
476
|
+
res += 'd0' unless res.include?('d')
|
477
|
+
res
|
255
478
|
elsif type == :type
|
256
|
-
puts "WARNING: 'type' values are currently unsupported
|
257
|
-
|
479
|
+
puts "WARNING: 'type' values are currently unsupported " \
|
480
|
+
"(regarding #{name}) because your humble author has no idea what " \
|
481
|
+
'they look like in an inlist. You should tell him what to do at ' \
|
482
|
+
"wmwolf@asu.edu. Your input, #{value}, has been passed through to "\
|
483
|
+
'your inlist verbatim.'
|
484
|
+
value.to_s
|
258
485
|
else
|
259
|
-
raise "Error parsing value for namelist item #{name}: #{value}.
|
260
|
-
"type was #{type}."
|
486
|
+
raise "Error parsing value for namelist item #{name}: #{value}. " \
|
487
|
+
"Expected type was #{type}."
|
261
488
|
end
|
262
489
|
end
|
263
490
|
|
@@ -268,7 +495,7 @@ class Inlist
|
|
268
495
|
# clean up and re-order your inlist, but all comments will be lost. All other
|
269
496
|
# information SHOULD remain intact.
|
270
497
|
def self.inlist_to_mesascript(inlist_file, script_file, dbg = false)
|
271
|
-
Inlist.get_data unless Inlist.have_data
|
498
|
+
Inlist.get_data unless Inlist.have_data # ensure we have inlist data
|
272
499
|
inlist_contents = File.readlines(inlist_file)
|
273
500
|
|
274
501
|
# make namelist separators comments
|
@@ -301,7 +528,7 @@ class Inlist
|
|
301
528
|
command =~ /(\s*$)/ # save buffer space
|
302
529
|
buffer_space = Regexp.last_match(1)
|
303
530
|
command.strip! # remove white space
|
304
|
-
name, value = command.split('=').map
|
531
|
+
name, value = command.split('=').map(&:strip)
|
305
532
|
if dbg
|
306
533
|
puts "name: #{name}"
|
307
534
|
puts "value: #{value}"
|
@@ -309,28 +536,28 @@ class Inlist
|
|
309
536
|
if name =~ /\((\d+)\)/ # fix 1D array assignments
|
310
537
|
name.sub!('(', '[')
|
311
538
|
name.sub!(')', ']')
|
312
|
-
name
|
539
|
+
name += ' ='
|
313
540
|
elsif name =~ /\((\s*\d+\s*,\s*)+\d\s*\)/ # fix multi-D arrays
|
314
541
|
# arrays become hashes in MesaScript, so rather than having multiple
|
315
542
|
# indices, the key becomes the array of indices themselves, hence
|
316
543
|
# the double braces replacing single parentheses
|
317
|
-
name.sub!('(', '[[')
|
544
|
+
name.sub!('(', '[[')
|
318
545
|
name.sub!(')', ']]')
|
319
|
-
name
|
546
|
+
name += ' ='
|
320
547
|
end
|
321
548
|
name.downcase!
|
322
|
-
if value =~ /'.*'/
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
549
|
+
results = if value =~ /'.*'/ || value =~ /".*"/
|
550
|
+
name + ' ' + value # leave strings alone
|
551
|
+
elsif %w[.true. .false.].include?(value.downcase)
|
552
|
+
name + ' ' + value.downcase.delete('.') # fix booleans
|
553
|
+
elsif value =~ /\d+\.?\d*([eEdD]\d+)?/
|
554
|
+
name + ' ' + value.downcase.sub('d', 'e') # fix floats
|
555
|
+
else
|
556
|
+
name + ' ' + value # leave everything else alone
|
557
|
+
end
|
331
558
|
result = leading_space + result + buffer_space + comment
|
332
559
|
if dbg
|
333
|
-
puts
|
560
|
+
puts 'parsed to:'
|
334
561
|
puts result
|
335
562
|
puts ''
|
336
563
|
end
|
@@ -342,11 +569,10 @@ class Inlist
|
|
342
569
|
f.puts ''
|
343
570
|
f.puts "Inlist.make_inlist('#{File.basename(inlist_file)}') do"
|
344
571
|
new_contents.each { |line| f.puts ' ' + line }
|
345
|
-
f.puts
|
572
|
+
f.puts 'end'
|
346
573
|
end
|
347
574
|
end
|
348
575
|
|
349
|
-
|
350
576
|
# Create an Inlist object, execute block of commands that presumably populate
|
351
577
|
# the inlist, then write the inlist to a file with the given name. This is
|
352
578
|
# the money routine with user-supplied commands in the instance_eval block.
|
@@ -370,29 +596,21 @@ class Inlist
|
|
370
596
|
# belongs to, and its relative ordering in that namelist. Bogus defaults are
|
371
597
|
# assigned according to the object's type, and the ordering is unknown.
|
372
598
|
|
373
|
-
def self.get_namelist_data(namelist
|
374
|
-
temp_data = Inlist.get_names_and_types(namelist
|
375
|
-
Inlist.get_defaults(temp_data, namelist
|
599
|
+
def self.get_namelist_data(namelist)
|
600
|
+
temp_data = Inlist.get_names_and_types(namelist)
|
601
|
+
Inlist.get_defaults(temp_data, namelist)
|
376
602
|
end
|
377
603
|
|
378
|
-
def self.get_names_and_types(namelist
|
379
|
-
nt_filenames ||= Inlist.nt_files[namelist]
|
380
|
-
unless nt_filenames.respond_to?(:each)
|
381
|
-
nt_filenames = [nt_filenames]
|
382
|
-
end
|
383
|
-
nt_full_paths = nt_filenames.map { |file| Inlist.nt_paths[namelist] + file }
|
384
|
-
|
604
|
+
def self.get_names_and_types(namelist)
|
385
605
|
namelist_data = []
|
386
606
|
|
387
|
-
|
388
|
-
unless File.
|
389
|
-
|
390
|
-
end
|
391
|
-
contents = File.readlines(nt_full_path)
|
607
|
+
source_files[namelist].each do |source_file|
|
608
|
+
raise "Couldn't find file #{source_file}" unless File.exist?(source_file)
|
609
|
+
contents = File.readlines(source_file)
|
392
610
|
|
393
611
|
# Throw out comments and blank lines, ensure remaining lines are a proper
|
394
612
|
# Fortran assignment, then remove leading and trailing white space
|
395
|
-
contents.reject! { |line| is_comment?(line)
|
613
|
+
contents.reject! { |line| is_comment?(line) || is_blank?(line) }
|
396
614
|
contents.map! do |line|
|
397
615
|
my_line = line.dup
|
398
616
|
my_line = my_line[0...my_line.index('!')] if has_comment?(my_line)
|
@@ -405,24 +623,24 @@ class Inlist
|
|
405
623
|
full_lines << Inlist.full_line(contents, i)
|
406
624
|
end
|
407
625
|
pairs = full_lines.map do |line|
|
408
|
-
line.split('::').map
|
626
|
+
line.split('::').map(&:strip)
|
409
627
|
end
|
410
628
|
pairs.each do |pair|
|
411
629
|
type = case pair[0]
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
630
|
+
when /logical/ then :bool
|
631
|
+
when /character/ then :string
|
632
|
+
when /real/ then :float
|
633
|
+
when /integer/ then :int
|
634
|
+
when /type/ then :type
|
635
|
+
else
|
636
|
+
raise "Couldn't determine type of entry #{pair[0]} in " \
|
637
|
+
"#{source_file}."
|
638
|
+
end
|
421
639
|
name_chars = pair[1].split('')
|
422
640
|
names = []
|
423
641
|
paren_level = 0
|
424
642
|
name_chars.each do |char|
|
425
|
-
if paren_level > 0
|
643
|
+
if paren_level > 0 && char == ','
|
426
644
|
names << '!'
|
427
645
|
next
|
428
646
|
elsif char == '('
|
@@ -432,7 +650,7 @@ class Inlist
|
|
432
650
|
end
|
433
651
|
names << char
|
434
652
|
end
|
435
|
-
names = names.join.split(',').map
|
653
|
+
names = names.join.split(',').map(&:strip)
|
436
654
|
names.each do |name|
|
437
655
|
is_arr = false
|
438
656
|
num_indices = 0
|
@@ -442,10 +660,9 @@ class Inlist
|
|
442
660
|
name.sub!(/\(.*\)/, '')
|
443
661
|
elsif pair[0] =~ /dimension\((.*)\)/i
|
444
662
|
is_arr = true
|
445
|
-
num_indices =
|
663
|
+
num_indices = Regexp.last_match[1].count(',') + 1
|
446
664
|
end
|
447
|
-
type_default = {:
|
448
|
-
:int => 0}
|
665
|
+
type_default = { bool: false, string: '', float: 0.0, int: 0 }
|
449
666
|
dft = is_arr ? Hash.new(type_default[type]) : type_default[type]
|
450
667
|
namelist_data << InlistItem.new(name, type, dft, namelist, -1, is_arr,
|
451
668
|
num_indices)
|
@@ -459,64 +676,85 @@ class Inlist
|
|
459
676
|
# Inlist.get_names_and_types and assigns defaults and orders to each item.
|
460
677
|
# Looks for this information in the specified defaults filename.
|
461
678
|
|
462
|
-
def self.get_defaults(temp_data, namelist,
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
contents
|
679
|
+
def self.get_defaults(temp_data, namelist, whine = false)
|
680
|
+
defaults_file = defaults_files[namelist]
|
681
|
+
unless File.exist?(defaults_file)
|
682
|
+
raise "Couldn't find file #{defaults_file}"
|
683
|
+
end
|
684
|
+
contents = File.readlines(defaults_file)
|
685
|
+
# throw out comments and blank lines
|
686
|
+
contents.reject! { |line| is_comment?(line) || is_blank?(line) }
|
687
|
+
# remaining lines should only be assignments. Only use the part of the line
|
688
|
+
# up to the comment character, then strip all whitespace
|
468
689
|
contents.map! do |line|
|
469
690
|
my_line = line.dup
|
470
|
-
if has_comment?(line)
|
471
|
-
|
691
|
+
my_line = my_line[0...my_line.index('!')] if has_comment?(line)
|
692
|
+
unless my_line =~ /=/
|
693
|
+
raise "Equal sign missing in line:\n\t #{my_line}\n in file " \
|
694
|
+
"#{full_path}."
|
472
695
|
end
|
473
|
-
raise "Equal sign missing in line:\n\t #{my_line}\n in file " +
|
474
|
-
"#{full_path}." unless my_line =~ /=/
|
475
696
|
my_line.strip!
|
476
697
|
end
|
477
|
-
|
478
|
-
|
479
|
-
|
698
|
+
# divide lines into two element arrays: name and value
|
699
|
+
pairs = contents.map { |line| line.split('=').map(&:strip) }
|
700
|
+
n_d_hash = {} # maps names to default values
|
701
|
+
n_o_hash = {} # maps names to default order in inlist
|
480
702
|
pairs.each_with_index do |pair, i|
|
481
703
|
name = pair[0]
|
482
704
|
default = pair[1]
|
705
|
+
# look for parentheses in name, indicating an array
|
483
706
|
if name =~ /\(.*\)/
|
707
|
+
# make selector be the stuff in the parentheses
|
484
708
|
selector = name[/\(.*\)/][1..-2]
|
709
|
+
# make name just be the part without parentheses
|
485
710
|
name.sub!(/\(.*\)/, '')
|
711
|
+
# colon indicates mass assignment
|
486
712
|
if selector.include?(':')
|
487
713
|
default = Hash.new(default)
|
488
|
-
|
489
|
-
|
714
|
+
# lack of a comma indicates dimension = 1
|
715
|
+
elsif selector.count(',').zero?
|
716
|
+
default = { selector.to_i => default }
|
717
|
+
# at least one comma, so dimension > 1
|
490
718
|
else
|
719
|
+
# reformat the selector (now a key in the default hash) to an
|
720
|
+
# array of integers
|
491
721
|
selector = selector.split(',').map { |index| index.strip.to_i }
|
492
|
-
default =
|
722
|
+
default = { selector => default }
|
493
723
|
end
|
494
724
|
end
|
725
|
+
# if the default value is a hash, we probably don't have every possible
|
726
|
+
# value, so just merge scraped values with the automatically chosen
|
727
|
+
# defaults
|
495
728
|
if n_d_hash[name].is_a?(Hash)
|
496
729
|
n_d_hash[name].merge!(default)
|
730
|
+
# scalar values get a simple assignment
|
497
731
|
else
|
498
732
|
n_d_hash[name] = default
|
499
733
|
end
|
734
|
+
# order is just the same as the order it appeared in its defaults file
|
500
735
|
n_o_hash[name] ||= i
|
501
736
|
end
|
502
737
|
temp_data.each do |datum|
|
503
|
-
unless n_d_hash.
|
504
|
-
|
738
|
+
unless n_d_hash.key?(datum.name)
|
739
|
+
if whine
|
740
|
+
puts "WARNING: no default found for control #{datum.name}. Using " \
|
741
|
+
'standard defaults.'
|
742
|
+
end
|
505
743
|
end
|
506
744
|
default = n_d_hash[datum.name]
|
507
|
-
if default.is_a?(Hash)
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
745
|
+
datum.value = if default.is_a?(Hash) && datum.value.is_a?(Hash)
|
746
|
+
datum.value.merge(default)
|
747
|
+
else
|
748
|
+
default || datum.value
|
749
|
+
end
|
512
750
|
datum.order = n_o_hash[datum.name] || datum.order
|
513
751
|
end
|
514
752
|
temp_data
|
515
753
|
end
|
516
754
|
|
517
|
-
def self.full_line(lines,
|
518
|
-
return lines[
|
519
|
-
[lines[
|
755
|
+
def self.full_line(lines, indx)
|
756
|
+
return lines[indx] unless lines[indx][-1] == '&'
|
757
|
+
[lines[indx].sub('&', ''), full_line(lines, indx + 1)].join(' ')
|
520
758
|
end
|
521
759
|
|
522
760
|
def self.is_comment?(line)
|
@@ -538,14 +776,15 @@ class Inlist
|
|
538
776
|
|
539
777
|
attr_accessor :data_hash
|
540
778
|
attr_reader :names
|
541
|
-
def initialize
|
542
|
-
|
779
|
+
def initialize(use_star_as_fallback: true)
|
780
|
+
unless Inlist.have_data?
|
781
|
+
Inlist.get_data(use_star_as_fallback: use_star_as_fallback)
|
782
|
+
end
|
543
783
|
@data = Inlist.inlist_data
|
544
784
|
@data_hash = {}
|
545
785
|
@data.each_value do |namelist_data|
|
546
786
|
namelist_data.each do |datum|
|
547
787
|
@data_hash[datum.name] = datum.dup
|
548
|
-
|
549
788
|
end
|
550
789
|
end
|
551
790
|
@names = @data_hash.keys
|
@@ -570,7 +809,7 @@ class Inlist
|
|
570
809
|
def flag_command(name)
|
571
810
|
@data_hash[name].flagged = true
|
572
811
|
end
|
573
|
-
|
812
|
+
|
574
813
|
def unflag_command(name)
|
575
814
|
@data_hash[name].flagged = false
|
576
815
|
end
|
@@ -580,21 +819,22 @@ class Inlist
|
|
580
819
|
if datum.is_arr
|
581
820
|
lines = @data_hash[name].value.keys.map do |key|
|
582
821
|
prefix = " #{datum.name}("
|
583
|
-
suffix =
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
822
|
+
suffix = ') = ' +
|
823
|
+
Inlist.parse_input(datum.name, datum.value[key], datum.type) +
|
824
|
+
"\n"
|
825
|
+
indices = if key.respond_to?(:inject)
|
826
|
+
key[1..-1].inject(key[0].to_s) do |res, elt|
|
827
|
+
"#{res}, #{elt}"
|
828
|
+
end
|
829
|
+
else
|
830
|
+
key.to_s
|
831
|
+
end
|
592
832
|
prefix + indices + suffix
|
593
833
|
end
|
594
834
|
lines = lines.join
|
595
835
|
@to_write[datum.namelist][datum.order] = lines
|
596
836
|
else
|
597
|
-
@to_write[datum.namelist][datum.order] =
|
837
|
+
@to_write[datum.namelist][datum.order] = ' ' + datum.name + ' = ' +
|
598
838
|
Inlist.parse_input(datum.name, datum.value, datum.type) + "\n"
|
599
839
|
end
|
600
840
|
end
|
@@ -615,13 +855,13 @@ class Inlist
|
|
615
855
|
# blank lines between disparate data
|
616
856
|
namelists.each do |namelist|
|
617
857
|
@to_write[namelist].each_index do |i|
|
618
|
-
next if
|
858
|
+
next if [0, @to_write[namelist].size - 1].include? i
|
619
859
|
this_line = @to_write[namelist][i]
|
620
|
-
prev_line = @to_write[namelist][i-1]
|
621
|
-
|
860
|
+
prev_line = @to_write[namelist][i - 1]
|
861
|
+
|
622
862
|
this_line = '' if this_line.nil?
|
623
863
|
prev_line = '' if prev_line.nil?
|
624
|
-
if this_line.empty?
|
864
|
+
if this_line.empty? && !(prev_line.empty? || prev_line == "\n")
|
625
865
|
@to_write[namelist][i] = "\n"
|
626
866
|
end
|
627
867
|
end
|
@@ -634,10 +874,11 @@ class Inlist
|
|
634
874
|
result = ''
|
635
875
|
namelists.each do |namelist|
|
636
876
|
result += "\n&#{namelist}\n"
|
637
|
-
result += @to_write[namelist].join(
|
877
|
+
result += @to_write[namelist].join('')
|
638
878
|
result += "\n/ ! end of #{namelist} namelist\n"
|
639
879
|
end
|
640
880
|
result.sub("\n\n\n", "\n\n")
|
641
881
|
end
|
642
|
-
|
643
882
|
end
|
883
|
+
|
884
|
+
class NamelistError < Exception; end
|
metadata
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mesa_script
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- William Wolf
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-05-14 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: MesaScript - a DSL for making dynamic inlists for the MESA stellar evolution
|
14
14
|
code.
|
15
|
-
email: wmwolf@
|
15
|
+
email: wmwolf@asu.edu
|
16
16
|
executables:
|
17
17
|
- inlist2mesascript
|
18
18
|
extensions: []
|
@@ -25,13 +25,7 @@ homepage: https://wmwolf.github.io
|
|
25
25
|
licenses:
|
26
26
|
- MIT
|
27
27
|
metadata: {}
|
28
|
-
post_install_message:
|
29
|
-
Thanks for installing MesaScript!
|
30
|
-
|
31
|
-
To learn more about how to use MesaScript and to keep up to date with its
|
32
|
-
features, check out the README at the project homepage:
|
33
|
-
|
34
|
-
https://github.com/wmwolf/MesaScript
|
28
|
+
post_install_message:
|
35
29
|
rdoc_options: []
|
36
30
|
require_paths:
|
37
31
|
- lib
|
@@ -47,12 +41,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
41
|
version: '0'
|
48
42
|
requirements: []
|
49
43
|
rubyforge_project:
|
50
|
-
rubygems_version: 2.
|
44
|
+
rubygems_version: 2.6.14
|
51
45
|
signing_key:
|
52
46
|
specification_version: 4
|
53
47
|
summary: MesaScript is a domain specific language (DSL) that allows the user to write
|
54
48
|
inlists for MESA that include variables, loops, conditionals, etc. For more detailed
|
55
|
-
instructions, see the readme on the github page at
|
49
|
+
instructions, see the readme on the github page at https://github.com/wmwolf/MesaScript This
|
56
50
|
software requires a relatively modern installation of MESA (version > 5596). It
|
57
51
|
has been tested on Ruby versions > 1.9, but there is no guarantee it will work on
|
58
52
|
older (or newer!) versions. Any bugs or requests should be sent to the author, Bill
|