-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGeneFlyer.py
More file actions
337 lines (263 loc) · 12.2 KB
/
GeneFlyer.py
File metadata and controls
337 lines (263 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
""" Set of classes for converting genetic codes to ontimes and durations, then
flying the Zeeman simulation.
These classes derive from the :class:`GeneFlyer` base class and must implement
the abstract methods:
* :func:`GeneFlyer.createGene`
* :func:`GeneFlyer.geneBounds`
* :func:`GeneFlyer.flyGene`
* :func:`GeneFlyer.saveGene`
Implementation
==============
The role of a :class:`GeneFlyer` is to store a reference to
:class:`ZeemanFlyer` object, and act as a conduit between this and
:class:`GeneFitness`. The class essentially converts a gene to a set of `pos`
and `vel` arrays: the positions and velocities of the particles at the end of
flight. These arrays are returned to the :class:`GeneFitness` object that
called this. The :func:`GeneFlyer.flyGene` function performs the conversion,
then calls :func:`GeneFlyer.fly` to handle interacting with the
:class:`ZeeemanFlyer`.
Utility functions
=================
Subclasses also provide three utility functions that help set-up the CMA-ES
computation, by providing an initial gene through :func:GeneFlyer.createGene`,
and and array of upper and lower bounds through a call to
:func:`GeneFlyer.geneBounds`, that may be used by the CMA-ES algorithm. A single
gene can be saved to an output file by calling :func:`GeneFlyer.saveGene`.
Subclasses
==========
Currently implemented genes are:
* :class:`DurationGene` Stores the duration of each pulse, relative to a
starting point and using a fixed overlap.
* :class:`OffTimeGene` Stores the off-time for each coil, uses a fixed overlap.
:author: Chris Rennick
:copyright: Copyright 2015 University of Oxford.
"""
import abc
import ctypes
import ConfigParser
import logging
import os
import numpy as np
class GeneFlyer(object):
""" Abstract base class for converting a CMA-ES gene to a set of final
particle positions and velocities.
This class is used as a bridge between the :class:`GeneFitness` and the
underlying :class:`ZeemanFlyer` dynamics simulation by calling
:func:`flyGene`.
Attributes:
flyer (ZeemanFlyer) : Reference to an initialised simulation.
optprops (dict) : Dictionary of parameters from `OPTIMISER` section of
config file.
states (list) : List of indicies of states that are run in the `flyer`
ontimes (numpy:array) : Array of ontimes derived from the gene.
offtimes (numpy:array) : Array of offtimes derived from the gene.
"""
__metaclass__ = abc.ABCMeta
def __init__(self, flyer, optprops):
""" Initialise the abstract base class and store the states that will
be flown.
Args:
flyer (ZeemanFlyer) : Reference to an initialised simulation.
optprops (dict) : Dictionary of optimiser parameters.
"""
self.flyer = flyer
self.optprops = optprops
self.states = optprops['optstates']
self.offtimes = None
self.ontimes = None
@abc.abstractmethod
def createGene(self):
""" Generates a suitable initial guess for a gene, which is used as the
starting point for a CMA-ES optimisation. Usually best to load
`ontimes` and `durations` from the `flyer` passed on creation, and
convert these to a gene.
Returns:
gene (list): A gene sequence of parameters to optimise.
"""
@abc.abstractmethod
def geneBounds(self):
""" Return a list of the upper and lower bounds following the CMA
options format. These are usually stored in the `optprops` dictionary.
Returns:
bound (list): List of [[upper, ...], [lower, ...]] bounds for the
gene parameter set.
"""
@abc.abstractmethod
def flyGene(self, gene):
""" Convert gene and store as `self.ontimes` and `self.offtimes`, then
call `self.fly`. Return the tuple `(pos, vel)`.
Args:
gene (list): Gene to convert and simulate.
Returns:
(tuple): tuple containing:
* pos (numpy.array): 3D position array.
* vel (numpy.array): 3D velocity array.
"""
@abc.abstractmethod
def saveGene(self, path):
""" Convert the gene to the arrays of ontimes and durations suitable
for use in config.info and save to a file.
Args:
path (string): Path to save file (file name is up to implementation).
"""
def fly(self):
""" Method implemented in the base class that performs the necessary
conversion and C-alignment of the `ontimes` and `durations` arrays and
passes them to the :class:`ZeemanFlyer`. This should be called from the
implementation of `flyGene`.
Returns:
(tuple): tuple containing:
* pos (numpy.array): 3D position array.
* vel (numpy.array): 3D velocity array.
"""
if self.offtimes is None or self.ontimes is None:
raise RuntimeError('offtimes and ontimes not set')
c_double_p = ctypes.POINTER(ctypes.c_double) # pointer type
self.flyer.prop.overwriteCoils(
self.ontimes.ctypes.data_as(c_double_p),
self.offtimes.ctypes.data_as(c_double_p))
self.flyer.offimes = self.offtimes
self.flyer.ontimes = self.ontimes
self.flyer.preparePropagation()
pos = np.zeros((0, 3))
vel = np.zeros((0, 3))
for i in self.states:
new_pos, new_vel, _ = self.flyer.propagate(i)
pos = np.concatenate((pos, new_pos))
vel = np.concatenate((vel, new_vel))
return (pos, vel)
def saveGene(self, path):
""" Save the ontimes and offtimes of the last-run gene to a config file
named optimised.info. Only the `[PROPAGATION]` section is created,
containing `ontimes` and `durations`.
Args:
path (string): Path in which to save.
"""
# Format a numpy array as a string, stripping extra spaces, and neatly
# comma delimiting the numbers followed by a space:
# np.array([1.0, 2.0, 3.0])
def fm(a):
return 'np.' + ''.join(repr(a).split()).replace(',', ', ')
config = ConfigParser.ConfigParser(allow_no_value = True)
config.optionxform=str
config.add_section('PROPAGATION')
config.set('PROPAGATION', '; Optimised using ' + self.__class__.__name__)
config.set('PROPAGATION', 'ontimes', fm(self.ontimes))
config.set('PROPAGATION', 'durations', fm(self.offtimes-self.ontimes))
with open(path, 'wb') as f:
config.write(f)
class OffTimeGene(GeneFlyer):
""" Gene that stores the off times for each coil. On times are calculated
assuming a 6 us overlap.
Other Parameters:
Options taken from the `config.info` file:
* maxofftime -- The upper bound for each offtime.
* minofftime -- The lower bound for each offtime.
"""
def __init__(self, flyer, optprops):
super(OffTimeGene, self).__init__(flyer, optprops)
self.log = logging.getLogger(__name__)
self.log.info('Using an OffTimeGene')
def createGene(self):
""" Just return the offtimes loaded from the config file by the `flyer`
object.
"""
return self.flyer.offtimes[:]
def geneBounds(self):
""" Upper and lower bounds taken from config.info.
"""
try:
maxOffTime = self.optprops['maxofftime']
minOffTime = self.optprops['minofftime']
except KeyError:
log.critical('maxofftime or minofftime missing from config.info.')
raise RuntimeError('Missing config values')
return [12*[minOffTime], 12*[maxOffTime]]
def flyGene(self, gene):
""" Offtimes are given by the gene, ontimes are taken as 6 us before
each subsequent `ontime`. A fixed 30 us duration sets the ontime of the
first coil.
"""
self.offtimes = np.require(gene[:12].copy(),
requirements=['c', 'a', 'o', 'w'])
self.ontimes = np.zeros((12,))
self.ontimes[1:] = self.offtimes[:11] - 6
self.ontimes[0] = self.offtimes[0] - 30
return self.fly()
def saveGene(self, gene, path):
self.offtimes = np.require(gene[:12].copy(),
requirements=['c', 'a', 'o', 'w'])
self.ontimes = np.zeros((12,))
self.ontimes[1:] = self.offtimes[:11] - 6
self.ontimes[0] = self.offtimes[0] - 30
print np.transpose((ontimes, offtimes, offtimes-ontimes))
super(OffTimeGene, self).saveGene(path)
class DurationGene(GeneFlyer):
""" This gene stores the duration of each coil pulse. Each coil pulse is
taken to overlap the previous one by 6 us. The ontime for the first coil is
stored from the input file and used to calculate all other switching times
using the duration in the gene and a standard 6 us overlap.
The actual ontime of the first coil is then set to 30 us before its offtime
to ensure this first pulse is long enough.
Note:
The arbitrary ontime of coil 1 used for the duration gene may result in
a negative duration for coil 1. This is not necessarily a problem, as
the duration is fixed at 30 us and a correct sequence will be produced.
Other Parameters:
Options taken from `config.info`:
* maxduration : The maximum pulse duration in us.
* minduration : The minimum pulse duration in us.
"""
def __init__(self, flyer, optprops):
super(DurationGene, self).__init__(flyer, optprops)
self.log = logging.getLogger(__name__)
self.log.info('Using a DurationGene')
def createGene(self):
""" Store a zero-time as the first coil ontime, as given in the config
file. The first coil duration is then relative to this. The duration of
other coils is taken from the config file.
"""
ontimes = self.flyer.ontimes
offtimes = self.flyer.offtimes
self.t0 = ontimes[0]
return offtimes-ontimes
def geneBounds(self):
""" Upper and lower bounds for the gene are taken from parameters
`maxduration' and `minduration` in config.info.
"""
try:
maxDuration = self.optprops['maxduration']
minDuration = self.optprops['minduration']
except KeyError:
log.critical('maxduration or minduration missing from config.info.')
raise RuntimeError('Missing config values')
return [12*[minDuration], 12*[maxDuration]]
def flyGene(self, gene):
""" Convert the gene of durations to a set of `ontimes` and `offtimes`
and fly the simulation. The switching times are calculated by working
forward from the initial time `t0` stored from initial sequence in the
input file. This time is arbitrary, however, and only used to as a
starting point for the relative times. The actual ontime for coil 1 is
set to 30 us.
"""
self._convertGene(gene)
return self.fly()
def saveGene(self, gene, path):
self._convertGene(gene)
durations = self.offtimes - self.ontimes
print np.transpose((self.ontimes, self.offtimes, durations))
super(DurationGene, self).saveGene(path)
def _convertGene(self, gene):
""" Convert the gene to ontimes and offtimes.
"""
durations = np.require(gene[:12].copy(),
requirements=['c', 'a', 'o', 'w'])
self.ontimes = np.zeros((12,))
self.offtimes = np.zeros((12,))
# set the first coil on time to the stored value from config.
self.ontimes[0] = self.t0 + durations[0] - 30
self.offtimes[0] = self.t0 + durations[0]
# The next coil ontime is 6 us before the previous coil is turned off.
for i in range(1, len(durations)):
self.ontimes[i] = self.offtimes[i-1] - 6
self.offtimes[i] = self.ontimes[i] + durations[i]