/
linearPotential.py
428 lines (362 loc) · 11.9 KB
/
linearPotential.py
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
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
import copy
import os
import os.path
import pickle
import numpy
from ..util import config, conversion, plot
from ..util.conversion import (
physical_compatible,
physical_conversion,
potential_physical_input,
)
from .Potential import PotentialError, flatten, potential_positional_arg
class linearPotential:
"""Class representing 1D potentials"""
def __init__(self, amp=1.0, ro=None, vo=None):
self._amp = amp
self.dim = 1
self.isRZ = False
self.hasC = False
self.hasC_dxdv = False
self.hasC_dens = False
# Parse ro and vo
if ro is None:
self._ro = config.__config__.getfloat("normalization", "ro")
self._roSet = False
else:
ro = conversion.parse_length_kpc(ro)
self._ro = ro
self._roSet = True
if vo is None:
self._vo = config.__config__.getfloat("normalization", "vo")
self._voSet = False
else:
vo = conversion.parse_velocity_kms(vo)
self._vo = vo
self._voSet = True
return None
def __mul__(self, b):
"""
Multiply a linearPotential's amplitude by a number
Parameters
----------
b : int or float
Number to multiply the amplitude of the linearPotential instance with.
Returns
-------
linearPotential instance
New instance with amplitude = (old amplitude) x b.
Notes
-----
- 2019-01-27 - Written - Bovy (UofT)
"""
if not isinstance(b, (int, float)):
raise TypeError(
"Can only multiply a planarPotential instance with a number"
)
out = copy.deepcopy(self)
out._amp *= b
return out
# Similar functions
__rmul__ = __mul__
def __div__(self, b):
return self.__mul__(1.0 / b)
__truediv__ = __div__
def __add__(self, b):
"""
Add linearPotential instances together to create a multi-component potential (e.g., pot= pot1+pot2+pot3)
Parameters
----------
b : linearPotential instance or a list thereof
Returns
-------
list of linearPotential instances
Represents the combined potential
Notes
-----
- 2019-01-27 - Written - Bovy (UofT)
"""
from ..potential import flatten as flatten_pot
if not isinstance(flatten_pot([b])[0], linearPotential):
raise TypeError(
"""Can only combine galpy linearPotential"""
""" objects with """
"""other such objects or lists thereof"""
)
assert physical_compatible(self, b), (
"""Physical unit conversion parameters (ro,vo) are not """
"""compatible between potentials to be combined"""
)
if isinstance(b, list):
return [self] + b
else:
return [self, b]
# Define separately to keep order
def __radd__(self, b):
from ..potential import flatten as flatten_pot
if not isinstance(flatten_pot([b])[0], linearPotential):
raise TypeError(
"""Can only combine galpy linearPotential"""
""" objects with """
"""other such objects or lists thereof"""
)
assert physical_compatible(self, b), (
"""Physical unit conversion parameters (ro,vo) are not """
"""compatible between potentials to be combined"""
)
# If we get here, b has to be a list
return b + [self]
def turn_physical_off(self):
"""
Turn off automatic returning of outputs in physical units.
Returns
-------
None
Notes
-----
- 2016-01-30 - Written - Bovy (UofT)
"""
self._roSet = False
self._voSet = False
return None
def turn_physical_on(self, ro=None, vo=None):
"""
Turn on automatic returning of outputs in physical units.
Parameters
----------
ro : float or Quantity, optional
Reference distance (kpc).
vo : float or Quantity, optional
Reference velocity (km/s).
Returns
-------
None
Notes
-----
- 2016-01-30 - Written - Bovy (UofT)
- 2020-04-22 - Don't turn on a parameter when it is False - Bovy (UofT)
"""
if not ro is False:
self._roSet = True
ro = conversion.parse_length_kpc(ro)
if not ro is None:
self._ro = ro
if not vo is False:
self._voSet = True
vo = conversion.parse_velocity_kms(vo)
if not vo is None:
self._vo = vo
return None
@potential_physical_input
@physical_conversion("energy", pop=True)
def __call__(self, x, t=0.0):
"""
Evaluate the potential.
Parameters
----------
x : float or Quantity
Position.
t : float or Quantity, optional
Time (default: 0.0).
Returns
-------
float or Quantity
Potential at position x and time t.
Notes
-----
- 2010-07-12 - Written - Bovy (NYU)
"""
return self._call_nodecorator(x, t=t)
def _call_nodecorator(self, x, t=0.0):
# Separate, so it can be used during orbit integration
try:
return self._amp * self._evaluate(x, t=t)
except AttributeError: # pragma: no cover
raise PotentialError(
"'_evaluate' function not implemented for this potential"
)
@potential_physical_input
@physical_conversion("force", pop=True)
def force(self, x, t=0.0):
"""
Evaluate the force.
Parameters
----------
x : float or Quantity
Position.
t : float or Quantity, optional
Time (default: 0.0).
Returns
-------
float or Quantity
Force at position x and time t.
Notes
-----
- 2010-07-12 - Written - Bovy (NYU)
"""
return self._force_nodecorator(x, t=t)
def _force_nodecorator(self, x, t=0.0):
# Separate, so it can be used during orbit integration
try:
return self._amp * self._force(x, t=t)
except AttributeError: # pragma: no cover
raise PotentialError("'_force' function not implemented for this potential")
def plot(self, t=0.0, min=-15.0, max=15, ns=21, savefilename=None):
"""
Plot the potential
Parameters
----------
t : float or Quantity, optional
The time at which to evaluate the forces. Default is 0.0.
min : float, optional
Minimum x.
max : float, optional
Maximum x.
ns : int, optional
Grid in x.
savefilename : str, optional
Save to or restore from this savefile (pickle).
Returns
-------
plot to output device
Notes
-----
- 2010-07-13 - Written - Bovy (NYU)
"""
if not savefilename == None and os.path.exists(savefilename):
print("Restoring savefile " + savefilename + " ...")
savefile = open(savefilename, "rb")
potx = pickle.load(savefile)
xs = pickle.load(savefile)
savefile.close()
else:
xs = numpy.linspace(min, max, ns)
potx = numpy.zeros(ns)
for ii in range(ns):
potx[ii] = self._evaluate(xs[ii], t=t)
if not savefilename == None:
print("Writing savefile " + savefilename + " ...")
savefile = open(savefilename, "wb")
pickle.dump(potx, savefile)
pickle.dump(xs, savefile)
savefile.close()
return plot.plot(
xs, potx, xlabel=r"$x/x_0$", ylabel=r"$\Phi(x)$", xrange=[min, max]
)
@potential_positional_arg
@potential_physical_input
@physical_conversion("energy", pop=True)
def evaluatelinearPotentials(Pot, x, t=0.0):
"""
Evaluate the sum of a list of potentials.
Parameters
----------
Pot : list of linearPotential instance(s)
The list of potentials to evaluate.
x : float or Quantity
The position at which to evaluate the potentials.
t : float or Quantity, optional
The time at which to evaluate the potentials. Default is 0.0.
Returns
-------
float or Quantity
The value of the potential at the given position and time.
Notes
-----
- 2010-07-13 - Written - Bovy (NYU)
"""
return _evaluatelinearPotentials(Pot, x, t=t)
def _evaluatelinearPotentials(Pot, x, t=0.0):
"""Raw, undecorated function for internal use"""
if isinstance(Pot, list):
sum = 0.0
for pot in Pot:
sum += pot._call_nodecorator(x, t=t)
return sum
elif isinstance(Pot, linearPotential):
return Pot._call_nodecorator(x, t=t)
else: # pragma: no cover
raise PotentialError(
"Input to 'evaluatelinearPotentials' is neither a linearPotential-instance or a list of such instances"
)
@potential_positional_arg
@potential_physical_input
@physical_conversion("force", pop=True)
def evaluatelinearForces(Pot, x, t=0.0):
"""
Evaluate the forces due to a list of potentials.
Parameters
----------
Pot : list of linearPotential instance(s)
The list of potentials to evaluate.
x : float or Quantity
The position at which to evaluate the forces.
t : float or Quantity, optional
The time at which to evaluate the forces. Default is 0.0.
Returns
-------
float or Quantity
The value of the forces at the given position and time.
Notes
-----
- 2010-07-13 - Written - Bovy (NYU)
"""
return _evaluatelinearForces(Pot, x, t=t)
def _evaluatelinearForces(Pot, x, t=0.0):
"""Raw, undecorated function for internal use"""
if isinstance(Pot, list):
sum = 0.0
for pot in Pot:
sum += pot._force_nodecorator(x, t=t)
return sum
elif isinstance(Pot, linearPotential):
return Pot._force_nodecorator(x, t=t)
else: # pragma: no cover
raise PotentialError(
"Input to 'evaluateForces' is neither a linearPotential-instance or a list of such instances"
)
def plotlinearPotentials(Pot, t=0.0, min=-15.0, max=15, ns=21, savefilename=None):
"""
Plot a combination of potentials
Parameters
----------
Pot : list of linearPotential instance(s)
The list of potentials to evaluate.
t : float or Quantity, optional
The time at which to evaluate the forces. Default is 0.0.
min : float, optional
Minimum x.
max : float, optional
Maximum x.
ns : int, optional
Grid in x.
savefilename : str, optional
Save to or restore from this savefile (pickle).
Returns
-------
plot to output device
Notes
-----
- 2010-07-13 - Written - Bovy (NYU)
"""
Pot = flatten(Pot)
if not savefilename == None and os.path.exists(savefilename):
print("Restoring savefile " + savefilename + " ...")
savefile = open(savefilename, "rb")
potx = pickle.load(savefile)
xs = pickle.load(savefile)
savefile.close()
else:
xs = numpy.linspace(min, max, ns)
potx = numpy.zeros(ns)
for ii in range(ns):
potx[ii] = evaluatelinearPotentials(Pot, xs[ii], t=t)
if not savefilename == None:
print("Writing savefile " + savefilename + " ...")
savefile = open(savefilename, "wb")
pickle.dump(potx, savefile)
pickle.dump(xs, savefile)
savefile.close()
return plot.plot(
xs, potx, xlabel=r"$x/x_0$", ylabel=r"$\Phi(x)$", xrange=[min, max]
)