Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitb73cedc

Browse files
authored
Merge pull request#7464 from alvarosg/string-func-parser
[MRG+2] ENH: _StringFuncParser to get numerical functions callables from strings
2 parents424d3b0 +c143e75 commitb73cedc

File tree

2 files changed

+317
-0
lines changed

2 files changed

+317
-0
lines changed

‎lib/matplotlib/cbook.py

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2637,3 +2637,259 @@ def __exit__(self, exc_type, exc_value, traceback):
26372637
os.rmdir(path)
26382638
exceptOSError:
26392639
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+
ifcallable(bounded_0_1):
2680+
self._bounded_0_1=bounded_0_1
2681+
else:
2682+
self._bounded_0_1=lambdax:bounded_0_1
2683+
2684+
ifcheck_paramsisNone:
2685+
self._check_params=lambdax:True
2686+
elifcallable(check_params):
2687+
self._check_params=check_params
2688+
else:
2689+
raiseValueError("Invalid 'check_params' argument.")
2690+
2691+
defis_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+
returnself._bounded_0_1(params)
2712+
2713+
defcheck_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+
returnself._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(lambdax:x,
2746+
lambdax:x,
2747+
True)
2748+
_funcs['quadratic']=_FuncInfo(np.square,
2749+
np.sqrt,
2750+
True)
2751+
_funcs['cubic']=_FuncInfo(lambdax:x**3,
2752+
lambdax:x**(1./3),
2753+
True)
2754+
_funcs['sqrt']=_FuncInfo(np.sqrt,
2755+
np.square,
2756+
True)
2757+
_funcs['cbrt']=_FuncInfo(lambdax:x**(1./3),
2758+
lambdax:x**3,
2759+
True)
2760+
_funcs['log10']=_FuncInfo(np.log10,
2761+
lambdax: (10**(x)),
2762+
False)
2763+
_funcs['log']=_FuncInfo(np.log,
2764+
np.exp,
2765+
False)
2766+
_funcs['log2']=_FuncInfo(np.log2,
2767+
lambdax: (2**x),
2768+
False)
2769+
_funcs['x**{p}']=_FuncInfo(lambdax,p:x**p[0],
2770+
lambdax,p:x**(1./p[0]),
2771+
True)
2772+
_funcs['root{p}(x)']=_FuncInfo(lambdax,p:x**(1./p[0]),
2773+
lambdax,p:x**p,
2774+
True)
2775+
_funcs['log{p}(x)']=_FuncInfo(lambdax,p: (np.log(x)/
2776+
np.log(p[0])),
2777+
lambdax,p:p[0]**(x),
2778+
False,
2779+
lambdap:p[0]>0)
2780+
_funcs['log10(x+{p})']=_FuncInfo(lambdax,p:np.log10(x+p[0]),
2781+
lambdax,p:10**x-p[0],
2782+
lambdap:p[0]>0)
2783+
_funcs['log(x+{p})']=_FuncInfo(lambdax,p:np.log(x+p[0]),
2784+
lambdax,p:np.exp(x)-p[0],
2785+
lambdap:p[0]>0)
2786+
_funcs['log{p}(x+{p})']=_FuncInfo(lambdax,p: (np.log(x+p[1])/
2787+
np.log(p[0])),
2788+
lambdax,p:p[0]**(x)-p[1],
2789+
lambdap:p[1]>0,
2790+
lambdap: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+
ifnotisinstance(str_func,six.string_types):
2802+
raiseValueError("'%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+
ifnotself._params:
2818+
func=_FuncInfo(func.function,func.inverse,
2819+
func.is_bounded_0_1())
2820+
else:
2821+
m=func.function
2822+
function= (lambdax,m=m:m(x,self._params))
2823+
2824+
m=func.inverse
2825+
inverse= (lambdax,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+
returnfunc
2832+
2833+
@property
2834+
deffunc_info(self):
2835+
"""
2836+
Returns the _FuncInfo object.
2837+
2838+
"""
2839+
returnself._func
2840+
2841+
@property
2842+
deffunction(self):
2843+
"""
2844+
Returns the callable for the direct function.
2845+
2846+
"""
2847+
returnself._func.function
2848+
2849+
@property
2850+
definverse(self):
2851+
"""
2852+
Returns the callable for the inverse function.
2853+
2854+
"""
2855+
returnself._func.inverse
2856+
2857+
@property
2858+
defis_bounded_0_1(self):
2859+
"""
2860+
Returns a boolean indicating if the function is bounded
2861+
in the [0-1 interval].
2862+
2863+
"""
2864+
returnself._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+
fori,paraminenumerate(params):
2873+
try:
2874+
params[i]=float(param)
2875+
exceptValueError:
2876+
raiseValueError("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+
raiseValueError("'%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+
ifnotfunc.check_params(params):
2891+
raiseValueError("%s are invalid values for the parameters "
2892+
"in %s."%
2893+
(params,str_func))
2894+
2895+
returnstr_func,params

‎lib/matplotlib/tests/test_cbook.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,3 +515,64 @@ def test_flatiter():
515515

516516
assert0==next(it)
517517
assert1==next(it)
518+
519+
520+
classTestFuncParser(object):
521+
x_test=np.linspace(0.01,0.5,3)
522+
validstrings= ['linear','quadratic','cubic','sqrt','cbrt',
523+
'log','log10','log2','x**{1.5}','root{2.5}(x)',
524+
'log{2}(x)',
525+
'log(x+{0.5})','log10(x+{0.1})','log{2}(x+{0.1})',
526+
'log{2}(x+{0})']
527+
results= [(lambdax:x),
528+
np.square,
529+
(lambdax:x**3),
530+
np.sqrt,
531+
(lambdax:x**(1./3)),
532+
np.log,
533+
np.log10,
534+
np.log2,
535+
(lambdax:x**1.5),
536+
(lambdax:x**(1/2.5)),
537+
(lambdax:np.log2(x)),
538+
(lambdax:np.log(x+0.5)),
539+
(lambdax:np.log10(x+0.1)),
540+
(lambdax:np.log2(x+0.1)),
541+
(lambdax:np.log2(x))]
542+
543+
bounded_list= [True,True,True,True,True,
544+
False,False,False,True,True,
545+
False,
546+
True,True,True,
547+
False]
548+
549+
@pytest.mark.parametrize("string, func",
550+
zip(validstrings,results),
551+
ids=validstrings)
552+
deftest_values(self,string,func):
553+
func_parser=cbook._StringFuncParser(string)
554+
f=func_parser.function
555+
assert_array_almost_equal(f(self.x_test),func(self.x_test))
556+
557+
@pytest.mark.parametrize("string",validstrings,ids=validstrings)
558+
deftest_inverse(self,string):
559+
func_parser=cbook._StringFuncParser(string)
560+
f=func_parser.func_info
561+
fdir=f.function
562+
finv=f.inverse
563+
assert_array_almost_equal(finv(fdir(self.x_test)),self.x_test)
564+
565+
@pytest.mark.parametrize("string",validstrings,ids=validstrings)
566+
deftest_get_inverse(self,string):
567+
func_parser=cbook._StringFuncParser(string)
568+
finv1=func_parser.inverse
569+
finv2=func_parser.func_info.inverse
570+
assert_array_almost_equal(finv1(self.x_test),finv2(self.x_test))
571+
572+
@pytest.mark.parametrize("string, bounded",
573+
zip(validstrings,bounded_list),
574+
ids=validstrings)
575+
deftest_bounded(self,string,bounded):
576+
func_parser=cbook._StringFuncParser(string)
577+
b=func_parser.is_bounded_0_1
578+
assert_array_equal(b,bounded)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp