1
2
3 """
4 Tools for making profiles of any kind.
5
6 Author: R. Lombaert
7
8 """
9
10 import sys, collections
11 import numpy as np
12 from scipy.interpolate import interp1d, interp2d, UnivariateSpline, BivariateSpline
13 from scipy.interpolate import InterpolatedUnivariateSpline as spline1d
14 from scipy.interpolate import RectBivariateSpline as spline2d
15 import os
16
17 from cc.tools.numerical import Operators as op
18 from cc.tools.io import DataIO
19 from cc.data import Data
20
21
22
24
25 '''
26 Create a 1-step fractional profile for water.
27
28 The original water abundance profile is taken from the output of the
29 original model without fractional abundances.
30
31 These fraction profiles can be used for CHANGE_ABUNDANCE_FRACTION in mline
32
33 @param model_id: The model id of the original cooling model
34 @type model_id: string
35 @param path_gastronoom: The model subfolder in ~/GASTRoNOoM/
36 @type path_gastronoom: string
37 @param fraction: the fraction used
38 @type fraction: float
39 @param rfrac: the radius at the step to the fractional abundance [cm]
40 @type rfrac: float
41
42 '''
43
44 rfrac = float(rfrac)
45 fraction = float(fraction)
46 filename = os.path.join(cc.path.gastronoom,path_gastronoom,'models',\
47 model_id,'coolfgr_all%s.dat'%model_id)
48 rad = Gastronoom.getGastronoomOutput(filename=filename,keyword='RADIUS',\
49 return_array=1)
50 fraction_profile = np.ones(len(rad))
51 step_index = np.argmin(abs(rad-rfrac))
52 fraction_profile[step_index:] = fraction
53 output_filename = os.path.join(cc.path.gastronoom,path_gastronoom,\
54 'profiles',\
55 'water_fractions_%s_%.2f_r%.3e.dat'\
56 %(model_id,fraction,rfrac))
57 DataIO.writeCols(output_filename,[rad,fraction_profile])
58
59
60
63
64 '''
65 Read an arbitrary file and return an interpolation object for that file.
66
67 Extra arguments are passed to the chosen read_func. This must include a
68 filename. Order of args/kwargs follows that of the requested function.
69
70 @keyword x: The x grid requested for the interpolation. Note that this is a
71 dummy variable to allow Profiler to work with this function. x
72 can however be used for the interpolation if xcol is None, but
73 that requires x to have the same dimensions as the y variable.
74
75 (default: None)
76 @type x: array
77 @keyword read_func: The function used to read the file. Default is
78 straightforward function for reading columns.
79 Alternatives include getInputData, getKeyData, etc.
80 Can be given as a string, in which case the function
81 must be defined in DataIO.
82
83 (default: DataIO.readCols)
84 @type read_func: function/str
85 @keyword xcol: The column index of the X independent variable
86
87 (default: 0)
88 @type xcol: int
89 @keyword ycol: The column index of the Y dependent variable
90
91 (default: 1)
92 @type ycol: int
93 @keyword itype: The type of interpolator used. Either 'linear' or 'spline'.
94
95 (default: 'spline')
96 @type itype: str
97 @keyword ikwargs: Extra keyword arguments for the interpolation. Default
98 extrapolates by returning boundary values, and
99 interpolates with spline of order 3.
100
101 (default: {'ext':3,'k':3})
102 @type ikwargs: dict
103
104 @return: The interpolation object for the file.
105 @rtype: interpolation object
106
107 '''
108
109
110 itype = itype.lower()
111 if itype == 'linear':
112 interp = interp1d
113 else:
114 interp = spline1d
115
116
117 if isinstance(read_func,str):
118 read_func = getattr(DataIO,read_func)
119
120
121 data = read_func(*args,**kwargs)
122 if not xcol is None: x = data[xcol]
123 y = data[ycol]
124
125
126 return (x,y,interp(x=x,y=y,**ikwargs))
127
128
129
130 -def step(x,ylow,yhigh,xstep):
131
132 '''
133 Step function. At x <= xstep: ylow, at x > xstep: yhigh.
134
135 @param x: x grid (can be array or float)
136 @type x: array/float
137 @param ylow: y value for x < xstep
138 @type ylow: float
139 @param yhigh: y value for x > xstep
140 @type yhigh: float
141 @param xstep: The boundary value between high and low end
142 @type xstep: float
143
144 '''
145
146 return np.piecewise(x,(x>xstep,x<=xstep),(yhigh,ylow))
147
148
149
151
152 '''
153 Define a 2D array where the variable is constant with respect to one of the
154 two axes.
155
156 The function can be passed any arbitrary args and kwargs.
157
158 @param x: The primary coordinate on axis 0
159 @type x: array
160
161 @param y: The secondary coordinate on axis 1.
162 @type y: array
163 @param cfunc: The function for the variable axis. Can be an interpolator.
164 @type cfunc: function/interpolator
165
166 @keyword axis: The axis that is constant. x is 0, y is 1.
167
168 (default: 1)
169 @type axis: int
170
171 @keyword args: args to be passed to function. Should be empty in case of
172 interpolator.
173
174 (default: ())
175 @type args: dict
176 @keyword kwargs: kwargs to be passed to function. Should be empty in case of
177 interpolator.
178
179 (default: {})
180 @type kwargs: dict
181
182 @return: The 2d profile (x.size,y.size)
183 @rtype: array
184
185 '''
186
187
188
189 fvar = cfunc(y if axis == 0 else x,*args,**kwargs)
190
191
192 fcst = np.ones_like(x if axis == 0 else y)
193 z = np.outer(fcst if axis == 0 else fvar, fvar if axis == 0 else fcst)
194
195 return z
196
197
198
200
201 '''
202 Define a function for a constant profile.
203
204 @param x: The radial points
205 @type x: array
206
207 @keyword c: The constant value. If None, it is taken from args/kwargs. This
208 allows arbitrary naming of the constant. Except of course either
209 x or y. A constant must always be given!
210
211 (default: None)
212 @type c: float
213
214 @keyword args: In case the constant is defined under a different name
215
216 (default: ())
217 @type args: dict
218 @keyword kwargs: In case the constant is defined under a different name
219
220 (default: {})
221 @type kwargs: dict
222
223 @return: The constant profile, either 1d (x.size) or 2d (x.size,y.size)
224 @rtype: array
225
226 '''
227
228 if c is None:
229 if len(args) + len(kwargs) > 1:
230 raise TypeError("constant() got unexpected arguments")
231 elif len(args) + len(kwargs) == 0:
232 raise TypeError("constant() got no arguments")
233 c = (args+tuple(kwargs.values()))[0]
234
235 return np.zeros_like(x)+c
236
237
238
239 -def zero(x,*args,**kwargs):
240
241 '''
242 Define a function for a zero profile.
243
244 @param x: The coordinate points
245 @type x: array
246
247 @keyword args: To catch any extra keywords. They are not used.
248
249 (default: ())
250 @type args: dict
251 @keyword kwargs: To catch any extra keywords. They are not used.
252
253 (default: {})
254 @type kwargs: dict
255
256 @return: The constant profile, either 1d (x.size) or 2d (x.size,y.size)
257 @rtype: array
258
259 '''
260
261 return np.zeros_like(x)
262
263
264
265 -def zero2D(x,y=None,*args,**kwargs):
266
267 '''
268 Define a function for a zero profile.
269
270 @param x: The coordinate points
271 @type x: array
272 @keyword y: The secondary points. If None, a 1d array is returned. If not
273 None a 2d array is returned.
274
275 (default: None)
276 @type y: array
277
278 @keyword args: To catch any extra keywords. They are not used.
279
280 (default: ())
281 @type args: dict
282 @keyword kwargs: To catch any extra keywords. They are not used.
283
284 (default: {})
285 @type kwargs: dict
286
287 @return: The constant profile, either 1d (x.size) or 2d (x.size,y.size)
288 @rtype: array
289
290 '''
291
292 if not y is None: x = np.outer(x,y)
293 print x.shape
294
295 return np.zeros_like(x)
296
297
298
300
301 '''
302 An interface for creating profiles, and allowing to evaluate them and
303 calculate the central difference with respect to a coordinate grid.
304
305 '''
306
307
310
311 '''
312 Create an instance of the Profiler() class. Requires a coordinate grid
313 and a function object for the profile. A function for the derivative is
314 optional. The functions can also be given as an interpolation object.
315
316 The optional args and kwargs give the additional arguments for the
317 two function, which are ignored in case func is an interpolation object.
318
319 The default coordinate grid is evaluated for both the function and the
320 derivative. They are saved in self.y and self.dydx. Alternatively, new
321 evaluations can be attained through eval and diff.
322
323 Note that if func is an interpolator object, the original input x and y
324 grids can be passed as additional keywords xin and yin, which would then
325 be arrays. Otherwise, the x and the interpolator(x) are set as xin and
326 yin. xin and yin are ignored if func is a function, even if it returns
327 an interpolator (in which case the original grids are known)
328
329 @param x: The default coordinate points, minimum three points. In the
330 case of an interpolation function, this is the default grid
331 returned by the instance. The original x/y of the
332 interpolated profile are saved as xori/yori in the object.
333 @type x: array
334
335 @keyword func: The function that describes the profile with respect to
336 x. Can be given as an interp1d object. Default is a read
337 function that interpolates data and returns the
338 interpolator object. If interpolation object, x and
339 eval(x) are assumed to be the original grids, unless
340 xin and yin are given as keywords with arrays as values
341 for the original grids.
342
343 (default: interp_file)
344 @type func: function/interp1d object
345 @keyword dfunc: Function that describes the derivative of the profile
346 with respect to x. Can be given as an interpolation
347 object. If None, a generic central difference is taken &
348 interpolated with a spline of which the order can be
349 chosen.
350
351 (default: None)
352 @type dfunc: function/interpolation object
353 @keyword order: Order of the spline interpolation of the derivative.
354 Default is cubic. Not used for the interpolation if func
355 returns an interpolation object. Use read_order in that
356 case.
357
358 (default: 3)
359 @type order: int
360
361 @keyword args: Additional parameters passed to the functions when eval
362 or diff are called.
363
364 (default: [])
365 @type args: tuple
366 @keyword kwargs: Additional keywords passed to the functions when eval
367 or diff are called.
368
369 (default: {})
370 @type kwargs: dict
371
372 '''
373
374
375
376 if len(x) < 3:
377 raise ValueError('Coordinate grid must have more than 2 elements.')
378
379
380
381
382
383
384 if isinstance(func,str):
385 try:
386
387 kwargs['c'] = float(func)
388 func = constant
389
390 except ValueError:
391
392 if hasattr(sys.modules[self.__module__],func):
393 func = getattr(sys.modules[self.__module__],func)
394
395
396
397 else:
398
399 func = DataIO.read(func=func,module=sys.modules[__name__],\
400 return_func=1)
401
402
403
404
405
406 self.args = args
407 self.kwargs = kwargs
408 self._args = self.args
409 self._dargs = self.args
410 self._dkwargs = self.kwargs
411 self._kwargs = self.kwargs
412 self.order = order
413
414
415
416 xin = self.kwargs.pop('xin',None)
417 yin = self.kwargs.pop('yin',None)
418
419
420 self.interp_func = 0
421 self.interp_dfunc = 0
422
423
424 self.xin = np.empty(0)
425 self.yin = np.empty(0)
426
427
428
429 self.x = None
430 if not (isinstance(func,interp1d) or isinstance(func,UnivariateSpline)):
431
432
433 y = func(x,*args,**kwargs)
434
435
436
437
438 if isinstance(y,tuple):
439 self.func = y[2]
440 self.xin = y[0]
441 self.yin = y[1]
442 self._args = []
443 self._kwargs = {}
444 self.interp_func = 1
445 self.y = self.func(x)
446 else:
447 self.func = func
448 self.y = y
449
450
451
452 else:
453 self.func = func
454 self._args = []
455 self._kwargs = {}
456 self.interp_func = 1
457 self.y = self.func(x)
458 self.yin = yin if not yin is None else self.y
459 self.xin = xin if not xin is None else x
460
461
462 if not dfunc is None:
463 self.dfunc = dfunc
464 elif self.func == constant:
465 self.dfunc = zero
466 else:
467
468
469
470
471
472
473
474
475
476
477 self.dfunc = spline1d(x=x,y=op.diff_central(self.y,x),\
478 k=self.order)
479
480 if (isinstance(self.dfunc,interp1d) \
481 or isinstance(self.dfunc,UnivariateSpline)):
482 self._dargs = []
483 self._dkwargs = {}
484 self.interp_dfunc = 1
485
486
487 self.dydx = self.dfunc(x,*self._dargs,**self._dkwargs)
488
489
490 self.x = Data.arrayify(x)
491
492
493
495
496 '''
497 Evaluate the profile function at a coordinate point.
498
499 x can be any value or array. If func is an interpolation object, it is
500 in principle limited by the x-range of the interpolator. It is advised
501 not to extend much beyond the given x-range. No warning is printed here.
502 Use eval() if that functionality is preferred.
503
504 The default y-grid is returned if x is None.
505
506 Passes the call to eval.
507
508 @keyword x: The primary coordinate point(s). If None, the default
509 coordinate grid is used.
510
511 (default: None)
512 @type x: array/float
513 @keyword warn: Warn when extrapolation occurs.
514
515 (default: 1)
516 @type warn: bool
517
518 @return: The profile evaluated at x
519 @rtype: array/float
520
521 '''
522
523 return self.eval(x,warn)
524
525
526
527 - def eval(self,x=None,warn=1):
528
529 '''
530 Evaluate the profile function at a coordinate point.
531
532 x can be any value or array. If func is an interpolation object, it is
533 in principle limited by the x-range of the interpolator. It is advised
534 not to extend much beyond the given x-range.
535
536 The default y-grid is returned if x is None.a
537
538 @keyword x: The coordinate point(s). If None, the default
539 coordinate grid is used.
540
541 (default: None)
542 @type x: array/float
543 @keyword warn: Warn when extrapolation occurs.
544
545 (default: 1)
546 @type warn: bool
547
548 @return: The profile evaluated at x
549 @rtype: array/float
550
551 '''
552
553
554 if self.interp_func and warn:
555
556 xarr = self.x if x is None else x
557
558
559 if np.any((xarr>self.xin[-1])|(xarr<self.xin[0])):
560 m = 'Warning! There were values outside of interpolation '+\
561 'range in module {}.'.format(sys.modules[self.__module__])
562 vals = Data.arrayify(xarr)
563 sel = vals[(vals>self.xin[-1])|(vals<self.xin[0])]
564 m += '\n {}'.format(str(sel))
565 print(m)
566
567
568 if x is None:
569 return self.y
570
571
572 return self.func(x,*self._args,**self._kwargs)
573
574
575
576 - def diff(self,x=None,warn=1):
577
578 '''
579 Evaluate the derivative of the profile function at a coordinate point.
580
581 x can be any value or array. If func is an interpolation object, it is
582 in principle limited by the x-range of the interpolator. It is advised
583 not to extend much beyond the given x-range.
584
585 @keyword x: The coordinate point(s). If None, the default
586 coordinate grid is used.
587
588 (default: None)
589 @type x: array/float
590 @keyword warn: Warn when extrapolation occurs.
591
592 (default: 1)
593 @type warn: bool
594
595 @return: The derivative evaluated at x
596 @rtype: array/float
597
598 '''
599
600
601
602 if self.interp_func and self.interp_dfunc and warn:
603
604 xarr = self.x if x is None else x
605
606
607 if np.any((xarr>self.xin[-1])|(xarr<self.xin[0])):
608 m = 'Warning! There were values outside of interpolation '+\
609 'range in module {}.'.format(sys.modules[self.__module__])
610 vals = Data.arrayify(xarr)
611 sel = vals[(vals>self.xin[-1])|(vals<self.xin[0])]
612 m += '\n {}'.format(str(sel))
613 print(m)
614
615
616 if x is None:
617 return self.dydx
618
619
620 return self.dfunc(x,*self._dargs,**self._dkwargs)
621
622
623
625
626 '''
627 An interface for creating 2D profiles, and allowing to evaluate them and
628 calculate the central difference with respect to a 2D coordinate grid.
629
630 '''
631
632
633 - def __init__(self,x,y,func,*args,**kwargs):
634
635 '''
636 Create an instance of the Profiler2D() class. Requires 2 coordinate
637 arrays and a function object for profile.
638
639 The function can also be given as an interpolation object.
640
641 The optional args and kwargs give the additional arguments for the
642 function, which are ignored in case func is an interpolation object.
643
644 The default coordinate grids are both evaluated for the function.
645 They are saved in self.z, as an array of dimensions (x.size,y.size).
646 Alternatively, new evaluations can be attained through eval and diff.
647
648 @param x: The default coordinates of the primary independent variable.
649 Minimum three points.
650 @type x: array
651 @param y: The default coordinates of the secondary independent variable.
652 Minimum three points.
653 @type y: array
654 @param func: The function that describes the profile with respect to x
655 and y. Can be given as a 2D interpolation object.
656 @type func: function/interpolation object
657
658 @keyword args: Additional parameters passed to the functions when eval
659 or diff are called.
660
661 (default: [])
662 @type args: tuple
663 @keyword kwargs: Additional keywords passed to the functions when eval
664 or diff are called.
665
666 (default: {})
667 @type kwargs: dict
668
669 '''
670
671
672
673
674
675 if isinstance(func,str):
676 try:
677
678 func = getattr(sys.modules[self.__module__],func)
679 except AttributeError:
680
681
682 func = DataIO.read(sys.modules[__name__],return_func=1)
683
684
685
686
687
688 self.args = args
689 self.kwargs = kwargs
690 self._args = args
691 self._kwargs = kwargs
692 self.func = func
693
694 if isinstance(self.func,interp2d) \
695 or isinstance(self.func,BivariateSpline):
696 self.interp_func = 1
697 self._args = []
698 self._kwargs = {}
699 else:
700 self.interp_func = 0
701
702
703
704 self.x = None
705 self.y = None
706 self.z = self.func(x,y,*self._args,**self._kwargs)
707
708
709 self.x = Data.arrayify(x)
710 self.y = Data.arrayify(y)
711
712
713
714 - def __call__(self,x=None,y=None,warn=1):
715
716 '''
717 Evaluate the profile function at a coordinate point.
718
719 x/y can be any value or array. If func is an interpolation object, it is
720 in principle limited by the x/y-range of the interpolator. It is advised
721 not to extend much beyond the given x/y-range.
722
723 If one of the two variables is None, it is replaced by the default grid.
724
725 Passes on the call to self.eval().
726
727 @keyword x: The primary coordinate point(s). If None, the default
728 coordinate grid is used.
729
730 (default: None)
731 @type x: array/float
732 @keyword y: The secondary coordinate point(s). If None, the default
733 coordinate grid is used.
734
735 (default: None)
736 @type y: array/float
737 @keyword warn: Warn when extrapolation occurs.
738
739 (default: 1)
740 @type warn: bool
741
742 @return: The profile evaluated at x and y
743 @rtype: array/float
744
745 '''
746
747 return self.eval(x=x,y=y,warn=warn)
748
749
750
751 - def eval(self,x=None,y=None,warn=1):
752
753 '''
754 Evaluate the profile function at a coordinate point.
755
756 x/y can be any value or array. If func is an interpolation object, it is
757 in principle limited by the x/y-range of the interpolator. It is advised
758 not to extend much beyond the given x/y-range.
759
760 If one of the two variables is None, it is replaced by the default grid.
761
762 @keyword x: The primary coordinate point(s). If None, the default
763 coordinate grid is used.
764
765 (default: None)
766 @type x: array/float
767 @keyword y: The secondary coordinate point(s). If None, the default
768 coordinate grid is used.
769
770 (default: None)
771 @type y: array/float
772 @keyword warn: Warn when extrapolation occurs.
773
774 (default: 1)
775 @type warn: bool
776
777 @return: The profile evaluated at x and y
778 @rtype: array/float
779
780 '''
781
782
783 xarr = self.x if x is None else x
784 yarr = self.y if y is None else y
785
786
787 if self.interp_func and warn:
788
789 if np.any((xarr>self.x[-1])|(xarr<self.x[0])) \
790 or np.any((yarr>self.y[-1])|(yarr<self.y[0])):
791 m = 'Warning! There were values outside of 2D interpolation '+\
792 'range in module {}.'.format(sys.modules[self.__module__])
793 xvals, yvals = Data.arrayify(xarr), Data.arrayify(yarr)
794 xsel = xvals[(xvals>self.x[-1])|(xvals<self.x[0])]
795 ysel = yvals[(yvals>self.y[-1])|(yvals<self.y[0])]
796 m += '\nx: {}, \ny: {}'.format(str(xsel),str(ysel))
797 print(m)
798
799
800 if x is None and y is None:
801 return self.z
802
803
804 return self.func(xarr,yarr,*self._args,**self._kwargs)
805