mesa_script 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +292 -0
- data/bin/inlist2mesascript +24 -0
- data/lib/mesa_script.rb +618 -0
- metadata +54 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ca9b7036396472f5a9ea98119ef282a875bac4e8
|
4
|
+
data.tar.gz: 6e2c6f0ffa3ef59365fd03815ea81b88fa5b316c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 46b15cee048454f84c79bfc6deaafe9356b91cfd60959599b7d874ab3a80194118d88e19c91b0ce26e9084933c65f52f8f37624b0f56a2c40b9fab78c75c5f0e
|
7
|
+
data.tar.gz: 28b1bc30eec9a956f760b30fda344c54f869f266fc94cc305c5893ce704efe9d13c629ba32256730dcadd799966faff035250b3f0547880604d7bfc92a3c772f
|
data/README.md
ADDED
@@ -0,0 +1,292 @@
|
|
1
|
+
MesaScript
|
2
|
+
==========
|
3
|
+
|
4
|
+
###MESA Requirement!
|
5
|
+
In its current state, MesaScript requires MESA rev. 5596 or above. This is due
|
6
|
+
to a sensitivity to where the `'.inc'` files are stored on earlier versions. If
|
7
|
+
there is demand for MesaScript for earlier revisions, I will look into making
|
8
|
+
it backward compatible.
|
9
|
+
|
10
|
+
###The Short Short Version
|
11
|
+
To get up and running fast, skip to installation, then try and use the included
|
12
|
+
sample file, `sample.rb` (via running `ruby sample.rb` in the command line). The
|
13
|
+
comments in `sample.rb` should get you started, especially if you have at least
|
14
|
+
a little Ruby know-how.
|
15
|
+
###What is MesaScript?
|
16
|
+
Lightweight, Ruby-based language that provides a powerful way to make inlists
|
17
|
+
for MESA projects. Really, MesaScript is a DSL (domain-specific language) built
|
18
|
+
on top of Ruby, so Ruby code "just works" inside MesaScript (if you're familiar
|
19
|
+
with it, think of what SASS is to CSS, but on a smaller scale).
|
20
|
+
|
21
|
+
### What does MesaScript do?
|
22
|
+
MesaScript provides a way to build inlists for use with
|
23
|
+
[MESA](http://mesa.sourceforge.net) using Ruby, though you need not know much
|
24
|
+
at all about Ruby to use it. The main point is that you can use
|
25
|
+
variables when creating an inlist, making a reusable template for parameter
|
26
|
+
space studies when only a few inlist commands vary between a large number of
|
27
|
+
inlists. Most, if not all, of what MesaScript does can be done by using MESA's
|
28
|
+
`run_star_extras` hooks, but for the purposes of documenting what I do with
|
29
|
+
MESA, I find inlists more enlightening, and I try to stick to high-level
|
30
|
+
languages whenever I can.
|
31
|
+
|
32
|
+
There are other benefits, too. MesaScript automatically checks your input to
|
33
|
+
make sure that the types of arguments you give for various namelist items match
|
34
|
+
what is expected, and the resulting inlist is neatly formatted and sensibly
|
35
|
+
ordered. You can also easily convert an existing inlist to MesaScript for
|
36
|
+
editing and further generalization. In general, *writing an inlist in*
|
37
|
+
*MesaScript is no more difficult than writing a normal inlist, but you have far*
|
38
|
+
*more flexibility*. So why not give it a try?
|
39
|
+
|
40
|
+
If you know a little Ruby (want to learn?
|
41
|
+
[Try Ruby here!](http://tryruby.org/levels/1/challenges/0)), the possibilities
|
42
|
+
are pretty wide open. You could easily make a script that starts with a given
|
43
|
+
set of parameters, run MESA star, then use the output of that run to dictate a
|
44
|
+
new inlist and run, creating a chain (maybe a MESA root find of sorts).
|
45
|
+
|
46
|
+
###Installation
|
47
|
+
Someday, I hope to package this as a gem, but for now, it's staying hosted on
|
48
|
+
Github, which means you need to install it yourself. Clone or otherwise
|
49
|
+
download the repository somewhere to your home directory with
|
50
|
+
|
51
|
+
git clone https://github.com/wmwolf/MesaScript.git ~/MesaScript
|
52
|
+
|
53
|
+
or somewhere else to your liking.
|
54
|
+
|
55
|
+
Then, either copy the file `mesa_script.rb` to somewhere along Ruby's path, or
|
56
|
+
set up another stand-in file that points to your `mesa_script.rb` file in
|
57
|
+
Ruby's path. To find Ruby's path, type
|
58
|
+
|
59
|
+
ruby -e 'puts $:'
|
60
|
+
|
61
|
+
in your terminal. If Ruby is properly configured, as it is on most modern Unix
|
62
|
+
systems, you should see a list of possible directories. Either copy
|
63
|
+
`mesa_script.rb` there or do what I do and make a new file called
|
64
|
+
`mesa_script.rb` there and have it just be
|
65
|
+
|
66
|
+
require '/PATH/TO/YOUR/CLONED/REPOSITORY/mesa_script.rb'
|
67
|
+
|
68
|
+
This way, if you later update your repo via `git pull`, you won't need to copy
|
69
|
+
`mesa_script.rb` again. Also, if you'd like to use the included (optional)
|
70
|
+
`inlist2mesascript` tool, copy that to somewhere along you system's path (
|
71
|
+
`echo $PATH`). Then type `inlist2mesascript -h` to learn more about that tool.
|
72
|
+
As you
|
73
|
+
might guess, it takes an existing MESA inlist and converts it to a file in
|
74
|
+
MesaScript that, if executed by Ruby should produce essentially the same inlist
|
75
|
+
(good for moving a project to MesaScript).
|
76
|
+
|
77
|
+
To check if Ruby can see the file, try doing `ruby -e 'require "mesa_script"'`.
|
78
|
+
If no error occurs, it is working fine.
|
79
|
+
|
80
|
+
Finally, you must have your `MESA_DIR` environment variable set for anything to
|
81
|
+
work. The `mesa_script.rb` file generates all the necessary data it needs from
|
82
|
+
the MESA source on the fly (this also makes it nearly MESA version
|
83
|
+
independent).
|
84
|
+
|
85
|
+
###Basic Usage
|
86
|
+
The `mesa_script.rb` file defines just one class, Inlist, which we'll interact
|
87
|
+
with primarily through one class method, `make_inlist`. Just put the following
|
88
|
+
in a file to make a blank inlist:
|
89
|
+
|
90
|
+
require 'mesa_script'
|
91
|
+
|
92
|
+
Inlist.make_inlist('babys_first_inlist') {
|
93
|
+
# inlist commands go here
|
94
|
+
}
|
95
|
+
|
96
|
+
This creates a file called `babys_first_inlist` that will be pretty
|
97
|
+
boring. It will create three namelists (the usual `star_job`, `controls`, and
|
98
|
+
`pgstar`) and leaves them blank inside, which is a perfectly acceptable inlist
|
99
|
+
for MESA to use, since it has defaults available. Now let's say you put this in
|
100
|
+
a file called `my_first_mesascript.rb` (`.rb` is the extension for Ruby files,
|
101
|
+
by the way). Then to actually generate the inlist, enter
|
102
|
+
`ruby my_first_mesascript.rb` at the command line and watch in awe as
|
103
|
+
`babys_first_inlist` pops into existence. You've created an inlist using
|
104
|
+
MesaScript, and you did so using fewer lines than it would have taken to
|
105
|
+
actually make that inlist on your own (technically)!
|
106
|
+
|
107
|
+
###Entering Inlist Commands
|
108
|
+
Making blank inlists is boring, so now let's cover how you actually make useful
|
109
|
+
inlists. For mesa inlists, there are really only two types of declarations:
|
110
|
+
those for scalars and those for array. Let's talk about scalars first, since
|
111
|
+
they are far more common. Then we'll get to the more complicated array
|
112
|
+
assignments.
|
113
|
+
|
114
|
+
####Scalar Assignments
|
115
|
+
As an example, let's say we want to set the initial mass of our star to 2.0
|
116
|
+
solar masses. The inlist command for this is `initial_mass`. In a regular
|
117
|
+
inlist file, we would need to put this in the proper namelist, `&controls` as
|
118
|
+
`initial_mass = 2.0`. In MesaScript, there are two ways to do this:
|
119
|
+
|
120
|
+
initial_mass 2.0 # this
|
121
|
+
initial_mass(2.0) # is the same as this
|
122
|
+
|
123
|
+
In Ruby, parentheses are optional for method calls, so either way is
|
124
|
+
acceptable. Note that unlike in normal inlists, MesaScript doesn't care about
|
125
|
+
the namelist this attribute belongs to. It'll figure it out on its own and
|
126
|
+
place it appropriately.
|
127
|
+
|
128
|
+
**WARNING**: You *cannot* use the standard inlist notation of
|
129
|
+
|
130
|
+
initial_mass = 2.0 # DON'T EVER DO THIS EVER EVER EVER
|
131
|
+
|
132
|
+
it will *not* throw an error, because it will simply set a new Ruby variable
|
133
|
+
called `initial_mass`. (For the person curious as to why I didn't program this
|
134
|
+
functionality in, google something like "instance_eval setter method" to
|
135
|
+
discover what took me too long to figure out.)
|
136
|
+
|
137
|
+
####Array Assignments
|
138
|
+
As an example, let's say we want to set a lower limit on a certain central
|
139
|
+
abundance as a stopping condition. Then we would, at the minimum, need to set
|
140
|
+
the inlist command `xa_central_lower_limit_species(1) = 'h1'`, for example. In MesaScript, there are three ways to do this:
|
141
|
+
|
142
|
+
xa_central_lower_limit_species[1] = 'h1' # These are
|
143
|
+
xa_central_lower_limit_species(1, 'h1') # all the
|
144
|
+
xa_central_lower_limit_species 1, 'h1' # same
|
145
|
+
|
146
|
+
**WARNING**: Again, the standard inlist notation for array assignment will not
|
147
|
+
work:
|
148
|
+
|
149
|
+
xa_central_lower_limit_species(1) = 'h1' # THIS ENDS IN SADNESS
|
150
|
+
|
151
|
+
I tried to program this functionality in, and the kind people at
|
152
|
+
[StackOverflow](http://stackoverflow.com/questions/21036873/how-do-i-write-a-method-to-edit-an-array-hash-using-parentheses-instead-of-squar/21044781?noredirect=1#21044781) kindly but firmly convinced me it was utterly impossible to to with Ruby without writing a parser of my own. Just stick to the bracket syntax or the less natural parentheses/space notations.
|
153
|
+
|
154
|
+
####Other Details
|
155
|
+
That's really all you need to know to start making inlists with MesaScript,
|
156
|
+
though I should remind you, especially if you aren't familiar with Ruby, about
|
157
|
+
the basic types of entries you might use. Most inlist commands are one of the
|
158
|
+
following: booleans, strings, floats, or integers.
|
159
|
+
|
160
|
+
**Booleans** in Ruby are `true` and `false` (case matters, and no periods).
|
161
|
+
|
162
|
+
**Strings** work the same as in fortran, though
|
163
|
+
single quotes are more "literal" than double quotes. Double quotes allow for
|
164
|
+
escaped characters and string interpolation using the `#{...}` notation, which
|
165
|
+
might be useful. For instance,
|
166
|
+
|
167
|
+
my_mass = 2.0
|
168
|
+
initial_mass = my_mass
|
169
|
+
save_model true
|
170
|
+
save_model_filename "my_star_#{my_mass}.mod"
|
171
|
+
|
172
|
+
will produce (among other things) the line
|
173
|
+
`save_model_filename = 'my_star_2.0.mod'` in the resulting inlist. Note also the
|
174
|
+
utility of having the initial mass and the save file name being dependent on a
|
175
|
+
single variable.
|
176
|
+
|
177
|
+
**Integers** are just
|
178
|
+
integers (I don't know of a useful literal other than just typing out the
|
179
|
+
entire number, though you can use underscores to make it clearer, e.g.
|
180
|
+
`100_000_000` is the same as `100000000` in Ruby).
|
181
|
+
|
182
|
+
**Floats** use an "e", and never a "d" for an exponential indicator, e.g.
|
183
|
+
`6.02e23`. Ruby floats have arbitrary precision, so there are no doubles.
|
184
|
+
|
185
|
+
Finally, if a particular command is giving you trouble, you can always just encase what you *want* it to be (i.e. in Fortran lingo) in quotes (obviously this does nothing useful if MesaScript is expecting a string). For example
|
186
|
+
|
187
|
+
mass_change 1e-7
|
188
|
+
|
189
|
+
will have the same effect as
|
190
|
+
|
191
|
+
mass_change '1d-7'
|
192
|
+
|
193
|
+
since MesaScript will not try to parse `'1d-7'`. It was expecting a float, but
|
194
|
+
since it got a string, it assumes you know better than it.
|
195
|
+
|
196
|
+
A useful tidbit is that methods are case sensitive to a point. They have the
|
197
|
+
same "spelling" as what is found in the `.inc` file (like
|
198
|
+
`star/private/star_controls.inc`), but every method has an aliased method that
|
199
|
+
is the same, but all in lower case, so you don't need to remember the
|
200
|
+
capitalization so long as you remember the actual spelling.
|
201
|
+
|
202
|
+
Any Ruby inside the `make_inlist` block will be executed normally, and it can
|
203
|
+
see variables named outside of the block. So if you have some basic parameters
|
204
|
+
that can determine a large number of inlist commands, you can simply name those
|
205
|
+
parameters as variables at the top of your MesaScript file and then make the
|
206
|
+
actual MesaScript code weave them into your inlist appropriately. This way, the
|
207
|
+
actual parameter changing from inlist to inlist is taken outside of the actual
|
208
|
+
inlist commands so you don't forget to change a particular command when you
|
209
|
+
move on to a different run (like forgetting to change a `LOG_dir`, which I've
|
210
|
+
done a few too many times and thus overwritten some data).
|
211
|
+
|
212
|
+
###Deeper and Deeper...
|
213
|
+
Are you still reading this? Well, you must want to do more.
|
214
|
+
|
215
|
+
###Using Custom Namelists
|
216
|
+
You can also make MesaScript know about additional namelists (or forget about
|
217
|
+
the standard three). After requiring the `mesa_script` file, you can change the
|
218
|
+
namelists it cares about via the following commands (obviously subbing out any
|
219
|
+
string containing `'namelist1'` or `'namelist2'` with your own appropriate
|
220
|
+
strings):
|
221
|
+
|
222
|
+
require 'mesa_script'
|
223
|
+
|
224
|
+
Inlist.namelists = ['namelist1', 'namelist2'] # all namelists you want
|
225
|
+
|
226
|
+
# Then indicate the name of the '.inc' files like star/private/star_controls.inc
|
227
|
+
Inlist.nt_files = {
|
228
|
+
'namelist1' => 'namelist1_controls.inc',
|
229
|
+
'namelist2' => 'namelist2_controls.inc'
|
230
|
+
}
|
231
|
+
# Then indicate the names of the '.defaults' files like those in star/defaults
|
232
|
+
Inlist.d_files = {
|
233
|
+
'namelist1' => 'namelist1.defaults,
|
234
|
+
'namelist2' => 'namelist2.defaults
|
235
|
+
}
|
236
|
+
# Then specify the paths to the files
|
237
|
+
Inlist.nt_paths ={
|
238
|
+
'namelist1' => '/path/to/namelist1_controls.inc',
|
239
|
+
'namelist2' => '/path/to/namelist2_controls.inc'
|
240
|
+
}
|
241
|
+
|
242
|
+
That *should* set things up to work with custom namelists, so long as the
|
243
|
+
`.inc` and `.defaults` files are formatted more or less the same as the "stock"
|
244
|
+
ones.
|
245
|
+
|
246
|
+
###Accessing Current Values and Displaying Default Values
|
247
|
+
Perhaps you want to display a default value in your inlist, but not actually
|
248
|
+
change it. Well, most of the assignment methods mentioned earlier
|
249
|
+
are also getter methods. I haven't mentioned how these methods actually work, so I'll do so now since you're still reading this manifesto.
|
250
|
+
|
251
|
+
These methods first flag the name of the data category for going into the
|
252
|
+
inlist. Then if a new value is supplied to them, it changes the value in the
|
253
|
+
`Inlist` object's internal hash. Then, when all the user-supplied code has been
|
254
|
+
executed, it gathers all the flagged data and formats it into
|
255
|
+
properly-formatted namelists, which it then prints out in sequence to the file
|
256
|
+
name provided by the user. One final note about these methods, they always
|
257
|
+
return the value associated with the inlist object (the new one if you assign
|
258
|
+
it, or the current/default value if you don't set one).
|
259
|
+
|
260
|
+
So if you want to access any scalar, just call its method without an argument.
|
261
|
+
Not only does this return the default value, but it also flags the category for
|
262
|
+
inclusion in the inlist so
|
263
|
+
|
264
|
+
save_this_value = initial_mass
|
265
|
+
|
266
|
+
will set `save_this_value` to `1.0` (the default value in `controls.defaults`)
|
267
|
+
unless you had already assigned another value, in which case that would be saved
|
268
|
+
instead. Additionally, `initial_mass = 1.0` will appear in the final inlist,
|
269
|
+
even though we didn't give `initial_mass` a new value. In fact, we could just
|
270
|
+
have a line like
|
271
|
+
|
272
|
+
initial_z
|
273
|
+
|
274
|
+
that neither uses the return value nor changes the stored value. This will just
|
275
|
+
flag `initial_z` for being put in the final inlist. Note that there is
|
276
|
+
currently no way to unflag an inlist item.
|
277
|
+
|
278
|
+
For arrays, things work like you might expect. Any time any one of the versions
|
279
|
+
of the array methods are called, that entire array category is staged for
|
280
|
+
inclusion in the inlist. For example, you could do any of the following:
|
281
|
+
|
282
|
+
xa_central_lower_limit # returns a hash of values
|
283
|
+
xa_central_lower_limit[1] # returns the value associated with 1 in the hash
|
284
|
+
xa_central_lower_limit(1) # same as above
|
285
|
+
xa_central_lower_limit 1 # same as above
|
286
|
+
|
287
|
+
Note that these array methods, as indicated, point to hashes (not arrays) of
|
288
|
+
values. So `xa_central_lower_limit_species[1] = 'h1'` would return
|
289
|
+
`{1 => 'h1'}`.
|
290
|
+
|
291
|
+
##Further Work
|
292
|
+
I warmly welcome bug reports, feature suggestions, and most all, pull requests!
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'mesa_script'
|
4
|
+
|
5
|
+
if ARGV.size == 0 or %w[-h --help].include?(ARGV[0])
|
6
|
+
puts ''
|
7
|
+
puts "inlist2mesascript help:"
|
8
|
+
puts '-----------------------'
|
9
|
+
puts "Only one command, which converts an inlist to a mesascript file:"
|
10
|
+
puts ''
|
11
|
+
puts 'inlist2mesascript SOURCE OUTPUT.rb'
|
12
|
+
puts ''
|
13
|
+
puts 'where SOURCE is your mesa inlist and OUTPUT is the name of the'
|
14
|
+
puts "mesascript file to be produced. '.rb' will not be appended, though"
|
15
|
+
puts "the resulting file will be a ruby/mesascript file."
|
16
|
+
puts ''
|
17
|
+
elsif ARGV.size == 2
|
18
|
+
source = ARGV[0]
|
19
|
+
output = ARGV[1]
|
20
|
+
|
21
|
+
Inlist.inlist_to_mesascript(source, output, false)
|
22
|
+
else
|
23
|
+
raise "Expected two arguments (received #{ARGV.size}). Enter 'inlist2mesa -h' for help."
|
24
|
+
end
|
data/lib/mesa_script.rb
ADDED
@@ -0,0 +1,618 @@
|
|
1
|
+
InlistItem = Struct.new(:name, :type, :value, :namelist, :order, :is_arr,
|
2
|
+
:num_indices, :flagged)
|
3
|
+
|
4
|
+
class Inlist
|
5
|
+
|
6
|
+
|
7
|
+
@inlist_data = {}
|
8
|
+
# Different namelists can be added or subtracted if MESA should change or
|
9
|
+
# proprietary inlists are required. Later hashes should be edited in a
|
10
|
+
# similar way to get the desired behavior for additional namelists.
|
11
|
+
|
12
|
+
#################### ADD NEW NAMELISTS HERE ####################
|
13
|
+
@namelists = %w{ star_job controls pgstar }
|
14
|
+
################## POINT TO .INC FILES HERE ####################
|
15
|
+
@nt_files = {
|
16
|
+
'star_job' => %w{star_job_controls.inc},
|
17
|
+
'controls' => %w{star_controls.inc ctrls_io.f},
|
18
|
+
'pgstar' => %w{pgstar_controls.inc}
|
19
|
+
}
|
20
|
+
# User can specify a custom name for a namelist defaults file. The default
|
21
|
+
# is simply the namelist name followed by '.defaults'
|
22
|
+
|
23
|
+
################ POINT TO .DEFAULTS FILES HERE #################
|
24
|
+
@d_files = {}
|
25
|
+
|
26
|
+
# User can add new paths to namelist default files through this hash
|
27
|
+
|
28
|
+
############ GIVE PATHS TO .INC AND .DEF FILES HERE ###########
|
29
|
+
@nt_paths = Hash.new(ENV['MESA_DIR'] + '/star/private/')
|
30
|
+
@d_paths = Hash.new(ENV['MESA_DIR'] + '/star/defaults/')
|
31
|
+
|
32
|
+
|
33
|
+
############### NO MORE [SIMPLE] USER-CUSTOMIZABLE FEATURES BELOW ##############
|
34
|
+
|
35
|
+
# This tells the class to initialize its structure if it hasn't already.
|
36
|
+
# If new namelists are added after an instance is initialized, this can be
|
37
|
+
# redone manually by the Inlist.get_data command.
|
38
|
+
@have_data = false
|
39
|
+
|
40
|
+
# Set up interface to access/change customizable inlist initialization data.
|
41
|
+
# Establish class instance variables
|
42
|
+
class << self
|
43
|
+
attr_accessor :have_data
|
44
|
+
attr_accessor :namelists, :nt_paths, :d_paths, :inlist_data, :d_files,
|
45
|
+
:nt_files
|
46
|
+
end
|
47
|
+
|
48
|
+
# Generate methods for the Inlist class that set various namelist parameters.
|
49
|
+
def self.get_data
|
50
|
+
Inlist.namelists.each do |namelist|
|
51
|
+
@inlist_data[namelist] = Inlist.get_namelist_data(namelist,
|
52
|
+
Inlist.nt_files[namelist], Inlist.d_files[namelist])
|
53
|
+
end
|
54
|
+
# create methods (interface) for each data category
|
55
|
+
@inlist_data.each_value do |namelist_data|
|
56
|
+
namelist_data.each do |datum|
|
57
|
+
if datum.is_arr
|
58
|
+
Inlist.make_parentheses_method(datum)
|
59
|
+
else
|
60
|
+
Inlist.make_regular_method(datum)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
# don't do this nonsense again unles specifically told to do so
|
65
|
+
Inlist.have_data = true
|
66
|
+
end
|
67
|
+
|
68
|
+
# Three ways to access array categories. All methods will cause the
|
69
|
+
# data category to be staged into your inlist, even if you do not change it
|
70
|
+
# Basically, if it appears in your mesascript, it will definitely appear
|
71
|
+
# in your inlist. There is no way to unflag an entry.
|
72
|
+
#
|
73
|
+
# 1. Standard array way like
|
74
|
+
# xa_lower_limit_species[1] = 'h1'
|
75
|
+
# (note square braces, NOT parentheses). Returns new value.
|
76
|
+
#
|
77
|
+
# 2. Just access (and flag), but don't change via array access, like
|
78
|
+
# xa_lower_limit_species[1]
|
79
|
+
# (again, note square braces). Returns current value
|
80
|
+
#
|
81
|
+
# 3. No braces method, like
|
82
|
+
# xa_lower_limit_species() # flags and returns hash of values
|
83
|
+
# xa_lower_limit_species # same, but more ruby-esque
|
84
|
+
# xa_lower_limit_species(1) # flags and returns value 1
|
85
|
+
# xa_lower_limit_species 1 # Same
|
86
|
+
# xa_lower_limit_species(1, 'h1') # flags and sets value 1
|
87
|
+
# xa_lower_limit_species 1, 'h1' # same
|
88
|
+
#
|
89
|
+
# For multi-dimensional arrays, things are even more vaired. You can treat
|
90
|
+
# them like 1-dimensional arrays with the "index" just being an array of
|
91
|
+
# indices, for instance:
|
92
|
+
#
|
93
|
+
# text_summary1_name[[1,2]] = 'star_mass' # flags ALL values and sets
|
94
|
+
# text_summary1_name([1,2], 'star_mass') # text_summary1_name(1,2)
|
95
|
+
# text_summary1_name [1,2], 'star_mass # to 'star_mass'
|
96
|
+
#
|
97
|
+
# text_summary1_name [1,2] # flags ALL values and
|
98
|
+
# text_summary1_name([1,2]) # returns
|
99
|
+
# # text_sumarry_name(1,2)
|
100
|
+
#
|
101
|
+
# text_summary_name() # flags ALL values and
|
102
|
+
# text_summary_name # returns entire hash for
|
103
|
+
# # text_summary_name
|
104
|
+
#
|
105
|
+
# Alternatively, can use the more intuitive form where indices are separate
|
106
|
+
# and don't need to be in an array, but this only works with the parentheses
|
107
|
+
# versions (i.e. the first option directly above has no counterpart):
|
108
|
+
#
|
109
|
+
# text_summary1_name(1, 2, 'star_mass')
|
110
|
+
# text_summary1_name 1, 2, 'star_mass' # same as above (first 3)
|
111
|
+
#
|
112
|
+
# text_summary1_name
|
113
|
+
|
114
|
+
def self.make_parentheses_method(datum)
|
115
|
+
name = datum.name
|
116
|
+
num_indices = datum.num_indices
|
117
|
+
define_method(name + '[]=') do|arg1, arg2|
|
118
|
+
if num_indices > 1
|
119
|
+
raise "First argument of #{name}[]= (part in brackets) must be an array with #{num_indices} indices since #{name} is a multi-dimensional array." unless (arg1.is_a?(Array) and arg1.length == num_indices)
|
120
|
+
end
|
121
|
+
self.flag_command(name)
|
122
|
+
self.data_hash[name].value[arg1] = arg2
|
123
|
+
end
|
124
|
+
define_method(name + '[]') do |arg|
|
125
|
+
if num_indices > 1
|
126
|
+
raise "Argument of #{name}[] (part in brackets) must be an array with #{num_indices} indices since #{name} is a multi-dimensional array." unless (arg.is_a?(Array) and arg.length == num_indices)
|
127
|
+
end
|
128
|
+
self.flag_command(name)
|
129
|
+
self.data_hash[name].value[arg]
|
130
|
+
end
|
131
|
+
define_method(name) do |*args|
|
132
|
+
self.flag_command(name)
|
133
|
+
case args.length
|
134
|
+
when 0 then self.data_hash[name].value
|
135
|
+
when 1
|
136
|
+
if num_indices > 1
|
137
|
+
raise "First argument of #{name} must be an array with #{num_indices} indices since #{name} is a multi-dimensional array OR must provide all indices as separate arguments." unless (args[0].is_a?(Array) and args[0].length == num_indices)
|
138
|
+
end
|
139
|
+
self.data_hash[name].value[args[0]]
|
140
|
+
when 2
|
141
|
+
if num_indices == 1 and (not args[0].is_a?(Array))
|
142
|
+
self.data_hash[name].value[args[0]] = args[1]
|
143
|
+
elsif num_indices == 2 and (not args[0].is_a?(Array)) and args[1].is_a?(Fixnum)
|
144
|
+
self.data_hash[name].value[args]
|
145
|
+
elsif num_indices > 1
|
146
|
+
raise "First argument of #{name} must be an array with #{num_indices} indices since #{name} is a multi-dimensional array OR must provide all indices as separate arguments." unless (args[0].is_a?(Array) and args[0].length == num_indices)
|
147
|
+
self.data_hash[name].value[args[0]] = args[1]
|
148
|
+
else
|
149
|
+
raise "First argument of #{name} must be an array with #{num_indices} indices since #{name} is a multi-dimensional array OR must provide all indices as separate arguments. The optional final argument is what the #{name} would be set to. Omission of this argument will simply flag #{name} to appear in the inlist."
|
150
|
+
end
|
151
|
+
when num_indices
|
152
|
+
self.data_hash[name].value[args]
|
153
|
+
when num_indices + 1
|
154
|
+
raise "Bad arguments for #{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." if args[0].is_a?(Array)
|
155
|
+
self.data_hash[name].value[args[0..-2]] = args[-1]
|
156
|
+
else
|
157
|
+
raise "Wrong number of arguments for #{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."
|
158
|
+
end
|
159
|
+
end
|
160
|
+
alias_method name.downcase.to_sym, name.to_sym
|
161
|
+
alias_method (name.downcase + '[]').to_sym, (name + '[]').to_sym
|
162
|
+
alias_method (name.downcase + '[]=').to_sym, (name + '[]=').to_sym
|
163
|
+
end
|
164
|
+
|
165
|
+
# Two ways to access/change scalars. All methods will cause the data category
|
166
|
+
# to be staged into your inlist, even if you do not change the value.
|
167
|
+
# Basically, if it appears in your mesascript, it will definitely appear in
|
168
|
+
# your inlist. There is no way to unflag an entry.
|
169
|
+
#
|
170
|
+
# 1. Change value, like
|
171
|
+
# initial_mass(1.0)
|
172
|
+
# initial_mass 1.0
|
173
|
+
# This flags the category to go in your inlist and changes the value. There
|
174
|
+
# is no difference between these two syntaxes (it's built into ruby).
|
175
|
+
# Returns new value.
|
176
|
+
#
|
177
|
+
# 2. Just access, like
|
178
|
+
# initial_mass()
|
179
|
+
# initial_mass
|
180
|
+
# This flags the category, but does not change the value. Again, both
|
181
|
+
# syntaxes are allowed, though the one without parentheses is more
|
182
|
+
# traditional for ruby (why do you want empty parentheses anyway?). Returns
|
183
|
+
# current value.
|
184
|
+
|
185
|
+
def self.make_regular_method(datum)
|
186
|
+
name = datum.name
|
187
|
+
define_method(name) do |*args|
|
188
|
+
self.flag_command(name)
|
189
|
+
return self.data_hash[name].value if args.empty?
|
190
|
+
self.data_hash[name].value = args[0]
|
191
|
+
end
|
192
|
+
aliases = [(name + '=').to_sym,
|
193
|
+
(name.downcase + '=').to_sym,
|
194
|
+
name.downcase.to_sym]
|
195
|
+
aliases.each { |ali| alias_method ali, name.to_sym }
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
# Ensure provided value's data type matches expected data type. Then convert
|
200
|
+
# to string for printing to an inlist. If value is a string, change nothing
|
201
|
+
# (no protection). If value is a string and SHOULD be a string, wrap it in
|
202
|
+
# single quotes.
|
203
|
+
def self.parse_input(name, value, type)
|
204
|
+
if value.class == String
|
205
|
+
if type == :string
|
206
|
+
value = "'#{value}'" unless value[0] == "'" and value[-1] == "'"
|
207
|
+
end
|
208
|
+
return value
|
209
|
+
elsif type == :bool
|
210
|
+
unless [TrueClass, FalseClass].include?(value.class)
|
211
|
+
raise "Invalid value for namelist item #{name}: #{value}. Use " +
|
212
|
+
"'.true.', '.false.', or a Ruby boolean (true/false)."
|
213
|
+
end
|
214
|
+
if value == true
|
215
|
+
return '.true.'
|
216
|
+
elsif value == false
|
217
|
+
return '.false.'
|
218
|
+
else
|
219
|
+
raise "Error converting value #{value} of #{name} to a boolean."
|
220
|
+
end
|
221
|
+
elsif type == :int
|
222
|
+
raise "Invalid value for namelist item #{name}: #{value}. Must provide"+
|
223
|
+
" an int or float." unless value.is_a?(Integer) or value.is_a?(Float)
|
224
|
+
if value.is_a?(Float)
|
225
|
+
puts "WARNING: Expected integer for #{name} but got #{value}. Value" +
|
226
|
+
" will be converted to an integer."
|
227
|
+
end
|
228
|
+
return value.to_i.to_s
|
229
|
+
elsif type == :float
|
230
|
+
raise "Invalid value for namelist item #{name}: #{value}. Must provide " +
|
231
|
+
"an int or float." unless value.is_a?(Integer) or value.is_a?(Float)
|
232
|
+
return sprintf("%g", value).sub('e', 'd')
|
233
|
+
elsif type == :type
|
234
|
+
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 wmwolf@physics.ucsb.edu. Your input, #{value}, has been passed through to your inlist verbatim."
|
235
|
+
return value.to_s
|
236
|
+
else
|
237
|
+
raise "Error parsing value for namelist item #{name}: #{value}. Expected "
|
238
|
+
"type was #{type}."
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# Converts a standard inlist to its equivalent mesascript formulation.
|
243
|
+
# Comments are preserved and namelist separators are converted to comments.
|
244
|
+
# Note that comments do NOT get put back into the fortran inlist through
|
245
|
+
# mesascript. Converting an inlist to mesascript and then back again will
|
246
|
+
# clean up and re-order your inlist, but all comments will be lost. All other
|
247
|
+
# information SHOULD remain intact.
|
248
|
+
def self.inlist_to_mesascript(inlist_file, script_file, dbg = false)
|
249
|
+
Inlist.get_data unless Inlist.have_data # ensure we have inlist data
|
250
|
+
inlist_contents = File.readlines(inlist_file)
|
251
|
+
|
252
|
+
# make namelist separators comments
|
253
|
+
new_contents = inlist_contents.map do |line|
|
254
|
+
case line
|
255
|
+
when /^\s*&/ then '# ' + line.chomp # start namelist
|
256
|
+
when /^\s*\// then '# ' + line.chomp # end namelist
|
257
|
+
else
|
258
|
+
line.sub('!', '#').chomp # fix comments
|
259
|
+
end
|
260
|
+
end
|
261
|
+
new_contents.map! do |line|
|
262
|
+
if line =~ /^\s*#/ or line.strip.empty? # leave comments and blanks
|
263
|
+
result = line
|
264
|
+
else
|
265
|
+
if dbg
|
266
|
+
puts "parsing line:"
|
267
|
+
puts line
|
268
|
+
end
|
269
|
+
comment_pivot = line.index('#')
|
270
|
+
if comment_pivot
|
271
|
+
command = line[0...comment_pivot]
|
272
|
+
comment = line[comment_pivot..-1].to_s.strip
|
273
|
+
else
|
274
|
+
command = line
|
275
|
+
comment = ''
|
276
|
+
end
|
277
|
+
command =~ /(^\s*)/ # save leading space
|
278
|
+
leading_space = Regexp.last_match(1)
|
279
|
+
command =~ /(\s*$)/ # save buffer space
|
280
|
+
buffer_space = Regexp.last_match(1)
|
281
|
+
command.strip! # remove white space
|
282
|
+
name, value = command.split('=').map { |datum| datum.strip }
|
283
|
+
if dbg
|
284
|
+
puts "name: #{name}"
|
285
|
+
puts "value: #{value}"
|
286
|
+
end
|
287
|
+
if name =~ /\((\d+)\)/ # fix 1D array assignments
|
288
|
+
name.sub!('(', '[')
|
289
|
+
name.sub!(')', ']')
|
290
|
+
name = name + ' ='
|
291
|
+
elsif name =~ /\((\s*\d+\s*,\s*)+\d\s*\)/ # fix multi-D arrays
|
292
|
+
# arrays become hashes in MesaScript, so rather than having multiple
|
293
|
+
# indices, the key becomes the array of indices themselves, hence
|
294
|
+
# the double braces replacing single parentheses
|
295
|
+
name.sub!('(', '[[')
|
296
|
+
name.sub!(')', ']]')
|
297
|
+
name = name + ' ='
|
298
|
+
end
|
299
|
+
name.downcase!
|
300
|
+
if value =~ /'.*'/ or value =~ /".*"/
|
301
|
+
result = name + ' ' + value # leave strings alone
|
302
|
+
elsif %w[.true. .false.].include?(value.downcase)
|
303
|
+
result = name + ' ' + value.downcase.gsub('.', '') # fix booleans
|
304
|
+
elsif value =~ /\d+\.?\d*([eEdD]\d+)?/
|
305
|
+
result = name + ' ' + value.downcase.sub('d', 'e') # fix floats
|
306
|
+
else
|
307
|
+
result = name + ' ' + value # leave everything else alone
|
308
|
+
end
|
309
|
+
result = leading_space + result + buffer_space + comment
|
310
|
+
if dbg
|
311
|
+
puts "parsed to:"
|
312
|
+
puts result
|
313
|
+
puts ''
|
314
|
+
end
|
315
|
+
end
|
316
|
+
result
|
317
|
+
end
|
318
|
+
File.open(script_file, 'w') do |f|
|
319
|
+
f.puts "require 'mesa_script'"
|
320
|
+
f.puts ''
|
321
|
+
f.puts "Inlist.make_inlist('#{File.basename(inlist_file)}') do"
|
322
|
+
new_contents.each { |line| f.puts ' ' + line }
|
323
|
+
f.puts "end"
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
|
328
|
+
# Create an Inlist object, execute block of commands that presumably populate
|
329
|
+
# the inlist, then write the inlist to a file with the given name. This is
|
330
|
+
# the money routine with user-supplied commands in the instance_eval block.
|
331
|
+
def self.make_inlist(name = 'inlist', &block)
|
332
|
+
inlist = Inlist.new
|
333
|
+
inlist.instance_eval(&block)
|
334
|
+
inlist.stage_flagged
|
335
|
+
File.open(name, 'w') { |f| f.write(inlist) }
|
336
|
+
end
|
337
|
+
|
338
|
+
# Checks to see if the data/methods for the Inlist class has been initialized.
|
339
|
+
def self.have_data?
|
340
|
+
@have_data
|
341
|
+
end
|
342
|
+
|
343
|
+
# Reads names and types for a specified namelist from given file (intended
|
344
|
+
# to be of the form of something like star/private/star_controls.inc).
|
345
|
+
#
|
346
|
+
# Returns an array of InlistItem Struct instances that contain a parameter's
|
347
|
+
# name, type (:bool, :string, :float, :int, or :type), the namelist it
|
348
|
+
# belongs to, and its relative ordering in that namelist. Bogus defaults are
|
349
|
+
# assigned according to the object's type, and the ordering is unknown.
|
350
|
+
|
351
|
+
def self.get_namelist_data(namelist, nt_filename = nil, d_filename = nil)
|
352
|
+
temp_data = Inlist.get_names_and_types(namelist, nt_filename)
|
353
|
+
Inlist.get_defaults(temp_data, namelist, d_filename)
|
354
|
+
end
|
355
|
+
|
356
|
+
def self.get_names_and_types(namelist, nt_filenames = nil)
|
357
|
+
nt_filenames ||= Inlist.nt_files[namelist]
|
358
|
+
unless nt_filenames.respond_to?(:each)
|
359
|
+
nt_filenames = [nt_filenames]
|
360
|
+
end
|
361
|
+
nt_full_paths = nt_filenames.map { |file| Inlist.nt_paths[namelist] + file }
|
362
|
+
|
363
|
+
namelist_data = []
|
364
|
+
|
365
|
+
nt_full_paths.each do |nt_full_path|
|
366
|
+
unless File.exists?(nt_full_path)
|
367
|
+
raise "Couldn't find file #{nt_full_path}"
|
368
|
+
end
|
369
|
+
contents = File.readlines(nt_full_path)
|
370
|
+
|
371
|
+
# Throw out comments and blank lines, ensure remaining lines are a proper
|
372
|
+
# Fortran assignment, then remove leading and trailing white space
|
373
|
+
contents.reject! { |line| is_comment?(line) or is_blank?(line) }
|
374
|
+
contents.map! do |line|
|
375
|
+
my_line = line.dup
|
376
|
+
my_line = my_line[0...my_line.index('!')] if has_comment?(my_line)
|
377
|
+
my_line.strip!
|
378
|
+
end
|
379
|
+
full_lines = []
|
380
|
+
contents.each_with_index do |line, i|
|
381
|
+
break if line =~ /\A\s*contains/
|
382
|
+
next unless line =~ /::/
|
383
|
+
full_lines << Inlist.full_line(contents, i)
|
384
|
+
end
|
385
|
+
pairs = full_lines.map do |line|
|
386
|
+
line.split('::').map { |datum| datum.strip}
|
387
|
+
end
|
388
|
+
pairs.each do |pair|
|
389
|
+
type = case pair[0]
|
390
|
+
when /logical/ then :bool
|
391
|
+
when /character/ then :string
|
392
|
+
when /real/ then :float
|
393
|
+
when /integer/ then :int
|
394
|
+
when /type/ then :type
|
395
|
+
else
|
396
|
+
raise "Couldn't determine type of entry #{pair[0]} in " +
|
397
|
+
"#{nt_full_path}."
|
398
|
+
end
|
399
|
+
name_chars = pair[1].split('')
|
400
|
+
names = []
|
401
|
+
paren_level = 0
|
402
|
+
name_chars.each do |char|
|
403
|
+
if paren_level > 0 and char == ','
|
404
|
+
names << '!'
|
405
|
+
next
|
406
|
+
elsif char == '('
|
407
|
+
paren_level += 1
|
408
|
+
elsif char == ')'
|
409
|
+
paren_level -= 1
|
410
|
+
end
|
411
|
+
names << char
|
412
|
+
end
|
413
|
+
names = names.join.split(',').map { |name| name.strip }
|
414
|
+
names.each do |name|
|
415
|
+
is_arr = false
|
416
|
+
num_indices = 0
|
417
|
+
if name =~ /\(.*\)/
|
418
|
+
is_arr = true
|
419
|
+
num_indices = name.count('!') + 1
|
420
|
+
name.sub!(/\(.*\)/, '')
|
421
|
+
end
|
422
|
+
type_default = {:bool => false, :string => '', :float => 0.0,
|
423
|
+
:int => 0}
|
424
|
+
dft = is_arr ? Hash.new(type_default[type]) : type_default[type]
|
425
|
+
namelist_data << InlistItem.new(name, type, dft, namelist, -1, is_arr,
|
426
|
+
num_indices)
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
namelist_data
|
431
|
+
end
|
432
|
+
|
433
|
+
# Similar to Inlist.get_names_and_types, but takes the output of
|
434
|
+
# Inlist.get_names_and_types and assigns defaults and orders to each item.
|
435
|
+
# Looks for this information in the specified defaults filename.
|
436
|
+
|
437
|
+
def self.get_defaults(temp_data, namelist, d_filename = nil, whine = false)
|
438
|
+
d_filename ||= namelist + '.defaults'
|
439
|
+
d_full_path = Inlist.d_paths[namelist] + d_filename
|
440
|
+
raise "Couldn't find file #{d_filename}" unless File.exists?(d_full_path)
|
441
|
+
contents = File.readlines(d_full_path)
|
442
|
+
contents.reject! { |line| is_comment?(line) or is_blank?(line) }
|
443
|
+
contents.map! do |line|
|
444
|
+
my_line = line.dup
|
445
|
+
if has_comment?(line)
|
446
|
+
my_line = my_line[0...my_line.index('!')]
|
447
|
+
end
|
448
|
+
raise "Equal sign missing in line:\n\t #{my_line}\n in file " +
|
449
|
+
"#{full_path}." unless my_line =~ /=/
|
450
|
+
my_line.strip!
|
451
|
+
end
|
452
|
+
pairs = contents.map {|line| line.split('=').map {|datum| datum.strip}}
|
453
|
+
n_d_hash = {}
|
454
|
+
n_o_hash = {}
|
455
|
+
pairs.each_with_index do |pair, i|
|
456
|
+
name = pair[0]
|
457
|
+
default = pair[1]
|
458
|
+
if name =~ /\(.*\)/
|
459
|
+
selector = name[/\(.*\)/][1..-2]
|
460
|
+
name.sub!(/\(.*\)/, '')
|
461
|
+
if selector.include?(':')
|
462
|
+
default = Hash.new(default)
|
463
|
+
elsif selector.count(',') == 0
|
464
|
+
default = {selector.to_i => default}
|
465
|
+
else
|
466
|
+
selector = selector.split(',').map { |index| index.strip.to_i }
|
467
|
+
default = default = {selector => default}
|
468
|
+
end
|
469
|
+
end
|
470
|
+
if n_d_hash[name].is_a?(Hash)
|
471
|
+
n_d_hash[name].merge!(default)
|
472
|
+
else
|
473
|
+
n_d_hash[name] = default
|
474
|
+
end
|
475
|
+
n_o_hash[name] ||= i
|
476
|
+
end
|
477
|
+
temp_data.each do |datum|
|
478
|
+
unless n_d_hash.keys.include?(datum.name)
|
479
|
+
puts "WARNING: no default found for control #{datum.name}. Using standard defaults." if whine
|
480
|
+
end
|
481
|
+
default = n_d_hash[datum.name]
|
482
|
+
if default.is_a?(Hash) and datum.value.is_a?(Hash)
|
483
|
+
datum.value = datum.value.merge(default)
|
484
|
+
else
|
485
|
+
datum.value = default || datum.value
|
486
|
+
end
|
487
|
+
datum.order = n_o_hash[datum.name] || datum.order
|
488
|
+
end
|
489
|
+
temp_data
|
490
|
+
end
|
491
|
+
|
492
|
+
def self.full_line(lines, i)
|
493
|
+
return lines[i] unless lines[i][-1] == '&'
|
494
|
+
[lines[i].sub('&', ''), full_line(lines, i+1)].join(' ')
|
495
|
+
end
|
496
|
+
|
497
|
+
def self.is_comment?(line)
|
498
|
+
line =~ /\A\s*!/
|
499
|
+
end
|
500
|
+
|
501
|
+
def self.is_blank?(line)
|
502
|
+
not (line =~/[a-z0-9]+/)
|
503
|
+
end
|
504
|
+
|
505
|
+
def self.has_comment?(line)
|
506
|
+
line.include?('!')
|
507
|
+
end
|
508
|
+
|
509
|
+
# Making an instance of Inlist first checks to see if the class methods are
|
510
|
+
# set up for the namelists in Inlist.namelists. If they aren't ready, it
|
511
|
+
# creates them. Then creates a hash with an array associated to each namelist
|
512
|
+
# that is the exact size of the number of entries available in that namelist.
|
513
|
+
|
514
|
+
attr_accessor :data_hash
|
515
|
+
attr_reader :names
|
516
|
+
def initialize
|
517
|
+
Inlist.get_data unless Inlist.have_data?
|
518
|
+
@data = Inlist.inlist_data
|
519
|
+
@data_hash = {}
|
520
|
+
@data.each_value do |namelist_data|
|
521
|
+
namelist_data.each do |datum|
|
522
|
+
@data_hash[datum.name] = datum.dup
|
523
|
+
|
524
|
+
end
|
525
|
+
end
|
526
|
+
@names = @data_hash.keys
|
527
|
+
@data = {}
|
528
|
+
Inlist.namelists.each do |namelist|
|
529
|
+
@data[namelist] = Array.new(Inlist.inlist_data[namelist].size, '')
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
# Zeroes out all staged data and blank lines
|
534
|
+
def make_fresh_writelist
|
535
|
+
@to_write = {}
|
536
|
+
@data.keys.each do |namelist|
|
537
|
+
@to_write[namelist] = Array.new(@data[namelist].size, '')
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
def namelists
|
542
|
+
@data.keys
|
543
|
+
end
|
544
|
+
|
545
|
+
def flag_command(name)
|
546
|
+
@data_hash[name].flagged = true
|
547
|
+
end
|
548
|
+
|
549
|
+
def unflag_command(name)
|
550
|
+
@data_hash[name].flagged = false
|
551
|
+
end
|
552
|
+
|
553
|
+
def stage_namelist_command(name)
|
554
|
+
datum = @data_hash[name]
|
555
|
+
if datum.is_arr
|
556
|
+
lines = @data_hash[name].value.keys.map do |key|
|
557
|
+
prefix = " #{datum.name}("
|
558
|
+
suffix = ") = " +
|
559
|
+
Inlist.parse_input(datum.name, datum.value[key], datum.type) + "\n"
|
560
|
+
if key.respond_to?(:inject)
|
561
|
+
indices = key[1..-1].inject(key[0].to_s) do |res, elt|
|
562
|
+
"#{res}, #{elt}"
|
563
|
+
end
|
564
|
+
else
|
565
|
+
indices = key.to_s
|
566
|
+
end
|
567
|
+
prefix + indices + suffix
|
568
|
+
end
|
569
|
+
lines = lines.join
|
570
|
+
@to_write[datum.namelist][datum.order] = lines
|
571
|
+
else
|
572
|
+
@to_write[datum.namelist][datum.order] = " " + datum.name + ' = ' +
|
573
|
+
Inlist.parse_input(datum.name, datum.value, datum.type) + "\n"
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
# Marks a data category so that it can be staged into an inlist
|
578
|
+
def flagged
|
579
|
+
@data_hash.keys.select { |key| @data_hash[key].flagged }
|
580
|
+
end
|
581
|
+
|
582
|
+
# Collects all data categories into a hash of arrays (each array is a
|
583
|
+
# namelist) that is read whenever the inlist is converted to a string
|
584
|
+
# (i.e. when it is printed to a file or the screen).
|
585
|
+
def stage_flagged
|
586
|
+
make_fresh_writelist # start from scratch
|
587
|
+
|
588
|
+
flagged.each { |name| stage_namelist_command(name) } # stage each datum
|
589
|
+
|
590
|
+
# blank lines between disparate data
|
591
|
+
namelists.each do |namelist|
|
592
|
+
@to_write[namelist].each_index do |i|
|
593
|
+
next if (i == 0 or i == @to_write[namelist].size - 1)
|
594
|
+
this_line = @to_write[namelist][i]
|
595
|
+
prev_line = @to_write[namelist][i-1]
|
596
|
+
|
597
|
+
this_line = '' if this_line.nil?
|
598
|
+
prev_line = '' if prev_line.nil?
|
599
|
+
if this_line.empty? and not(prev_line.empty? or prev_line == "\n")
|
600
|
+
@to_write[namelist][i] = "\n"
|
601
|
+
end
|
602
|
+
end
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
# Takes the staged data categories and formats them into a string series of
|
607
|
+
# namelists that are MESA-readable.
|
608
|
+
def to_s
|
609
|
+
result = ''
|
610
|
+
namelists.each do |namelist|
|
611
|
+
result += "\n&#{namelist}\n"
|
612
|
+
result += @to_write[namelist].join("")
|
613
|
+
result += "\n/ ! end of #{namelist} namelist\n"
|
614
|
+
end
|
615
|
+
result.sub("\n\n\n", "\n\n")
|
616
|
+
end
|
617
|
+
|
618
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mesa_script
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- William Wolf
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-21 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: MesaScript - a DSL for making dynamic inlists for the MESA stellar evolution
|
14
|
+
code.
|
15
|
+
email: wmwolf@physics.ucsb.edu
|
16
|
+
executables:
|
17
|
+
- inlist2mesascript
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- README.md
|
22
|
+
- bin/inlist2mesascript
|
23
|
+
- lib/mesa_script.rb
|
24
|
+
homepage: https://wmwolf.github.io
|
25
|
+
licenses:
|
26
|
+
- MIT
|
27
|
+
metadata: {}
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements: []
|
43
|
+
rubyforge_project:
|
44
|
+
rubygems_version: 2.2.2
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: MesaScript is a domain specific language (DSL) that allows the user to write
|
48
|
+
inlists for MESA that include variables, loops, conditionals, etc. For more detailed
|
49
|
+
instructions, see the readme on the github page at https://github.com/wmwolf/MesaScript This
|
50
|
+
software requires a relatively modern installation of MESA (version > 5596). It
|
51
|
+
has been tested on Ruby versions > 1.9, but there is no guarantee it will work on
|
52
|
+
older (or newer!) versions. Any bugs or requests should be sent to the author, Bill
|
53
|
+
Wolf, at wmwolf@physics.ucsb.edu.
|
54
|
+
test_files: []
|