@@ -2637,3 +2637,259 @@ def __exit__(self, exc_type, exc_value, traceback):
2637
2637
os .rmdir (path )
2638
2638
except OSError :
2639
2639
pass
2640
+
2641
+
2642
+ class _FuncInfo (object ):
2643
+ """
2644
+ Class used to store a function.
2645
+
2646
+ """
2647
+
2648
+ def __init__ (self , function , inverse , bounded_0_1 = True , check_params = None ):
2649
+ """
2650
+ Parameters
2651
+ ----------
2652
+
2653
+ function : callable
2654
+ A callable implementing the function receiving the variable as
2655
+ first argument and any additional parameters in a list as second
2656
+ argument.
2657
+ inverse : callable
2658
+ A callable implementing the inverse function receiving the variable
2659
+ as first argument and any additional parameters in a list as
2660
+ second argument. It must satisfy 'inverse(function(x, p), p) == x'.
2661
+ bounded_0_1: bool or callable
2662
+ A boolean indicating whether the function is bounded in the [0,1]
2663
+ interval, or a callable taking a list of values for the additional
2664
+ parameters, and returning a boolean indicating whether the function
2665
+ is bounded in the [0,1] interval for that combination of
2666
+ parameters. Default True.
2667
+ check_params: callable or None
2668
+ A callable taking a list of values for the additional parameters
2669
+ and returning a boolean indicating whether that combination of
2670
+ parameters is valid. It is only required if the function has
2671
+ additional parameters and some of them are restricted.
2672
+ Default None.
2673
+
2674
+ """
2675
+
2676
+ self .function = function
2677
+ self .inverse = inverse
2678
+
2679
+ if callable (bounded_0_1 ):
2680
+ self ._bounded_0_1 = bounded_0_1
2681
+ else :
2682
+ self ._bounded_0_1 = lambda x : bounded_0_1
2683
+
2684
+ if check_params is None :
2685
+ self ._check_params = lambda x : True
2686
+ elif callable (check_params ):
2687
+ self ._check_params = check_params
2688
+ else :
2689
+ raise ValueError ("Invalid 'check_params' argument." )
2690
+
2691
+ def is_bounded_0_1 (self , params = None ):
2692
+ """
2693
+ Returns a boolean indicating if the function is bounded in the [0,1]
2694
+ interval for a particular set of additional parameters.
2695
+
2696
+ Parameters
2697
+ ----------
2698
+
2699
+ params : list
2700
+ The list of additional parameters. Default None.
2701
+
2702
+ Returns
2703
+ -------
2704
+
2705
+ out : bool
2706
+ True if the function is bounded in the [0,1] interval for
2707
+ parameters 'params'. Otherwise False.
2708
+
2709
+ """
2710
+
2711
+ return self ._bounded_0_1 (params )
2712
+
2713
+ def check_params (self , params = None ):
2714
+ """
2715
+ Returns a boolean indicating if the set of additional parameters is
2716
+ valid.
2717
+
2718
+ Parameters
2719
+ ----------
2720
+
2721
+ params : list
2722
+ The list of additional parameters. Default None.
2723
+
2724
+ Returns
2725
+ -------
2726
+
2727
+ out : bool
2728
+ True if 'params' is a valid set of additional parameters for the
2729
+ function. Otherwise False.
2730
+
2731
+ """
2732
+
2733
+ return self ._check_params (params )
2734
+
2735
+
2736
+ class _StringFuncParser (object ):
2737
+ """
2738
+ A class used to convert predefined strings into
2739
+ _FuncInfo objects, or to directly obtain _FuncInfo
2740
+ properties.
2741
+
2742
+ """
2743
+
2744
+ _funcs = {}
2745
+ _funcs ['linear' ] = _FuncInfo (lambda x : x ,
2746
+ lambda x : x ,
2747
+ True )
2748
+ _funcs ['quadratic' ] = _FuncInfo (np .square ,
2749
+ np .sqrt ,
2750
+ True )
2751
+ _funcs ['cubic' ] = _FuncInfo (lambda x : x ** 3 ,
2752
+ lambda x : x ** (1. / 3 ),
2753
+ True )
2754
+ _funcs ['sqrt' ] = _FuncInfo (np .sqrt ,
2755
+ np .square ,
2756
+ True )
2757
+ _funcs ['cbrt' ] = _FuncInfo (lambda x : x ** (1. / 3 ),
2758
+ lambda x : x ** 3 ,
2759
+ True )
2760
+ _funcs ['log10' ] = _FuncInfo (np .log10 ,
2761
+ lambda x : (10 ** (x )),
2762
+ False )
2763
+ _funcs ['log' ] = _FuncInfo (np .log ,
2764
+ np .exp ,
2765
+ False )
2766
+ _funcs ['log2' ] = _FuncInfo (np .log2 ,
2767
+ lambda x : (2 ** x ),
2768
+ False )
2769
+ _funcs ['x**{p}' ] = _FuncInfo (lambda x , p : x ** p [0 ],
2770
+ lambda x , p : x ** (1. / p [0 ]),
2771
+ True )
2772
+ _funcs ['root{p}(x)' ] = _FuncInfo (lambda x , p : x ** (1. / p [0 ]),
2773
+ lambda x , p : x ** p ,
2774
+ True )
2775
+ _funcs ['log{p}(x)' ] = _FuncInfo (lambda x , p : (np .log (x ) /
2776
+ np .log (p [0 ])),
2777
+ lambda x , p : p [0 ]** (x ),
2778
+ False ,
2779
+ lambda p : p [0 ] > 0 )
2780
+ _funcs ['log10(x+{p})' ] = _FuncInfo (lambda x , p : np .log10 (x + p [0 ]),
2781
+ lambda x , p : 10 ** x - p [0 ],
2782
+ lambda p : p [0 ] > 0 )
2783
+ _funcs ['log(x+{p})' ] = _FuncInfo (lambda x , p : np .log (x + p [0 ]),
2784
+ lambda x , p : np .exp (x ) - p [0 ],
2785
+ lambda p : p [0 ] > 0 )
2786
+ _funcs ['log{p}(x+{p})' ] = _FuncInfo (lambda x , p : (np .log (x + p [1 ]) /
2787
+ np .log (p [0 ])),
2788
+ lambda x , p : p [0 ]** (x ) - p [1 ],
2789
+ lambda p : p [1 ] > 0 ,
2790
+ lambda p : p [0 ] > 0 )
2791
+
2792
+ def __init__ (self , str_func ):
2793
+ """
2794
+ Parameters
2795
+ ----------
2796
+ str_func : string
2797
+ String to be parsed.
2798
+
2799
+ """
2800
+
2801
+ if not isinstance (str_func , six .string_types ):
2802
+ raise ValueError ("'%s' must be a string." % str_func )
2803
+ self ._str_func = six .text_type (str_func )
2804
+ self ._key , self ._params = self ._get_key_params ()
2805
+ self ._func = self ._parse_func ()
2806
+
2807
+ def _parse_func (self ):
2808
+ """
2809
+ Parses the parameters to build a new _FuncInfo object,
2810
+ replacing the relevant parameters if necessary in the lambda
2811
+ functions.
2812
+
2813
+ """
2814
+
2815
+ func = self ._funcs [self ._key ]
2816
+
2817
+ if not self ._params :
2818
+ func = _FuncInfo (func .function , func .inverse ,
2819
+ func .is_bounded_0_1 ())
2820
+ else :
2821
+ m = func .function
2822
+ function = (lambda x , m = m : m (x , self ._params ))
2823
+
2824
+ m = func .inverse
2825
+ inverse = (lambda x , m = m : m (x , self ._params ))
2826
+
2827
+ is_bounded_0_1 = func .is_bounded_0_1 (self ._params )
2828
+
2829
+ func = _FuncInfo (function , inverse ,
2830
+ is_bounded_0_1 )
2831
+ return func
2832
+
2833
+ @property
2834
+ def func_info (self ):
2835
+ """
2836
+ Returns the _FuncInfo object.
2837
+
2838
+ """
2839
+ return self ._func
2840
+
2841
+ @property
2842
+ def function (self ):
2843
+ """
2844
+ Returns the callable for the direct function.
2845
+
2846
+ """
2847
+ return self ._func .function
2848
+
2849
+ @property
2850
+ def inverse (self ):
2851
+ """
2852
+ Returns the callable for the inverse function.
2853
+
2854
+ """
2855
+ return self ._func .inverse
2856
+
2857
+ @property
2858
+ def is_bounded_0_1 (self ):
2859
+ """
2860
+ Returns a boolean indicating if the function is bounded
2861
+ in the [0-1 interval].
2862
+
2863
+ """
2864
+ return self ._func .is_bounded_0_1 ()
2865
+
2866
+ def _get_key_params (self ):
2867
+ str_func = self ._str_func
2868
+ # Checking if it comes with parameters
2869
+ regex = '\{(.*?)\}'
2870
+ params = re .findall (regex , str_func )
2871
+
2872
+ for i , param in enumerate (params ):
2873
+ try :
2874
+ params [i ] = float (param )
2875
+ except ValueError :
2876
+ raise ValueError ("Parameter %i is '%s', which is "
2877
+ "not a number." %
2878
+ (i , param ))
2879
+
2880
+ str_func = re .sub (regex , '{p}' , str_func )
2881
+
2882
+ try :
2883
+ func = self ._funcs [str_func ]
2884
+ except (ValueError , KeyError ):
2885
+ raise ValueError ("'%s' is an invalid string. The only strings "
2886
+ "recognized as functions are %s." %
2887
+ (str_func , list (self ._funcs )))
2888
+
2889
+ # Checking that the parameters are valid
2890
+ if not func .check_params (params ):
2891
+ raise ValueError ("%s are invalid values for the parameters "
2892
+ "in %s." %
2893
+ (params , str_func ))
2894
+
2895
+ return str_func , params
0 commit comments