mesa_script 0.1
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 +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: []
|