mesa_script 0.1.4 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|